如何将自定义PowerShell模块导入远程会话?

如何将自定义PowerShell模块导入远程会话?,powershell,powershell-2.0,powershell-remoting,powershell-3.0,Powershell,Powershell 2.0,Powershell Remoting,Powershell 3.0,我正在开发一个自定义PowerShell模块,我希望在与其他计算机的远程会话上下文中使用它。以下代码(显然不起作用)解释了我试图实现的目标: import-module .\MyCustomModule.psm1 $session = new-pssession -computerName server01 invoke-command -session $session -scriptblock { <# use function defined in MyCustomModule

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

import-module .\MyCustomModule.psm1
$session = new-pssession -computerName server01
invoke-command -session $session -scriptblock { 
  <# use function defined in MyCustomModule here #> 
}
导入模块。\MyCustomModule.psm1
$session=new pssession-computerName server01
调用命令-session$session-scriptblock{
}
第一个问题是,是否有可能实现这种情况?我的意思是,我只希望我的自定义模块实际存在于我的机器上,而不是远程服务器上

我发现了,但我没有管理它工作-它不允许创建从远程计算机返回到本地计算机的会话。可能,我遇到了在对该线程的评论中提到的配置限制。。。此外,作者还提到了对我的解决方案至关重要的性能影响

如果可能的话,那怎么办


PowerShell的版本目前不是一个约束-如果该解决方案仅在PS 3.0中可用-我可以接受它。

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

$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 #> 
}
$session=new pssession-computerName server01
调用命令-session$session-scriptblock{
#将executionpolicy设置为仅跳过此会话中的警告
设置ExecutionPolicy-ExecutionPolicy旁路-作用域进程
#从公共位置导入模块
导入模块\\fileserver\folders\modulelocation。。。
}

对这个问题有一些很好的评论,我花了一些时间研究解决这个问题的各种方法

首先,我最初要求的是不可能的。我的意思是,如果您采用模块方式,那么模块应该实际存在于目标机器上,以便能够
将模块导入远程会话

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

模块法 应遵循的流程:

  • 将每个逻辑上不同的功能放入PowerShell模块(
    *.psm1
  • 将模块分发到远程机器,并扩展
    PSModulePath
    变量以包括新模块的位置
  • 在客户端计算机上,创建到远程服务器的新会话,并使用
    Invoke命令-session$s-ScriptBlock{…}
  • 在脚本块中,从
    导入模块CustomModule
    开始-它将在远程机器上搜索
    CustomModule
    ,显然会找到它
优势 以下是喜欢这种方法的原因:

  • 传统模块角色的结果——促进可重用库的创建
  • 根据这本伟大的书,“模块可以用来创建特定领域的应用程序”。据我所知,它可以通过组合模块嵌套和混合脚本/二进制模块来实现,以公开特定于某个领域的直观界面。基本上,对于基于PowerShell的部署框架的目标,这是我最看重的一个
缺点 必须考虑以下因素:

  • 您必须找到一种将自定义模块交付到远程机器的方法。我已经玩过了,我不确定它是否适合这项任务,但还有其他选择,例如,MSI安装程序或共享文件夹中的纯
    xcopy
    。此外,交付机制应该支持升级/降级和(最好)多实例安装,但这与我的任务有关,而不是一般的问题
脚本方法 应遵循的流程:

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

  • 这很简单-您不必了解模块的特性。只需编写简单的PowerShell脚本即可
  • 您不必向远程机器交付任何东西-这使得解决方案更加简单,维护时更不容易出错
缺点 当然,这并不理想:

  • 对解决方案的控制较少:例如,如果您将一组功能“导入”到会话中,则所有功能都是“导入”的,并且对用户可见,因此没有“封装”,等等。我相信许多解决方案都可以接受这一点,因此不要仅基于这一点来决定。
  • 每个文件中的功能必须是自包含的-从那里进行的任何点源或模块导入都将搜索远程机器,而不是本地机器
最后,我应该说远程机器仍然需要为远程处理做好准备。这就是我的意思:

  • 执行策略应更改为其他内容,因为默认情况下它是受限的:
    Set ExecutionPolicy Unrestricted
  • 应启用PowerShell远程处理:
    启用PSRemoting
  • 脚本运行时使用的帐户应添加到远程服务器的本地管理员
  • 如果你
    Import-module YourModule
    $s = [scriptblock]::Create($(get-item Function:\Your-ModuleFunction).Definition)
    
    Invoke-Command -ScriptBlock $s -Computername s1,s2,sn
    
    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;
    }
    
    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
    }
    
    $s = New-PSSession MyTargetMachine
    Get-Module MyLocalModule | Import-LocalModuleToRemoteSession -Session $s -Force
    # Show module is loaded
    Invoke-Command $s -ScriptBlock { Get-Module }
    
    <#
        .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: $_"
            }
        }
    }