internal/functions/Send-File.ps1
function Send-File { <# .SYNOPSIS This function sends a file (or folder of files recursively) to a destination WinRm session. This function was originally built by Lee Holmes (http://poshcode.org/2216) but has been modified to recursively send folders of files as well as to support UNC paths. Author: Adam Bertram From: https://gallery.technet.microsoft.com/scriptcenter/Send-Files-or-Folders-over-273971bf .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 .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.IO.FileInfo #> [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) { if ($p.StartsWith('\\')) { Write-Message -Level Verbose -Message "[$($p)] is a UNC path. Copying locally first" Copy-Item -Path $p -Destination ([environment]::GetEnvironmentVariable('TEMP', 'Machine')) -ErrorAction Stop $p = "$([environment]::GetEnvironmentVariable('TEMP', 'Machine'))\$($p | Split-Path -Leaf)" } if (Test-Path -Path $p -PathType Container) { Write-Message -Level Verbose -Message "[$($p)] is a folder. Sending all files" $files = Get-ChildItem -Path $p -File -Recurse -ErrorAction Stop $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-Message -Level 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 -ErrorAction Stop Write-Message -Level Verbose -Message "WinRM copy of [$($p)] to [$($Destination)] complete" } } } } |