web-dev-qa-db-fra.com

La liste d'exclusion dans PowerShell Copy-Item ne semble pas fonctionner

J'ai l'extrait de script PowerShell suivant:

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

Ce qui fonctionne pour copier tous les fichiers et dossiers du t1 au t2, mais il exclut seulement la liste d'exclusion dans le dossier "racine"/"premier niveau" et non dans les sous-dossiers.

Comment faire pour exclure la liste d'exclusion de tous les dossiers?

58
Guy

Je pense que le meilleur moyen consiste à utiliser Get-ChildItem et à canaliser la commande Copy-Item. 

J'ai trouvé que cela fonctionnait:

$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)}

En gros, ce qui se passe ici, c'est que vous parcourez les fichiers valides un par un, puis que vous les copiez dans le nouveau chemin. L'instruction 'Join-Path' à la fin est telle que les répertoires sont également conservés lors de la copie des fichiers. Cette partie prend le répertoire de destination et le joint au répertoire situé après le chemin source.

J'ai eu l'idée de ici , puis je l'ai modifiée un peu pour que cela fonctionne pour cet exemple.

J'espère que ca fonctionne!

84
landyman

J'ai également eu ce problème et j'ai passé 20 minutes à appliquer les solutions ici, mais je continuais à avoir des problèmes.
J'ai donc choisi d'utiliser robocopy - OK, ce n'est pas PowerShell, mais devrait être disponible partout où il fonctionne.

Et cela a fonctionné dès la sortie de la boîte:

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

par exemple.

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

De plus, il a des tonnes de fonctionnalités, telles que la copie résumable . Docs ici .

6
White hawk

Le paramètre exclude ne fonctionnera pas avec dirs. Une variante du script de Bo fait l'affaire:

$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)}
5
danglund

Comme les commentaires formulent mal le code, je posterai comme réponse, mais ce n’est qu’un ajout à la réponse de @ landyman ... Le script proposé présente un inconvénient: il créera des dossiers double imbriqués. Par exemple, pour 'd:\t1\sub1', il créera un répertoire vide 'd:\t2\sub1\sub1'. Cela est dû au fait que Copy-Item pour les répertoires attend le nom du répertoire parent dans la propriété -Destination et non le nom du répertoire lui-même . Voici une solution de contournement que j'ai trouvée:

    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)
        }
       }
5
Shrike

Notez que la syntaxe spécifie un STRING ARRAY; 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>]

Si vous n'êtes pas explicite dans votre génération de tableau, vous vous retrouvez avec un objet [] - ce qui est ignoré dans de nombreux cas, laissant l'apparence d'un "comportement bogué" pour des raisons de sécurité. Etant donné que PowerShell peut traiter des blocs de script, l’évaluation de variables autres que propres à un type (afin qu’une chaîne valide puisse être déterminée) laisserait une ouverture au potentiel d’une attaque en mode injection sur tout système dont la stratégie d’exécution serait laxiste. 

Donc, ce n'est pas fiable:

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

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

Et cela fonctionne .... par exemple:

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

Notez que même un élément "unique" nécessiterait toujours la même conversion, de manière à créer un tableau à 1 élément.

Si vous essayez cela à la maison, veillez à utiliser les "omissions" Remplacer-Variable pour éliminer les omissions $ avant de les reformater dans les exemples ci-dessus.

Et en ce qui concerne un pipeline fiable que j'ai testé ...

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

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

La liste ci-dessus répertorie les fichiers source du répertoire de base (sélectionné "en cours"), filtre les éléments posant problème, convertit le fichier en nom de base et force cp (alias de composant de copie) à accéder de nouveau au fichier "par name "dans le" répertoire courant "- récupère ainsi l’objet fichier et le copie. Cela créera des répertoires vides, y compris ceux pouvant contenir des fichiers exclus (moins les exclusions bien sûr). Notez également que "ls" (get-childitem) NE PAS -recurse - qui est laissé à cp. Enfin, si vous rencontrez des problèmes et que vous avez besoin de déboguer, supprimez le commutateur et l'argument -ErrorAction pour continuer en silence, ce qui masque de nombreuses nuisances qui pourraient autrement interrompre le script.

