Common/Public/Send-ModuleToPsSession.ps1
function Send-ModuleToPSSession { [CmdletBinding( RemotingCapability = 'PowerShell', #V3 and above, values documented here: http://msdn.microsoft.com/en-us/library/system.management.automation.remotingcapability(v=vs.85).aspx SupportsShouldProcess = $false, ConfirmImpact = 'None', DefaultParameterSetName = '' )] [OutputType([System.IO.FileInfo])] #OutputType is supported in 3.0 and above param ( [Parameter( HelpMessage = 'Provide the source module info object', Position = 0, Mandatory = $true, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [PSModuleInfo] $Module, [Parameter( HelpMessage = 'Enter the destination path on the remote computer', Position = 1, Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [System.Management.Automation.Runspaces.PSSession[]] $Session, [ValidateSet('AllUsers', 'CurrentUser')] [string] $Scope = 'AllUsers', [switch] $IncludeDependencies, [switch] $Move, [switch] $Encrypt, [switch] $NoWriteBuffer, [switch] $Verify, [switch] $Force, [switch] $NoClobber, [ValidateRange(1KB, 7.4MB)] #might be good to have much higher top end as the underlying max is controlled by New-PSSessionOption [uint32] $MaxBufferSize = 1MB ) begin { $isCalledRecursivly = (Get-PSCallStack | Where-Object Command -eq $MyInvocation.InvocationName | Measure-Object | Select-Object -ExpandProperty Count) -gt 1 } process { $fileParams = ([hashtable]$PSBoundParameters).Clone() [void]$fileParams.Remove('Module') [void]$fileParams.Remove('Scope') [void]$fileParams.Remove('IncludeDependencies') if ($Local:Module.ModuleType -eq 'Script' -and ($Local:Module.Path -notmatch '\.psd1$')) { Write-Error "Cannot send the module '$($Module.Name)' that is not described by a .psd1 file" return } #Remove any sessions where the same or newer module version already exists if (-not $Force.IsPresent) { Write-Verbose 'Filtering out target sessions that do not need the module' $Session = foreach ($item in $PSBoundParameters.Session) { #recursive calls will need to refresh the cached module list because we may have just placed new modules there if ($isCalledRecursivly) { $modules = Get-Module -PSSession $item -ListAvailable -Name $Local:Module.Name -Refresh } else { $modules = Get-Module -PSSession $item -ListAvailable -Name $Local:Module.Name } #no version of the module installed, select for sending if (-not $modules) { $item } else { #determine what versions we have $versions = $modules | ForEach-Object { [System.Version]$_.Version } | Sort-Object -Unique -Descending $highestVersion = $versions | Select-Object -First 1 #if the version we are sending is newer than the highest installed version, select for sending if ([System.Version]$Local:Module.Version -gt $highestVersion) { $item } elseif ($highestVersion -gt [System.Version]$Local:Module.Version) { write-Warning "Skipping $($item.ComputerName) which has a higher version $highestVersion of the module installed" } else { write-Verbose "Skipping $($item.ComputerName) because the same version of the module is installed already" } } } } foreach ($s in $Session) { [version]$sessionVersion = Invoke-Command -Session $s -ScriptBlock { if ($PSEdition -eq 'core') {return ('{0}.{1}.{2}' -f $PSVersionTable.PSVersion.Major,$PSVersionTable.PSVersion.Minor,$PSVersionTable.PSVersion.Patch)} $PSVersionTable.PSVersion } if ($Local:Module.PowerShellVersion -gt $sessionVersion) { Write-Warning -Message "Module $($Local:Module.Name) requires PS Version $($Local:Module.PowerShellVersion). We only found $($sessionVersion) on $($s.ComputerName). Skipping." continue } $destination = if ($Scope -eq 'AllUsers') { Invoke-Command -Session $s -ScriptBlock { $destination = if (-not $IsLinux -and -not $IsMacOs) { if ($PSVersionTable.PSVersion.Major -ge 4) { Join-Path -Path ([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath WindowsPowerShell\Modules } else { Join-Path -Path ([System.Environment]::GetFolderPath('System')) -ChildPath WindowsPowerShell\v1.0\Modules } } else { '/usr/local/share/powershell/Modules' } if (-not (Test-Path -Path $destination)) { New-Item -ItemType Directory -Path $destination -Force | Out-Null } $destination } } else { Invoke-Command -Session $s -ScriptBlock { $destination = if (-not $IsLinux -and -not $IsMacOs) { Join-Path -Path ([System.Environment]::GetFolderPath('MyDocuments')) -ChildPath WindowsPowerShell\Modules } else { '~/.local/share/powershell/Modules' } if (-not (Test-Path -Path $destination)) { New-Item -ItemType Directory -Path $destination -Force | Out-Null } $destination } } Write-Verbose "Sending psd1 manifest module in directory $($Local:Module.ModuleBase)" if (($Local:Module.ModuleBase -match '\d{1,4}\.\d{1,4}\.\d{1,4}\.\d{1,4}$' -or $Local:Module.ModuleBase -match '\d{1,4}\.\d{1,4}\.\d{1,4}$') -and $sessionVersion -ge ([version]::new(5,0))) { #parent folder contains a specific version. In order to copy the module right, the parent of this parent is required $Local:moduleParentFolder = Split-Path -Path $Local:Module.ModuleBase -Parent } else { $Local:moduleParentFolder = $Local:Module.ModuleBase } Send-Directory -SourceFolderPath $Local:moduleParentFolder -DestinationFolderPath $destination -Session $s if ($PSBoundParameters.IncludeDependencies -and ($Local:Module.RequiredAssemblies -or $Local:Module.RequiredModules)) { foreach ($requiredModule in $Module.RequiredModules) { $requiredModule = Get-Module -ListAvailable $requiredModule | Sort-Object Version -Descending | Select-Object -First 1 $params = ([hashtable]$PSBoundParameters).Clone() [void]$params.Remove('Module') Send-ModuleToPSSession -Module $requiredModule @params } foreach ($requiredAssembly in $Local:Module.RequiredAssemblies) { if (Test-Path -Path $requiredAssembly) { Send-FileToPSSession -Source (Get-Item -Path $requiredAssembly -Force).FullName @fileParams } else { write-Warning "Sending required assemblies that do not have the full path information is not currently supported, $requiredAssembly not sent" } } } } } end { } } |