Copy-Item, Move-Item: Dateien kopieren und verschieben mit PowerShell

    Powershell-LogoPowerShell bietet eine ganze Sammlung von Cmdlets für das Datei-Management. Für das Kopieren oder Verschieben von Dateien und Verzeichnissen sind Copy-Item bzw. Move-Item zuständig. Auch wenn die Befehle viele Optionen bieten, ist für grundlegende Features einige zusätzliche Handarbeit nötig.

    Grundsätzlich bestünde in PowerShell die Möglichkeit, wie unter cmd.exe etablierte Dienstprogramme von Windows aufzurufen, beispielsweise xcopy oder robocopy. Allerdings ist ihre Integration in die objektorientierte PowerShell nur sehr oberflächlich. Man kann ihnen Input über eine Pipe nur eingeschränkt übergeben und nach erfolgter Kopieraktion bleibt alleine die Abfrage des Return-Codes, um Hinweise auf den Erfolg der Aktion zu bekommen.

    robocopy oft die erste Wahl

    In vielen Fällen mag eine engere Zusammenarbeit des Kopiervorgangs mit anderen Funktionen von PowerShell aber nicht erforderlich sein, so dass man sich die höhere Leistungsfähigkeit von robocopy zunutze machen kann. Aber sogar im Vergleich zu xcopy ist Copy-Item relativ primitiv.

    Die Cmdlets Copy-Item (Alias copy, cp) und Move-Item (Alias move, mv) verhalten sich weitgehend gleich und nehmen bis auf wenige Ausnahmen die gleichen Parameter. Die folgenden Beispiele beziehen sich daher auf Copy-Item, wobei ich auf einzelne Unterschiede zu Move-Item hinweise.

    Bestimmung von Quelle und Ziel

    Die einfachste Anwendung von Copy-Item spezifiziert nur die Quelldateien und den Zielordner, in den sie kopiert werden sollen. Die Namen dieser Parameter sind -Path und -Destination, sie müssen nicht angegeben werden. Lässt man den Wert für das Ziel weg, dann setzt Copy-Item dafür das aktuelle Verzeichnis ein.

    Wenn das Ziel ein Verzeichnis sein soll und noch nicht existiert, dann legt Copy-Item stattdessen eine Datei mit dem betreffenden Namen an. Dort landen dann aneinander gehängt sämtliche Quelldateien. Nachdem man ein solches Resultat selten gebrauchen kann, muss man sicherstellen, dass das Zielverzeichnis existiert, bevor man den Befehl startet.

    Filter für Quelldateien

    Wenn man möchte, kann man die Liste der Quelldateien mit Hilfe der Parameter -filter, -include bzw. -exclude noch genauer eingrenzen. So würde die Kombination -filter *.j* -exclude *.jpg alle Dateien auswählen, deren Endung mit "j" beginnt, aber JPG-Grafiken dabei ausschließen.

    Copy-Item *.ini ./config_bak

    Führt man den obigen Befehl aus, um alle *.ini-Dateien in das Unterverzeichnis config_bak zu schreiben, dann fallen im Vergleich zu den herkömmlichen Kopierbefehlen zwei Dinge auf: Das Cmdlet gibt nichts aus, so dass man über die Operation keine Informationen erhält, und es fragt nicht nach, ob gleichnamige Dateien im Zielordner überschrieben werden sollen.

    Infos über die Kopieraktion anzeigen

    Das Schweigen von Copy-Item kann man relativ leicht brechen, indem man mit Hilfe des Parameters -passthru dafür sorgt, dass es die Informationen über die betroffenen Dateiobjekte in die Pipeline schreibt. Von dort kann man alle erdenklichen Angaben auslesen:

    Copy *.ini ./config_bak -passthru | foreach{$_.FullName; $len += ($_.length/1KB);}; Write-Host $len "Bytes kopiert"; $len=0;

    In diesem Beispiel werden die Namen aller kopierten Dateien inklusive Pfad über eine Schleife ausgegeben. Außerdem summiert der Befehl die Größe der Dateien in KB in der Variable $len auf und zeigt das Ergebnis am Ende der Dateiliste an. Zum Schluss setzt die Anweisung $len auf 0 zurück, um bei einer weiteren Ausführung des Befehls die Verfälschung des Ergebnisses zu verhindern.

    Überschreiben von Dateien vermeiden

    Schwieriger ist es, Copy-Item abzugewöhnen, gleichnamige Dateien im Zielverzeichnis zu überschreiben. Der Parameter -confirm ist dabei keine Hilfe, weil sich Copy-Item damit einfach das Kopieren jeder Datei bestätigen lässt, unabhängig davon, ob ein Konflikt vorliegt. Und selbst wenn eine Datei gleichen Namens existiert, erhält man auf diese Weise keine Kenntnis davon.

    In diesem Fall verhält sich Move-Item abweichend von Copy-Item und verweigert das Verschieben eines Verzeichnisses oder einer Datei, wenn diese am Ziel bereits existieren.

    Abhilfe schafft hier die zusätzliche Verwendung von Test-Path, das die Existenz eines Objekts im Dateisystem überprüft. Der Aufruf gestaltet jedoch recht kompliziert angesichts des relativ einfachen Anliegens:

    gci *.ini | %{if(-not(Test-Path (".\config_bak\" + $_.name))){copy $_ .\config_bak -passthru|select name}}

    Hier muss man die Quelldateien mit Hilfe von Get-ChildItem erfassen und diese Liste einer Schleife übergeben, in der Test-Path jede einzelne Datei auf ihre Existenz prüft. Nur wenn sie nicht vorhanden ist, kommt der Kopierbefehl zum Zug, und zwar für jede Datei separat.

    Wenn man Copy-Item regelmäßig interaktiv einsetzen und keine Dateien ungewollt überschreiben möchte, dann ist eine solche Befehlssequenz zu aufwändig. In diesem Fall müsste man eine function schreiben, die den Prüfvorgang mit Test-Path enthält.

    Rekursives Kopieren und Verschieben

    Eine gängige Anforderung an einen Kopierbefehl besteht darin, dass er Dateien aus ganzen Verzeichnisbäumen an das gewünschte Ziel schreiben kann. Copy-Item hat zu diesem Zweck den Parameter -recurse, den man mit -r abkürzen kann:

    copy -r scripts\* $env:temp

    Dieser Befehl kopiert den Inhalt von scripts (Dateien und Unterverzeichnisse) in das Verzeichnis, auf das die Umgebungsvariable %temp% zeigt. Möchte man, dass scripts unterhalb des Zielverzeichnisses angelegt wird, dann muss man den Aufruf so anpassen:

    copy -r scripts $env:temp

    Bei Move-Item kann man sich die explizite Angabe eines Parameters für das rekursive Verschieben von Verzeichnissen sparen. Dieses Cmdlet bewegt immer den ganzen Teilbaum unterhalb des Quellverzeichnisses.

    4 Kommentare

    Bild von #YOLOSWAG!
    #YOLOSWAG! sagt:
    7. Januar 2014 - 11:19

    Danke! Sehr gute Erklärung.

    Bild von Mike Haaser
    Mike Haaser sagt:
    21. Oktober 2016 - 9:54

    Wollte fragen gibt es auch eine Möglichkeit, Dateien zu überschreiben, indem man Move-item benutzt.Ich habe das Problem, dass ich eine gleichnamige Datei überschreiben will und möchte aber nicht Copy-item benutzen, da ich die Datei nicht kopieren will sondern nur verschieben möchte.

    Bild von Wolfgang Sommergut
    21. Oktober 2016 - 12:36

    Diesem Zweck dient der Schalter -Force