send-file.ps1

<#PSScriptInfo
 
.VERSION 1.0
 
.GUID 7dc95080-07d2-461c-b928-56a4480204a1
 
.AUTHOR Adam Bertram
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
#>


<#
 
.Synopsis
This function sends a file (or folder of files recursively) to a destination WinRm session.
 
.DESCRIPTION
This function sends a file (or folder of files recursively) to a destination WinRm session. This function was originally built by Lee Holmes but has been modified to recursively send folders of files as well as to support UNC paths.
 
.NOTES
Name : Send-File
Author : Adam Bertram
DateCreated: 2020-10-24
DateUpdated:
  
.PARAMETER Path
The local or UNC folder path that you'd like to copy to the session. This also support multiple paths in a comma-delimited format. If this is a UNC path, it will be copied locally to accomodate copying. If it's a folder, it will recursively copy all files and folders to the destination.
 
.PARAMETER Destination
The local path on the remote computer where you'd like to copy the folder or file. If the folder does not exist on the remote computer it will be created.
 
.PARAMETER Session
The remote session. Create with New-PSSession.
 
.EXAMPLE
$session = New-PSSession -ComputerName MYSERVER
Send-File -Path C:\test.txt -Destination C:\ -Session $session
 
This example will copy the file C:\test.txt to be C:\test.txt on the computer MYSERVER
 
#>

[CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Destination,
        
        [Parameter(Mandatory)]
        [System.Management.Automation.Runspaces.PSSession]$Session
    )
    process
    {
        foreach ($p in $Path)
        {
            try
            {
                if ($p.StartsWith('\\'))
                {
                    Write-Verbose -Message "[$($p)] is a UNC path. Copying locally first"
                    Copy-Item -Path $p -Destination ([environment]::GetEnvironmentVariable('TEMP', 'Machine'))
                    $p = "$([environment]::GetEnvironmentVariable('TEMP', 'Machine'))\$($p | Split-Path -Leaf)"
                }
                if (Test-Path -Path $p -PathType Container)
                {
                    Write-Log -Source $MyInvocation.MyCommand -Message "[$($p)] is a folder. Sending all files"
                    $files = Get-ChildItem -Path $p -File -Recurse
                    $sendFileParamColl = @()
                    foreach ($file in $Files)
                    {
                        $sendParams = @{
                            'Session' = $Session
                            'Path' = $file.FullName
                        }
                        if ($file.DirectoryName -ne $p) ## It's a subdirectory
                        {
                            $subdirpath = $file.DirectoryName.Replace("$p\", '')
                            $sendParams.Destination = "$Destination\$subDirPath"
                        }
                        else
                        {
                            $sendParams.Destination = $Destination
                        }
                        $sendFileParamColl += $sendParams
                    }
                    foreach ($paramBlock in $sendFileParamColl)
                    {
                        Send-File @paramBlock
                    }
                }
                else
                {
                    Write-Verbose -Message "Starting WinRM copy of [$($p)] to [$($Destination)]"
                    # Get the source file, and then get its contents
                    $sourceBytes = [System.IO.File]::ReadAllBytes($p);
                    $streamChunks = @();
                    
                    # Now break it into chunks to stream.
                    $streamSize = 1MB;
                    for ($position = 0; $position -lt $sourceBytes.Length; $position += $streamSize)
                    {
                        $remaining = $sourceBytes.Length - $position
                        $remaining = [Math]::Min($remaining, $streamSize)
                        
                        $nextChunk = New-Object byte[] $remaining
                        [Array]::Copy($sourcebytes, $position, $nextChunk, 0, $remaining)
                        $streamChunks +=, $nextChunk
                    }
                    $remoteScript = {
                        if (-not (Test-Path -Path $using:Destination -PathType Container))
                        {
                            $null = New-Item -Path $using:Destination -Type Directory -Force
                        }
                        $fileDest = "$using:Destination\$($using:p | Split-Path -Leaf)"
                        ## Create a new array to hold the file content
                        $destBytes = New-Object byte[] $using:length
                        $position = 0
                        
                        ## Go through the input, and fill in the new array of file content
                        foreach ($chunk in $input)
                        {
                            [GC]::Collect()
                            [Array]::Copy($chunk, 0, $destBytes, $position, $chunk.Length)
                            $position += $chunk.Length
                        }
                        
                        [IO.File]::WriteAllBytes($fileDest, $destBytes)
                        
                        Get-Item $fileDest
                        [GC]::Collect()
                    }
                    
                    # Stream the chunks into the remote script.
                    $Length = $sourceBytes.Length
                    $streamChunks | Invoke-Command -Session $Session -ScriptBlock $remoteScript
                    Write-Verbose -Message "WinRM copy of [$($p)] to [$($Destination)] complete"
                }
            }
            catch
            {
                Write-Error $_.Exception.Message
            }
        }
    }