How to Transfer Files Via SSH or WinRM

Secured Shell (SSH) is the prevalent standard for remote accessing of Linux systems. Even Microsoft has embraced this style of efficient remote controls via its implementation of WinRM.

Often, System Administrators need to transfer files to remote machines that may not have File Transfer Procotol (FTP), FTP Secured (FTPS), or Secured FTP (SFTP) installed. The fallback would be to perform this transmission using the available SSH session. That tool is called Secured Copy (SCP). Here is the syntax to run this utility:

General usage:
scp <source> <destination>

To copy a file from Remote to Local while logged into Remote:
scp /path/to/file username@Local:/path/to/destination

To copy a file from Remote to Local while logged into Local:
scp username@Remote:/path/to/file /path/to/destination

What about WinRM? We also have those commands here:

To copy a file from Local to Remote while logged into Local
Copy-Item –Path C:\Source\file1.txt –Destination '\\Remote\c$\destination'

To copy a file from Remote to Local while logged into Local
Copy-Item –Path \\Remote\C:$\Source\file1.txt –Destination 'C:\destination'

Wait-a-minute... This Copy-Item command is reliant on SMB (port 445). This defeats the purpose to being able to copy over the secure ports such as HTTPS/443 that WinRM should be running on. Ignoring the copying speed trade-off, here are some methods to transfer files using only WinRM ports.


Method 1: Copying from Local to Remote
$session = New-PSSession –ComputerName REMOTE
Copy-Item –Path C:\Source\file1.txt –Destination 'C:\Destination\' –ToSession $session
$session | Remove-PSSession
Method 2: Copying from Local to Remote with 1-line
Copy-Item –Path C:\Source\file1.txt –Destination 'C:\Destination\ –ToSession (New-PSSession –ComputerName SERVER1)

Here’s an even cooler PowerShell function, written by adamtheautomator.com guy.

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.
.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)
{
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
}
}
}

}

Leave a Reply

Your email address will not be published. Required fields are marked *