39

別のコンピューターへのリモート セッションのコンテキストで使用したいカスタム 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

6 に答える 6

47

この質問には素晴らしいコメントがいくつかありました。私は時間をかけて、問題にアプローチするためのさまざまな方法を調査しました。

そもそも、私が最初に求めたものは不可能です。Import-Moduleつまり、モジュールの方法を使用する場合、リモート セッションに入ることができるようにするには、モジュールがターゲット マシン上に物理的に存在する必要があります。

私の質問をさらに抽象化するために、製品展開用の再利用可能な PowerShell ベースのフレームワークを作成しようとしています。これはプッシュ方式の展開になる予定です。つまり、ローカル マシンでスクリプトを実行して、リモート サーバーに展開することをお勧めします。私がその地域を調査した限り、常識に優しい2つの方法が考えられます。

モジュールアプローチ

従うべきプロセス:

  • 論理的に異なる機能をそれぞれ 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 スクリプトを作成するだけです
  • リモート マシンに何も配信する必要はありません。これにより、ソリューションがさらにシンプルになり、メンテナンス時にエラーが発生しにくくなります。

短所

確かに、それは理想的ではありません:

  • ソリューションを制御することはあまりありません。たとえば、一連の関数をセッションに「インポート」すると、それらはすべて「インポート」されてユーザーに表示されるため、「カプセル化」などはありません。多くのソリューションがあると確信していますこの点だけで判断しないでください。
  • 各ファイルの機能は自己完結型でなければなりません - そこからのドットソースまたはモジュールのインポートは、ローカルマシンではなくリモートマシンを検索します

最後に、リモート マシンはリモート処理の準備が必要です。これが私が意味することです:

于 2013-02-15T06:39:06.657 に答える
7

別の方法を次に示します。ファイルをコピーせずに、リモート セッションでモジュールを再作成します。

モジュール間の依存関係に対処しようとはしていませんが、これは単純な自己完結型モジュールではうまくいくようです。モジュールがローカル セッションで利用可能であることに依存しているため、これによりエクスポートの決定が容易になりますが、少し余分な作業を行うと、モジュール ファイルでも機能します。

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;
}
于 2016-04-19T11:07:49.937 に答える
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 #> 
}
于 2013-01-21T15:53:18.737 に答える
1

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: $_"
        }
    }
}
于 2020-12-21T17:27:09.557 に答える
0

カスタム関数から scriptblock を作成し、それを terget サーバーに送信するにはどうすればよいですか?Invoke-command

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

Invoke-Command -ScriptBlock $s -Computername s1,s2,sn
于 2015-09-21T15:56:52.703 に答える
0

このスレッドのおかげで役に立ちました…。

しかし、実際には関数を書き直しました。

この投稿の元の関数またはこの書き換えられた関数には、モジュール マニフェスト データが含まれていないことに注意してください。そのため、モジュールのバージョン チェックに頼ることはできません。

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
}
于 2019-04-04T20:56:39.720 に答える