Die Ausschlussliste in PowerShell Copy-Item scheint nicht zu funktionieren

70

Ich habe den folgenden Ausschnitt aus dem PowerShell-Skript:

$source = 'd:\t1\*'
$dest = 'd:\t2'
$exclude = @('*.pdb','*.config')
Copy-Item $source $dest -Recurse -Force -Exclude $exclude

Dies funktioniert, um alle Dateien und Ordner von t1 nach t2 zu kopieren, schließt jedoch nur die Ausschlussliste im Ordner "root" / "first-level" und nicht in Unterordnern aus.

Wie kann ich die Ausschlussliste in allen Ordnern ausschließen?

Kerl
quelle

Antworten:

99

Ich denke, der beste Weg ist, Get-ChildItem und Pipe im Befehl Copy-Item zu verwenden.

Ich fand, dass dies funktionierte:

$source = 'd:\t1'
$dest = 'd:\t2'
$exclude = @('*.pdb','*.config')
Get-ChildItem $source -Recurse -Exclude $exclude | Copy-Item -Destination {Join-Path $dest $_.FullName.Substring($source.length)}

Grundsätzlich passiert hier, dass Sie die gültigen Dateien einzeln durchgehen und sie dann in den neuen Pfad kopieren. Die Anweisung 'Join-Path' am Ende lautet, dass die Verzeichnisse auch beim Kopieren über die Dateien beibehalten werden. Dieser Teil nimmt das Zielverzeichnis und verbindet es mit dem Verzeichnis nach dem Quellpfad.

Ich habe die Idee von hier bekommen und sie dann ein wenig modifiziert, damit sie für dieses Beispiel funktioniert.

Ich hoffe es klappt!

Landmann
quelle
1
Das war genau die Lösung, die ich veröffentlichen wollte. :) Ich habe festgestellt, dass -Include und -Exclude für Select-String, Copy-Item und einige andere Befehle nicht richtig funktionieren. Es funktioniert richtig für Get-ChildItem (dir).
JasonMArcher
1
Beachten Sie, wenn die $ source ein Pfadobjekt ist, sollten Sie "$ source.path.length" schreiben, um die Länge zu erhalten.
AZ.
1
Diese Lösung hängt von der Verwendung eines Pfads ohne Platzhalter und ohne Schrägstrich ab. Ich denke, die einzige Lösung besteht darin, das Verhalten von Copy-Item als Fehler in Microsoft Connect zu deklarieren. Das Problem ist, dass gci ohne -name den relativen Pfad nicht angibt und mit -name den relativen Pfad erhält, aber Sie wissen nicht, in welchem ​​Quellverzeichnis.
Bernd_k
1
Ist dies immer noch ein Problem mit dem Attribut copy-item - exclude? Ich meine, die obige Antwort funktioniert, aber es ist jetzt 2016. Hat Microsoft neue Funktionen in copy-item behoben oder hinzugefügt?
LP13
1
Laut MSFT am 16. März 2016 wurde dies behoben und wird in der nächsten öffentlichen Version von PowerShell veröffentlicht. windowsserver.uservoice.com/forums/301869-powershell/…
Signal15
12

Ich hatte auch dieses Problem und verbrachte 20 Minuten damit, die Lösungen hier anzuwenden, hatte aber weiterhin Probleme.
Also habe ich mich für Robocopy entschieden - OK, es ist keine Powershell, sollte aber überall dort verfügbar sein, wo Powershell läuft.

Und es hat sofort funktioniert:

robocopy $source $dest /S /XF <file patterns to exclude> /XD <directory patterns to exclude>

z.B

robocopy $source $dest /S /XF *.csproj /XD obj Properties Controllers Models

Außerdem verfügt es über unzählige Funktionen, wie z. B. eine wiederaufnehmbare Kopie. Docs hier .

Weißer Falke
quelle
1
robocopyist auch mein Favorit, aber pass auf, ob dein Skript auf dem Exit-Code basiert. Siehe blogs.technet.microsoft.com/deploymentguys/2008/06/16/…
SBF
6

Als Kommentar formatieren Code schlecht, ich werde als Antwort posten, aber es ist nur eine Ergänzung zu @ landymans Antwort. Das vorgeschlagene Skript hat einen Nachteil: Es werden doppelt verschachtelte Ordner erstellt. Zum Beispiel wird für 'd: \ t1 \ sub1' ein leeres Verzeichnis 'd: \ t2 \ sub1 \ sub1' erstellt. Dies liegt an der Tatsache, dass Copy-Item für Verzeichnisse den Namen des übergeordneten Verzeichnisses in der Eigenschaft -Destination und nicht den Verzeichnisnamen selbst erwartet. Hier ist eine Problemumgehung, die ich gefunden habe:

