我正在开发一个自定义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中使用-我可以接受这个
对这个问题有一些很好的评论,我花了一些时间研究了解决这个问题的各种方法。
首先,我最初的要求是不可能的。我的意思是,如果您采用模块方式,那么模块应该在物理上存在于目标机器上,以便能够将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
- 脚本运行的帐户,应该添加到远程服务器的本地管理员帐户
- 如果您计划在远程会话中访问文件共享,请确保了解多跳身份验证并采取适当的操作
- 确保你的防病毒是你的朋友,不会把你送到PowerShell地狱
这是另一种方法:在远程会话中重新创建模块,而不复制任何文件。
我没有尝试处理模块之间的依赖关系,但对于简单的自包含模块,这似乎工作得很好。它依赖于本地会话中可用的模块,因为这使得确定导出更容易,但是通过一些额外的工作,它也可以与模块文件一起工作。
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;
}
如果没有任何"hack",我不相信这是可以支持的。明智的做法可能是将模块放在公共位置(如fileserver)上,并在需要时将其导入服务器。例:
$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 \fileserverfoldersmodulelocation...
<# use function defined in MyCustomModule here #>
}
从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 }
<<p> Import-LocalModuleToRemoteSession函数/strong> 注意它不会加载模块依赖项
<#
.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: $_"
}
}
}
如何将scriptblock从您的自定义函数中取出并使用Invoke-command
将其发送到terget服务器
Import-module YourModule
$s = [scriptblock]::Create($(get-item Function:Your-ModuleFunction).Definition)
Invoke-Command -ScriptBlock $s -Computername s1,s2,sn
感谢这个帖子,它是有帮助的....
但是我实际上重写了这个函数
请注意,本文中的原始函数或重写的函数都不包含模块清单数据。所以你不能依赖于模块上的版本检查。
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
}