1

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

phuclv
  • 37,963
  • 15
  • 156
  • 475
user613307
  • 13
  • 1
  • 5
  • Search for `clip /?` At least show us your efforts. – elzooilogico Apr 30 '17 at 17:15
  • For the copy see `clip /?` For the paste I use a 3rd party tool [winclip.exe](https://www.dmst.aueb.gr/dds/sw/outwit/) which has a `-p` paste switch. –  Apr 30 '17 at 17:18
  • 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 Apr 30 '17 at 18:14
  • 1
    Possible duplicate of [How can you get the clipboard contents with a Windows command?](http://stackoverflow.com/questions/17819814/how-can-you-get-the-clipboard-contents-with-a-windows-command). – user613307 Apr 30 '17 at 21:58

2 Answers2

2

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()
}
rojo
  • 24,000
  • 5
  • 55
  • 101
  • Thanks your copy script works great! but I can't get you paste script to work in Windows 7 Powershell, it just quietly exits with no pasting done. i'm new at this stuff so maybe i'm doing it wrong ? – user613307 May 20 '17 at 04:20
  • @user613307 These are .bat scripts, not PowerShell scripts, even though they contain PowerShell hybrid code. They're intended to be run from the cmd console, not from the PowerShell ISE. If running from a PS prompt, you need to do `cmd /c scriptname.bat` to launch the script with the cmd interpreter. – rojo May 20 '17 at 04:29
  • Hm. What a strange mistake you made in the fcopy.bat file. This code can only be called from the command line, but not from the batch file. It will be better not have %CD% in the batch code. You need to change set "wd = %CD%" to set "wd = %USERPROFILE%" in fcopy.bat. The copy script is called easier than you described. For example, %USERPROFILE%\Desktop\fcopy.bat C:\Windows\explorer.exe – Garric May 26 '20 at 15:38
  • After replacing %СD% with %USERPROFILE% fcopy.bat works with batch files and shortcuts, but does not work when called from relative shortcuts. – Garric May 26 '20 at 16:20
1

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
elzooilogico
  • 1,659
  • 2
  • 17
  • 18
  • when a run your .bat script in Powershell is just says: ECHO is off. it does not show the clipboard contents, am i doing it wrong ? – user613307 May 20 '17 at 04:04
  • @user613307 this is a batch script. it was what you originally asked. save as `blahblah.bat` or `blahblah.cmd` and run from `command line` not from `powershell`. here works perfectly (win 8). – elzooilogico May 20 '17 at 09:26