Get-ChildItem -Path $from -Recurse -Exclude $exclude | Copy-Item -Force -Destination {
  if ($_.GetType() -eq [System.IO.FileInfo]) {
    Join-Path $to $_.FullName.Substring($from.length)
  } else {
    Join-Path $to $_.Parent.FullName.Substring($from.length)
  }
}
Würger
quelle
1
Vielen Dank! Ich bin genau auf dieses Problem gestoßen.
Ferventcoder
5

Der Parameter exclude funktioniert nicht mit Verzeichnissen. Eine Variante von Bo's Skript macht den Trick:

$source = 'c:\tmp\foo'
$dest = 'c:\temp\foo'
$exclude = '\.bak'
Get-ChildItem $source -Recurse  | where {$_.FullName -notmatch $exclude} | 
    Copy-Item -Destination {Join-Path $dest $_.FullName.Substring($source.length)}
Dan Gøran Lunde
quelle
1
Ihre 'where'-Klausel wird im regulären Ausdruck ".bak", dh "[beliebiges Zeichen] bak", tatsächlich "nicht übereinstimmen". Ihre Regex muss '\ .bak' sein, oder Sie sollten '-notlike' verwenden, wenn Sie einen geraden String-Abgleich wünschen.
Ryan Fisher
4

Beachten Sie, dass die Syntaxspezifikation einen STRING ARRAY erfordert. ala String []

SYNTAX
    Copy-Item [[-Destination] <String>] [-Confirm] [-Container] [-Credential <PSCredential>] [-Exclude <String[]>] [-Filter <String>] [-Force] [-FromSession <PSSession>] [-Include 
    <String[]>] -LiteralPath <String[]> [-PassThru] [-Recurse] [-ToSession <PSSession>] [-UseTransaction] [-WhatIf] [<CommonParameters>]

Wenn Sie in Ihrer Array-Generierung nicht explizit sind, erhalten Sie ein Objekt [] - und das wird in vielen Fällen ignoriert, sodass aufgrund der Typensicherheit das Erscheinungsbild eines "fehlerhaften Verhaltens" auftritt. Da PowerShell Skriptblöcke verarbeiten kann, würde die Auswertung einer anderen als einer typspezifischen Variablen (damit eine gültige Zeichenfolge ermittelt werden kann) eine Öffnung für das Potenzial eines Angriffs im Injektionsmodus auf jedes System hinterlassen, dessen Ausführungsrichtlinie lax war.

Das ist also unzuverlässig:

PS > $omissions = @("*.iso","*.pdf","*.zip","*.msi")
PS > $omissions.GetType()

Note the result....
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

Und das funktioniert ... zum Beispiel:

PS > $omissions = [string[]]@("*.iso","*.pdf","*.zip","*.msi")
**or**
PS > [string[]]$omissions = ("*.iso,*.pdf,*.zip,*.msi").split(',')
PS > $omissions.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String[]                                 System.Array

Beachten Sie, dass selbst ein "einzelnes" Element immer noch dieselbe Umwandlung erfordert, um ein Array mit 1 Element zu erstellen.

Wenn Sie dies zu Hause versuchen, stellen Sie sicher, dass Sie die "Auslassungen" der Ersetzungsvariablen verwenden, um das Vorhandensein von $ Auslassungen zu beseitigen, bevor Sie sie in den oben gezeigten Beispielen neu zusammenstellen.

Und was eine Pipeline betrifft, die zuverlässig funktioniert, die ich getestet habe ...

--------------------------------------------------------------------------------------- cd $sourcelocation

ls | ?{$_ -ne $null} | ?{$_.BaseName -notmatch "^\.$"} | %{$_.Name} | cp -Destination $targetDir -Exclude $omissions -recurse -ErrorAction silentlycontinue 
---------------------------------------------------------------------------------------

