Using PowerShell it is possible to copy a list of files and folders to [Windows.Forms.Clipboard]::SetFileDropList($collection)
and to paste using [Windows.Forms.Clipboard]::GetFileDropList()
along with standard file copy or stream reader / writer methods. These methods truly use the clipboard, so files copied through Explorer can be pasted from the console, and vice versa. As a bonus, using scripting to manipulate the clipboard FileDropList allows you to append files to the list from multiple locations -- something the GUI interface doesn't allow.
PowerShell scripts can be created as Batch + PowerShell polyglots. So the answer to your question is, yes, it is possible to do what you want with .bat scripts.
fcopy.bat:
Usage:
fcopy.bat [switch] filemask [filemask [filemask [etc.]]]
fcopy.bat /?
Code:
<# : fcopy.bat -- https://stackoverflow.com/a/43924711/1683264
@echo off & setlocal
if "%~1"=="" ( goto usage ) else if "%~1"=="/?" goto usage
set args=%*
rem // kludge for PowerShell's reluctance to start in a dir containing []
set "wd=%CD%"
powershell -STA -noprofile "iex (${%~f0} | out-string)"
goto :EOF
:usage
echo Usage: %~nx0 [switch] filemask [filemask [filemask [...]]]
echo example: %~nx0 *.jpg *.gif *.bmp
echo;
echo Switches:
echo /L list current contents of clipboard file droplist
echo /C clear clipboard
echo /X cut files (Without this switch, the action is copy.)
echo /A append files to existing clipboard file droplist
goto :EOF
: end batch / begin powershell #>
$col = new-object Collections.Specialized.StringCollection
Add-Type -AssemblyName System.Windows.Forms
$files = $()
$switches = @{}
# work around PowerShell's inability to start in a directory containing brackets
cd -PSPath $env:wd
# cmd calling PowerShell calling cmd. Awesome. Tokenization of arguments and
# expansion of wildcards is profoundly simpler when using a cmd.exe for loop.
$argv = @(
cmd /c "for %I in ($env:args) do @echo(%~I"
cmd /c "for /D %I in ($env:args) do @echo(%~I"
) -replace "([\[\]])", "```$1"
$argv | ?{$_.length -gt 3 -and (test-path $_)} | %{ $files += ,(gi -force $_).FullName }
$argv | ?{$_ -match "^[/-]\w\W*$"} | %{
switch -wildcard ($_) {
"?a" { $switches["append"] = $true; break }
"?c" { $switches["clear"] = $true; break }
"?l" { $switches["list"] = $true; break }
"?x" { $switches["cut"] = $true; break }
default { "Unrecognized option: $_"; exit 1 }
}
}
if ($switches["clear"]) {
[Windows.Forms.Clipboard]::Clear()
"<empty>"
exit
}
if ($switches["list"] -and [Windows.Forms.Clipboard]::ContainsFileDropList()) {
$cut = [windows.forms.clipboard]::GetData("Preferred DropEffect").ReadByte() -eq 2
[Windows.Forms.Clipboard]::GetFileDropList() | %{
if ($cut) { write-host -f DarkGray $_ } else { $_ }
}
}
if ($files.Length) {
$data = new-object Windows.Forms.DataObject
if ($switches["cut"]) { $action = 2 } else { $action = 5 }
$effect = [byte[]]($action, 0, 0, 0)
$drop = new-object IO.MemoryStream
$drop.Write($effect, 0, $effect.Length)
if ($switches["append"] -and [Windows.Forms.Clipboard]::ContainsFileDropList()) {
[Windows.Forms.Clipboard]::GetFileDropList() | %{ $files += ,$_ }
}
$color = ("DarkGray","Gray")[!$switches["cut"]]
$files | select -uniq | %{ write-host -f $color $col[$col.Add($_)] }
$data.SetFileDropList($col)
$data.SetData("Preferred DropEffect", $drop)
[Windows.Forms.Clipboard]::Clear()
[Windows.Forms.Clipboard]::SetDataObject($data, $true)
$drop.Close()
}
paste.bat:
Usage:
paste.bat [destination]
Code:
<# : paste.bat -- https://stackoverflow.com/a/43924711/1683264
@echo off & setlocal
set args=%*
set "wd=%CD%"
powershell -STA -noprofile "iex (${%~f0} | out-string)"
goto :EOF
: end batch / begin powershell #>
$files = @()
[uint64]$totalbytes = 0
# kludge for PowerShell's reluctance to start in a path containing []
cd -PSPath $env:wd
Add-Type -AssemblyName System.Windows.Forms
if (-not [Windows.Forms.Clipboard]::ContainsFileDropList()) { exit }
# cut = 2; copy = 5
$de = [Windows.Forms.Clipboard]::GetData("Preferred DropEffect")
if ($de) {$cut = $de.ReadByte() -eq 2} else {$cut = $false}
function newdir ([string]$dir) {
[void](md $dir)
write-host -nonewline "* " -f cyan
write-host -nonewline "Created "
write-host $dir -f white
}
if ($env:args) {
[string[]]$argv = cmd /c "for %I in ($env:args) do @echo(%~I"
if (test-path -PSPath $argv[0] -type Container) {
try { cd -PSPath $argv[0] }
catch { write-host -f red "* Unable to change directory to $($argv[0])"; exit 1 }
} else {
try { newdir $argv[0] }
catch { write-host -f red "* Unable to create $($argv[0])"; exit 1 }
cd -PSPath $argv[0]
}
}
Add-Type @'
using System;
using System.Runtime.InteropServices;
namespace shlwapi {
public static class dll {
[DllImport("shlwapi.dll")]
public static extern long StrFormatByteSize64(ulong fileSize,
System.Text.StringBuilder buffer, int bufferSize);
}
}
'@
function num2size ($num) {
$sb = new-object Text.StringBuilder 16
[void][shlwapi.dll]::StrFormatByteSize64($num, $sb, $sb.Capacity)
$sb.ToString()
}
# returns the true drive letter, even if the supplied path contains a junction / symlink
function Resolve-Drive ([string]$path) {
while ($path.Length -gt 3) {
$dir = gi -force -PSPath $path
if ($dir.Attributes -band [IO.FileAttributes]::ReparsePoint) {
$path = $dir.Target
if ($path.Length -eq 3) { break }
}
$path = (resolve-path "$path\..").Path
}
$path
}
function Move-File ([string]$from, [string]$to) {
$srcdrive = Resolve-Drive $from
$destdrive = Resolve-Drive (Convert-Path .)
if ($srcdrive -eq $destdrive) {
Move-Item $from $to -force
write-host -n -f green "* "
write-host -n -f white (gi -force -PSPath $to).Name
write-host " moved."
} else {
Copy-File $from $to "Moving"
gi -force -PSPath $from | Remove-Item -force
}
}
# based on https://stackoverflow.com/a/13658936/1683264
function Copy-File {
param([string]$from, [string]$to, [string]$action = "Copying")
if (test-path -type leaf -PSPath $to) { gi -force -PSPath $to | Remove-Item -force }
$ffile = [io.file]::OpenRead($from)
$tofile = [io.file]::OpenWrite($to)
$fileobj = gi -force -PSPath $tofile.Name
$filesize = $ffile.length
$size = num2size $filesize
try {
if ($filesize -ge 16*1024*1024) {
$buffersize = 16*1024*1024
} else { $buffersize = $filesize }
Write-Progress `
-Id 1 `
-Activity "0% $action $size file" `
-status $fileobj.Name `
-PercentComplete 0
$sw = [System.Diagnostics.Stopwatch]::StartNew();
[byte[]]$buff = new-object byte[] $buffersize
[uint64]$total = [uint64]$count = 0
do {
$count = $ffile.Read($buff, 0, $buff.Length)
$tofile.Write($buff, 0, $count)
$total += $count
if (!$ffile.Length) {
$pctcomp = 0
} else {
[int]$pctcomp = ([int]($total/$ffile.Length* 100))
}
[int]$secselapsed = [int]($sw.elapsedmilliseconds.ToString())/1000
if ( $secselapsed -ne 0 ) {
[single]$xferrate = (($total/$secselapsed)/1mb)
} else {
[single]$xferrate = 0.0
}
if ($total % 1mb -eq 0) {
if ($pctcomp -gt 0) {
[int]$secsleft = ((($secselapsed/$pctcomp)* 100)-$secselapsed)
} else {
[int]$secsleft = 0
}
Write-Progress `
-Id 1 `
-Activity ($pctcomp.ToString() + "% $action $size file @ " + `
"{0:n2}" -f $xferrate + " MB/s") `
-status $fileobj.Name `
-PercentComplete $pctcomp `
-SecondsRemaining $secsleft
}
} while ($count -gt 0)
$sw.Stop()
$sw.Reset()
}
finally {
$tofile.Close()
$ffile.Close()
$ffile = gi -force -PSPath $from
$fileobj.CreationTime = $ffile.CreationTime
$fileobj.LastWriteTime = $ffile.LastWriteTime
if ( $secselapsed -ne 0 ) {
[string]$xferrate = "{0:n2} MB" -f (($total/$secselapsed)/1mb)
} else {
[string]$xferrate = num2size $fileobj.Length
}
write-host -nonewline "* " -f green
write-host -nonewline $fileobj.Name -f white
write-host (" written in $secselapsed second{0} at $xferrate/s." -f (`
"s" * ($secselapsed -ne 1)));
}
}
[Windows.Forms.Clipboard]::GetFileDropList() | %{
if (test-path -PSPath $_ -Type Leaf) {
$add = @($_.Trim(), ((Convert-Path .) + "\" + (gi -force -PSPath $_).Name))
if ($files -notcontains $add) {
$totalbytes += (gi -force -PSPath $_).Length
$files += ,$add
}
} else {
if (test-path -PSPath $_ -Type Container) {
$src = (Convert-Path -PSPath $_).Trim()
$dest = (Convert-Path .) + "\" + (gi -force -PSPath $src).Name
if (!(test-path -PSPath $dest)) { newdir $dest }
gci -PSPath $src -recurse -force | %{
$dest1 = $dest + $_.FullName.Replace($src, '')
if ((test-path -PSPath $_.FullName -Type Container) -and !(test-path -PSPath $dest1)) {
newdir $dest1
}
if (test-path -PSPath $_.FullName -Type Leaf) {
$add = @($_.FullName.Trim(), $dest1)
if ($files -notcontains $add) {
$totalbytes += $_.Length
$files += ,$add
}
}
}
}
}
}
[string]$totalsize = num2size $totalbytes
$destdrive = resolve-drive (Convert-Path .)
$capacity = (Get-PSDrive ($destdrive -split ':')[0]).Free
if ($totalbytes -gt $capacity) {
write-host -f red "* Not enough space on $destdrive"
exit 1
}
for ($i=0; $i -lt $files.length; $i++) {
Write-Progress `
-Activity "Pasting to $(Convert-Path .)" `
-Status ("Total Progress {0}/{1} files {2} total" `
-f ($i + 1), $files.length, $totalsize) `
-PercentComplete ($i / $files.length * 100)
if ($cut) {
Move-File $files[$i][0] $files[$i][1]
} else {
Copy-File $files[$i][0] $files[$i][1]
}
}
if ($cut) {
[Windows.Forms.Clipboard]::GetFileDropList() | %{
if (test-path -PSPath $_ -type Container) {
gi -force -PSPath $_ | Remove-Item -force -recurse
}
}
[Windows.Forms.Clipboard]::Clear()
}
clip /?
At least show us your efforts. - elzooilogicoclip /?
For the paste I use a 3rd party tool winclip.exe which has a-p
paste switch. - user6811411clip
, but Microsoft didn't bother to provide something to get it back from the clipboard. There are some utilities out there to do this (google forcmd clipboard paste
). But you can't copy "files" like Explorer does. - Stephan