1
votes

In a batch script can "Windows Explorer style" copy & paste be used?

For example

copy example.exe
some arbitrary  commands 
paste example.exe

Update: copying to the clipboard can do done through cmd but

It looks like it's not possible to paste anything but text with out third party utilities like WinClip & Paste

2
Search for clip /? At least show us your efforts. - elzooilogico
For the copy see clip /? For the paste I use a 3rd party tool winclip.exe which has a -p paste switch. - user6811411
No, this isn't possible in batch. You can put some text into the clipboard with clip, 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 for cmd clipboard paste). But you can't copy "files" like Explorer does. - Stephan

2 Answers

1
votes

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()
}
1
votes

You can read the clipboard content with powershell,

@echo off
set "myText=This is my text"

rem copy variable content to clipboard
set /p"=%myText%"<nul|clip

rem get clipboard content into variable
set "psCmd=powershell -Command "add-type -an system.windows.forms; [System.Windows.Forms.Clipboard]::GetText()""
for /F "usebackq delims=" %%# in (`%psCmd%`) do set "clipContent=%%#"

echo %clipContent%

exit/B