In an effort to continually improve my deployment scripts, I stumbled on a function someone wrote to allow me to push files over WinRM which alleviates me opening additional ports just for installation. It’s a nice little function I’ve start including in my scripts. I attached the code at the bottom for reference.
That said, some of my installs I need to actually fetch files from the resulting build, not just push files, so I was wondering if I should try to reverse engineer a “Get-File” function or if there was a more “natural” way to use the same logic but in reverse: use the WINRM service to retrieve a file rather than opening additional ports. Has anyone done anything like this? I’m curious as to other people’s approach.
To restate the goal: I’d like to be able to push/pull small simple configs to/from remote systems without having to open the additional file and print sharing ports.
function Send-File { [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 } } } }#End Function Send-File