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]
        $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
    {
        Write-LogFunctionEntry
        $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)
        {
            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)
        {
            $destination = if ($Scope -eq 'AllUsers')
            {
                Invoke-Command -Session $s -ScriptBlock {
                    $destination = Join-Path -Path ([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath WindowsPowerShell\Modules
                    if (-not (Test-Path -Path $destination))
                    {
                        mkdir -Path $destination -Force | Out-Null
                    }
                    $destination
                }
            }
            else
            {
                Invoke-Command -Session $s -ScriptBlock { 
                    $destination = Join-Path -Path ([System.Environment]::GetFolderPath('MyDocuments')) -ChildPath WindowsPowerShell\Modules
                    if (-not (Test-Path -Path $destination))
                    {
                        mkdir -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}$')
            {
                #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
    {
        Write-LogFunctionExit
    }
}