Oben wird eine Verzeichnisliste der Quelldateien im Basisverzeichnis (ausgewählt "aktuell") erstellt, potenzielle Problemelemente herausgefiltert, die Datei in den Basisnamen konvertiert und cp (Alias ​​für Kopierelemente) gezwungen, erneut auf die Datei zuzugreifen name "im" aktuellen Verzeichnis "- so wird das Dateiobjekt erneut abgerufen und kopiert. Dadurch werden leere Verzeichnisse erstellt, einschließlich solcher, die möglicherweise sogar ausgeschlossene Dateien enthalten (natürlich abzüglich der Ausschlüsse). Beachten Sie auch, dass "ls" (get-childitem) NICHT -recurse ist - das bleibt cp überlassen. Schließlich - wenn Sie Probleme haben und debuggen müssen, entfernen Sie den Schalter -ErrorAction Silentcontinue und das Argument, das viele Belästigungen verbirgt, die das Skript andernfalls unterbrechen könnten.

Beachten Sie für diejenigen, deren Kommentare sich auf "\" -Einschlüsse beziehen, dass Sie über die .NET-Unterschicht über einen Interpreter (z. B. PowerShell) arbeiten und in c # beispielsweise ein einzelnes "\" ( oder mehrere Singles in einer Zeichenfolge), führt der Compiler dazu, dass Sie die Bedingung korrigieren müssen, indem Sie entweder "\\" verwenden, um dem Backslash zu entkommen, oder der Zeichenfolge ein @ wie in @ "\" voranstellen. Die andere verbleibende Option ist das Einschließen der Zeichenfolge in einfache Anführungszeichen als '\'. All dies ist auf die ASCII-Interpolation von Zeichenkombinationen wie "\ n" usw. zurückzuführen.

Letzteres ist ein viel größeres Thema, daher überlasse ich Ihnen diese Überlegung.

Rob R.
quelle
2

Ich suchte nach einer Möglichkeit, Dateien zu kopieren, die nach einem bestimmten Datum / Zeitstempel geändert wurden, um sie zu archivieren. Auf diese Weise konnte ich genau speichern, an welchen Dateien ich gearbeitet habe (vorausgesetzt, ich weiß, wann ich angefangen habe). (Ja, ich weiß, dafür ist SCM gedacht, aber manchmal möchte ich nur einen Schnappschuss meiner Arbeit machen, ohne sie einzuchecken.)

Mit Landyman's Tipp und Sachen, die ich woanders gefunden habe, stellte ich fest, dass dies funktionierte:

$source = 'c:\tmp\foo'
$dest = 'c:\temp\foo'
$exclude = @('*.pdb', '*.config')
Get-ChildItem $source -Recurse -Exclude $exclude |  
    where-object {$_.lastwritetime -gt "8/24/2011 10:26 pm"} | 
    Copy-Item -Destination {Join-Path $dest $_.FullName.Substring($source.length)}
Robert Cohn
quelle
1

Get-ChildItem mit Join-Path funktionierte hauptsächlich für mich, aber mir wurde klar, dass Root-Verzeichnisse in die anderen Root-Verzeichnisse kopiert wurden, was schlecht war.

Zum Beispiel

  • c: \ SomeFolder
  • c: \ SomeFolder \ CopyInHere
  • c: \ SomeFolder \ CopyInHere \ Thing.txt
  • c: \ SomeFolder \ CopyInHere \ SubFolder
  • c: \ SomeFolder \ CopyInHere \ SubFolder \ Thin2.txt

  • Quellverzeichnis: c: \ SomeFolder \ CopyInHere

  • Zielverzeichnis: d: \ PutItInHere

Ziel: Kopieren Sie jedes untergeordnete Element in c: \ SomeFolder \ CopyInHere in das Stammverzeichnis von d: \ PutItInHere, jedoch ohne c: \ SomeFolder \ CopyInHere selbst.
- Nehmen Sie zB alle Kinder von CopyInHere und machen Sie sie zu Kindern von PutItInHere

Die obigen Beispiele tun dies meistens, aber es wird ein Ordner namens SubFolder erstellt und ein Ordner im Ordner SubFolder erstellt.

Dies liegt daran, dass Join-Path einen Zielpfad von d: \ PutItInHere \ SubFolder für das untergeordnete Element SubFolder berechnet, sodass SubFolder in einem Ordner namens SubFolder erstellt wird.

Ich habe dies umgangen, indem ich Get-ChildItems verwendet habe, um eine Sammlung der Elemente zurückzubringen, und dann eine Schleife verwendet habe, um sie zu durchlaufen.

