【问题标题】:How to import custom PowerShell module into the remote session?如何将自定义 PowerShell 模块导入远程会话?
【发布时间】:2013-01-21 15:23:32
【问题描述】:

我正在开发一个自定义 PowerShell 模块,我想在与另一台计算机进行远程会话的上下文中使用它。以下代码(显然不起作用)解释了我想要实现的目标:

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

第一个问题是是否有可能实现这种情况?我的意思是我只希望我的自定义模块物理存在于我的机器上,而不是远程服务器上。

我找到了this thread,但我没有设法让它工作——它不允许创建从远程机器回到本地机器的会话。可能,我遇到了该线程的 cmets 中某处提到的配置限制......此外,作者提到了对我的解决方案至关重要的性能影响......

如果可以,那怎么做?

PowerShell 的版本目前不受限制 - 如果解决方案仅在 PS 3.0 中可用 - 我可以接受。

【问题讨论】:

  • 我不相信这是支持的。您必须像他们尝试过的那样使用黑客。请问为什么不能在远程计算机上安装模块?这才是明智的解决方案。
  • @DavidBrabant,嗯,是的,非常接近。但是,我未能使那里的解决方案起作用,我明确引用了该线程以表明我尝试了该选项并要求替代方案:)
  • @Graimer,这个想法是使用任何可用的机器来执行一些部署场景,而不需要在远程机器上安装任何东西。但如果不支持该场景,我们将不得不忍受这个......
  • 为什么不把模块放在公共位置,用Import-Module \\fileserver\folders\modulefolder 开始你的脚本块?确保在导入之前对其进行数字签名或使用Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process 之类的内容以绕过警告?

标签: powershell powershell-2.0 powershell-remoting powershell-3.0


【解决方案1】:

这个问题有一些很棒的方法,我花了一些时间研究解决问题的各种方法。

首先,我最初要求的内容是不可能的。我的意思是,如果你采用模块方式,那么模块应该物理存在于目标机器上,以便能够Import-Module 进入远程会话。

为了进一步抽象我的问题,我正在尝试为产品部署创建一个可重用的基于 PowerShell 的框架。这将是一种推送方式部署,这意味着我们鼓励人们在本地机器上运行一些脚本以部署到某个远程服务器。就我对该地区的调查而言,有两种可能的方式对常识很友好。

模块方法

要遵循的过程:

  • 将每个逻辑上不同的功能块放入 PowerShell 模块 (*.psm1)
  • 将模块分发到远程计算机并扩展PSModulePath 变量以包含新模块位置
  • 在客户端机器上,创建一个到远程服务器的新会话,并使用Invoke-Command -Session $s -ScriptBlock {...}
  • 在从Import-Module CustomModule 开始的脚本块中 - 它会在远程机器上搜索CustomModule,显然会找到它

优势

以下是喜欢这种方法的原因:

  • 传统模块角色的结果 - 促进可重用库的创建
  • 根据伟大的书Windows PowerShell in Action,“模块可用于创建特定领域的应用程序”。据我了解,可以通过结合模块嵌套和混合脚本/二进制模块来暴露特定领域的直观界面来实现。基本上,这是我最看重的基于 PowerShell 的部署框架的目标

缺点

需要考虑以下重要事项:

  • 您必须找到一种方法将自定义模块传送到远程计算机。我玩过NuGet,我不确定它是否适合这项任务,但还有其他选项,例如,MSI 安装程序或共享文件夹中的普通xcopy。此外,交付机制应该支持升级/降级和(最好)多实例安装,但这与我的任务比一般问题更相关

脚本方法

要遵循的过程:

  • 将每个逻辑上不同的功能放在单独的 PowerShell 脚本 (*.ps1) 中
  • 在客户端计算机上,创建与远程服务器的新会话,并使用Invoke-Command -Session $s -FilePath .\myscript.ps1 将脚本中定义的函数加载到远程会话中
  • 使用另一个 Invoke-Command -Session $s -ScriptBlock {...} 并引用您的自定义函数 - 它们将在会话中出现