Pour ceux dont les commentaires étaient liés aux inclusions "\", n'oubliez pas que vous travaillez sur la sous-couche .NET via un interpréteur (c.-à-d. PowerShell), et en c # par exemple, l'inclusion d'un seul "\" ( ou plusieurs singles dans une chaîne), le compilateur vous demande de corriger la condition en utilisant "\\" pour échapper à la barre oblique inversée ou en faisant précéder la chaîne par un @ comme dans ""; l'autre option restante étant la clôture de la chaîne entre guillemets simples, sous la forme '\'. Tout cela est dû à l’interpolation ASCII des combinaisons de caractères comme "\ n" etc. 

Ce dernier est un sujet beaucoup plus vaste, je vous laisse donc avec cette considération.

3
Rob R.

Get-ChildItem with Join-Path fonctionnait principalement pour moi, mais je me suis rendu compte qu'il était en train de copier des répertoires racine dans les autres répertoires racine, ce qui était une mauvaise chose.

Par exemple

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

  • Répertoire source: c:\SomeFolder\CopyInHere

  • Répertoire de destination: d:\PutItInHere

Objectif: Copier tous les enfants à l’intérieur de c:\SomeFolder\CopyInHere à la racine de d:\PutItInHere, mais sans inclure c:\SomeFolder\CopyInHere.
- Par exemple. Prenez tous les enfants de CopyInHere et faites-en enfants de PutItInHere

Les exemples ci-dessus s’appliquent presque entièrement, mais cela se produit. Il crée un dossier appelé SubFolder et crée un dossier dans un dossier appelé SubFolder.

C'est parce que Join-Path calcule un chemin de destination de d:\PutItInHere\SubFolder pour l'élément enfant SubFolder, afin que SubFolder soit créé dans un dossier appelé SubFolder.

J'ai contourné ce problème en utilisant Get-ChildItems pour ramener une collection d'éléments, puis en utilisant une boucle pour le parcourir.

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
}

En bref, le nom complet de l'élément en cours est utilisé pour le calcul de la destination. Cependant, il vérifie ensuite s'il s'agit d'un objet DirectoryInfo. Si c'est le cas, il vérifie que le dossier source est le dossier parent, cela signifie que le dossier en cours d'itération est un enfant direct du répertoire source. Par conséquent, nous ne devrions pas ajouter son nom au répertoire de destination, car nous voulons que ce dossier créé dans le répertoire de destination, pas dans un dossier de celui-ci.

Ensuite, tous les autres dossiers fonctionneront correctement.

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

c’est ce que j’ai fait, j’avais besoin de copier un tas de fichiers de sauvegarde dans un emplacement distinct du réseau pour la collecte des clients. nous ne voulions pas qu'ils aient les sauvegardes de la base de données système ci-dessus.

1
mrkurtz

Je cherchais un moyen de copier des fichiers modifiés après une certaine date/heure afin de les archiver. De cette façon, je pourrais sauvegarder exactement les fichiers sur lesquels j'ai travaillé (en supposant que je sache quand j'ai commencé). (Oui, je sais que c'est à cela que sert SCM, mais il y a des moments où je veux juste prendre un cliché de mon travail sans l'enregistrer.)

En utilisant le conseil de landyman, et d'autres choses que j'ai trouvées ailleurs, j'ai trouvé que cela fonctionnait:

$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)}
1
Robert Cohn

J'ai eu un problème similaire prolongeant un peu cela. Je veux une solution fonctionnant pour des sources telles que

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

aussi. J'ai trouvé cette solution:

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
            }
        }
    }
}
0
bernd_k

L'extrait de la question fonctionne maintenant comme prévu dans PowerShell 5.

0
mnsc