web-dev-qa-db-ja.com

カスタム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でのみ利用可能な場合-私はこれで生きることができます。

35
Yan Sklyarenko

この質問には素晴らしいコメントがいくつかあり、私はこの問題に取り組むためのさまざまな方法を調査するのに少し時間を費やしました。

そもそも、私が最初に求めたことは不可能です。つまり、モジュールを使用する場合、リモートセッションに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スクリプトを作成するだけです。
  • リモートマシンに何も提供する必要はありません。これにより、ソリューションがさらにシンプルになり、メンテナンスのエラーが発生しにくくなります。

欠点

もちろん、それは理想的ではありません。

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

最後に、リモート処理のためにリモートマシンを準備する必要があると言っておく必要があります。これは私が意味するものです:

  • 実行ポリシーはデフォルトで制限されているため、何かに変更する必要があります:Set-ExecutionPolicy Unrestricted
  • PowerShellリモート処理を有効にする必要があります:Enable-PSRemoting
  • リモートサーバーのローカル管理者に追加する必要があるスクリプトの実行アカウント
  • リモートセッションでファイル共有にアクセスする予定がある場合は、 マルチホップ認証と適切なアクションの実行
  • アンチウイルスがあなたの友人であり、あなたを PowerShell hell に送らないことを確認してください
43
Yan Sklyarenko

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

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

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

これは、「ハッキング」なしですぐにサポートされるとは思わない。モジュールをファイルサーバーなどの公共の場所に置き、必要なときにサーバーにインポートするのがおそらく賢明でしょう。例:

$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 #> 
}
3
Frode F.

このスレッドをありがとう、それは役に立ちました…。

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

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

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

カスタム関数からスクリプトブロックを作成し、Invoke-commandを使用してターゲットサーバーに送信する方法について

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

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