优势

以下是这种方法的优点:

  • 这很简单——您不必了解模块特性。只需编写简单的 PowerShell 脚本即可
  • 您不必向远程机器提供任何东西 - 这使得解决方案更加简单,并且在维护过程中更不容易出错

缺点

当然,这并不理想:

  • 对解决方案的控制较少:例如,如果您将一组函数“导入”到会话中,则所有这些函数都被“导入”并且对用户可见,因此没有“封装”等。我相信很多解决方案都可以接受这一点,所以不要只基于这一点来决定
  • 每个文件中的功能必须是自包含的 - 从那里导入的任何点源或模块都将搜索远程计算机,而不是本地计算机

最后,我应该说远程机器仍然需要为远程处理做好准备。这就是我的意思:

  • 执行策略应该改成什么,因为默认限制:Set-ExecutionPolicy Unrestricted
  • 应启用 PowerShell 远程处理:Enable-PSRemoting
  • 脚本运行的帐户应添加到远程服务器的本地管理员中
  • 如果您计划在远程会话中访问文件共享,请确保您了解multi-hop authentication and take proper actions
  • 确保您的防病毒软件是您的朋友,不会将您发送到PowerShell hell

【讨论】:

  • 严,这是一个非常详细且通过的回应。感谢您的信息。
  • 很高兴知道它很有帮助:)
  • 有谁知道在过去 3 年中是否发生了一些变化以使这更容易?
  • 这似乎是模块方法的主要缺点:它打开了一个冗余的蠕虫罐(每个目标上的模块/脚本版本不一致)。
  • @JustinHelgerson 对于 4 年后的价值,我 added an answer 展示了如何在 PS 5.0 中做到这一点,而无需共享目录、在远程机器上预安装模块或重新创建模块动态的。
【解决方案2】:

这是另一种方法:在远程会话中重新创建模块,而不复制任何文件。

我没有尝试处理模块之间的依赖关系,但这似乎适用于简单的自包含模块。它依赖于本地会话中可用的模块,因为这使得确定导出更容易,但通过一些额外的工作,它也可以与模块文件一起使用。

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

【讨论】:

  • 当我第一次使用它时,这对我来说效果很好,不像其他一些选项。感谢您的贡献!
【解决方案3】:

我不相信这是在没有任何“黑客”的情况下得到支持的。明智的做法可能是将模块放在文件服务器等公共位置,并在需要时将其导入服务器。例如:

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

【讨论】:

  • 这种方法的问题将是克服双跳问题。
【解决方案4】:

从 PS 5.0 开始,我认为现在有另一种更清洁的方式:

利用 Copy-Item 的 ToSession 参数将本地模块复制到远程机器。

这不涉及以前解决方案的缺点:

  • 无需事先将模块复制到远程机器上
  • 没有共享文件夹或动态重新创建模块:

使用示例:

$s = New-PSSession MyTargetMachine
Get-Module MyLocalModule | Import-LocalModuleToRemoteSession -Session $s -Force
# Show module is loaded
Invoke-Command $s -ScriptBlock { Get-Module }

Import-LocalModuleToRemoteSession 函数

注意它不会加载模块依赖项

<#
    .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: $_"
        }
    }
}

【讨论】:

    【解决方案5】:

    如何从您的自定义函数中制作脚本块并使用 Invoke-command 将其发送到 terget 服务器

    Import-module YourModule
    $s = [scriptblock]::Create($(get-item Function:\Your-ModuleFunction).Definition)
    
    Invoke-Command -ScriptBlock $s -Computername s1,s2,sn
    

    【讨论】:

    • 不会工作,因为模块可以引用它自己的函数。
    【解决方案6】:

    感谢这个帖子,它很有帮助......。

    但我实际上重写了函数。

    请注意,本文中的原始函数或此重写的函数都包含模块清单数据。所以你不能依赖模块上的版本检查。

    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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      相关资源
      最近更新 更多