Param(
[Parameter(Mandatory=$True,Position=1)][string]$sourceDirectory,
[Parameter(Mandatory=$True,Position=2)][string]$destinationDirectory
)
$sourceDI = [System.IO.DirectoryInfo]$sourceDirectory
$destinationDI = [System.IO.DirectoryInfo]$destinationDirectory
$itemsToCopy = Get-ChildItem $sourceDirectory -Recurse -Exclude @('*.cs', 'Views\Mimicry\*')
foreach ($item in $itemsToCopy){        
    $subPath = $item.FullName.Substring($sourceDI.FullName.Length)
$destination = Join-Path $destinationDirectory $subPath
if ($item -is [System.IO.DirectoryInfo]){
    $itemDI = [System.IO.DirectoryInfo]$item
    if ($itemDI.Parent.FullName.TrimEnd("\") -eq $sourceDI.FullName.TrimEnd("\")){      
        $destination = $destinationDI.FullName  
    }
}
$itemOutput = New-Object PSObject 
$itemOutput | Add-Member -Type NoteProperty -Name Source -Value $item.FullName
$itemOutput | Add-Member -Type NoteProperty -Name Destination -Value $destination
$itemOutput | Format-List
Copy-Item -Path $item.FullName -Destination $destination -Force
}

Kurz gesagt, es wird der vollständige Name des aktuellen Elements für die Zielberechnung verwendet. Anschließend wird jedoch überprüft, ob es sich um ein DirectoryInfo-Objekt handelt. Wenn dies der Fall ist, wird überprüft, ob der übergeordnete Ordner das Quellverzeichnis ist. Dies bedeutet, dass der aktuell iterierte Ordner ein direktes untergeordnetes Element des Quellverzeichnisses ist. Daher sollten wir seinen Namen nicht an das Zielverzeichnis anhängen, da dieser Ordner sein soll Erstellt im Zielverzeichnis, nicht in einem Ordner, der sich im Zielverzeichnis befindet.

Danach funktioniert jeder andere Ordner einwandfrei.

Ryan Mann
quelle
1
$sourcePath="I:\MSSQL\Backup\Full"
$excludedFiles=@("MASTER", "DBA", "MODEL", "MSDB")
$sourceFiles=(ls $sourcePath -recurse -file) | where-object { $_.directory.name -notin $excludedFiles }

Dies ist, was ich getan habe, ich musste eine Reihe von Sicherungsdateien an einen separaten Ort im Netzwerk für die Client-Abholung kopieren. Wir wollten nicht, dass sie die oben genannten System-DB-Backups haben.

mrkurtz
quelle
0

Ich hatte ein ähnliches Problem, das ein bisschen erweitert wurde. Ich möchte eine Lösung für Quellen wie

$source = "D:\scripts\*.sql"

auch. Ich habe diese Lösung gefunden:

function Copy-ToCreateFolder
{
    param(
        [string]$src,
        [string]$dest,
        $exclude,
        [switch]$Recurse
    )

    # The problem with Copy-Item -Rec -Exclude is that -exclude effects only top-level files
    # Copy-Item $src $dest    -Exclude $exclude       -EA silentlycontinue -Recurse:$recurse
    # http://stackoverflow.com/questions/731752/exclude-list-in-powershell-copy-item-does-not-appear-to-be-working

    if (Test-Path($src))
    {
        # Nonstandard: I create destination directories on the fly
        [void](New-Item $dest -itemtype directory -EA silentlycontinue )
        Get-ChildItem -Path $src -Force -exclude $exclude | % {

            if ($_.psIsContainer)
            {
                if ($Recurse) # Non-standard: I don't want to copy empty directories
                {
                    $sub = $_
                    $p = Split-path $sub
                    $currentfolder = Split-Path $sub -leaf
                    #Get-ChildItem $_ -rec -name  -exclude $exclude -Force | % {  "{0}    {1}" -f $p, "$currentfolder\$_" }
                    [void](New-item $dest\$currentfolder -type directory -ea silentlycontinue)
                    Get-ChildItem $_ -Recurse:$Recurse -name  -exclude $exclude -Force | % {  Copy-item $sub\$_ $dest\$currentfolder\$_ }
                }
            }
            else
            {

                #"{0}    {1}" -f (split-path $_.fullname), (split-path $_.fullname -leaf)
                Copy-Item $_ $dest
            }
        }
    }
}
bernd_k
quelle
Es gibt Probleme damit, bei denen das letzte Kopierelement nicht funktioniert, wenn Sie sich in einem verschachtelten Ordner befinden.
Blake Niemyjski
0

Das folgende Snippet kopiert alle Dateien und Ordner von $sourcenach $dest, mit Ausnahme von .pdbund .configDateien aus dem Stammordner und den Unterordnern:

Get-ChildItem -Path $source | Copy-Item -Destination $dest -Recurse -Container -Exclude @('*.pdb','*.config')
ElasticCode
quelle