Список исключений в PowerShell Copy-Item не работает - PullRequest
60 голосов
/ 09 апреля 2009

У меня есть следующий фрагмент скрипта PowerShell:

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

Работает для копирования всех файлов и папок из t1 в t2, но исключает только список исключений в папке «root» / «first-level», а не в подпапках.

Как сделать так, чтобы исключить список исключений во всех папках?

Ответы [ 9 ]

86 голосов
/ 09 апреля 2009

Я думаю, что лучший способ - это использовать Get-ChildItem и pipe в команде Copy-Item.

Я обнаружил, что это работает:

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

По сути, здесь происходит то, что вы просматриваете действительные файлы один за другим, а затем копируете их по новому пути. Инструкция «Join-Path» в конце такова, что каталоги также сохраняются при копировании файлов. Эта часть берет каталог назначения и соединяет его с каталогом после исходного пути.

Я получил идею от здесь , а затем немного изменил ее, чтобы она работала в этом примере.

Надеюсь, это сработает!

7 голосов
/ 01 августа 2016

У меня тоже была эта проблема, и я потратил 20 минут на применение решений здесь, но проблемы продолжались.
Поэтому я решил использовать robocopy - ОК, это не powershell, но он должен быть доступен везде, где работает powershell.

И это сработало прямо из коробки:

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

, например

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

Плюс, у этого есть тонна особенностей, как возобновляемая копия. Документы здесь .

5 голосов
/ 10 января 2013

Поскольку код комментариев плохо, я буду публиковать как ответ, но это всего лишь дополнение к ответу @ landyman. Предложенный скрипт имеет недостаток - он будет создавать двойные вложенные папки. Например, для 'd: \ t1 \ sub1' будет создан пустой каталог 'd: \ t2 \ sub1 \ sub1'. Это связано с тем, что Copy-Item для каталогов ожидает имя родительского каталога в свойстве -Destination, а не само имя каталога. Вот обходной путь, который я нашел:

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 голосов
/ 11 ноября 2011

Параметр exclude не будет работать с каталогами. Вариант сценария Бо делает свое дело:

$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)}
3 голосов
/ 23 марта 2018

Обратите внимание, что в спецификации синтаксиса требуется 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>]

Если вы не явно указали в генерации своего массива, вы получите Object [] - и это во многих случаях игнорируется, оставляя видимость «глючного поведения» из-за безопасности типов. Поскольку PowerShell может обрабатывать блоки сценариев, оценка переменной, не зависящей от типа (чтобы можно было определить допустимую строку), оставила бы возможность для атаки в режиме внедрения на любую систему, политика выполнения которой была слабой.

Так что это ненадежно:

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

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

И это работает .... например:

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

Обратите внимание, что даже для "одного" элемента все равно потребуется такое же приведение, чтобы создать массив из 1 элемента.

Если вы пытаетесь сделать это дома, обязательно используйте «пропуски» Переменной-переменной, чтобы очистить существование $ упущений, прежде чем переписать его в приведенных выше примерах.

А что касается проверенного надежного конвейера, который я тестировал ...

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

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

Выше приведен список каталогов исходных файлов в базовом (выбранном «текущем») каталоге, отфильтровывает потенциальные проблемные элементы, преобразует файл в базовое имя и вынуждает cp (псевдоним элемента копирования) повторно обращаться к файл «по имени» в «текущем каталоге» - таким образом, повторно запрашивает объект файла и копирует его. Это создаст пустые каталоги, в том числе те, которые могут даже содержать исключенные файлы (за исключением, конечно, исключений). Также обратите внимание, что "ls" (get-childitem) не -recurse - это оставлено для cp. Наконец, если у вас возникли проблемы и вам необходимо выполнить отладку, удалите ключ-аргумент -ErrorAction без вывода сообщений, который скрывает множество неприятностей, которые в противном случае могут прервать выполнение сценария.

Для тех, чьи комментарии были связаны с "\" включениями, имейте в виду, что вы работаете над подуровнем .NET через интерпретатор (например, PowerShell), а в c #, например, включение одного " \ "(или несколько одиночных строк в строке) приводит к тому, что компилятор требует, чтобы вы исправили условие, используя либо" \\ "для экранирования обратной косой черты, либо перед строкой @, как в @" \ "; с другим оставшимся параметром, заключающим строку в одинарные кавычки, как '\'. Все это из-за ASCII-интерполяции комбинаций символов, таких как \ n и т. Д.

Последний вопрос гораздо важнее, поэтому я оставлю вас с этим соображением.

1 голос
/ 13 июня 2016
$sourcePath="I:\MSSQL\Backup\Full"
$excludedFiles=@("MASTER", "DBA", "MODEL", "MSDB")
$sourceFiles=(ls $sourcePath -recurse -file) | where-object { $_.directory.name -notin $excludedFiles }

это то, что я сделал, мне нужно было скопировать кучу файлов резервных копий в отдельное место в сети для получения клиента. мы не хотели, чтобы у них были вышеупомянутые резервные копии системной БД.

1 голос
/ 22 мая 2014

Get-ChildItem с Join-Path работал в основном для меня, но я понял, что он копирует корневые каталоги в другие корневые каталоги, что было плохо.

Например

  • C: \ SomeFolder
  • C: \ SomeFolder \ CopyInHere
  • C: \ SomeFolder \ CopyInHere \ Thing.txt
  • C: \ SomeFolder \ CopyInHere \ подпапка
  • C: \ SomeFolder \ CopyInHere \ подпапка \ Thin2.txt

  • Исходный каталог: c: \ SomeFolder \ CopyInHere

  • Каталог назначения: d: \ PutItInHere

Цель: Скопируйте каждый дочерний элемент в c: \ SomeFolder \ CopyInHere в корень d: \ PutItInHere, но не включая сам c: \ SomeFolder \ CopyInHere.
- Например, Возьми всех детей CopyInHere и сделай их детьми PutItInHere

Приведенные выше примеры делают это в большинстве случаев, но в результате создается папка с именем SubFolder и папка в папке с именем SubFolder.

Это потому, что Join-Path вычисляет целевой путь d: \ PutItInHere \ SubFolder для дочернего элемента SubFolder, поэтому метод GetFolder создается в папке с именем SubFolder.

Я справился с этим, используя Get-ChildItems, чтобы вернуть коллекцию элементов, а затем используя цикл для его просмотра.

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
}

Короче говоря, он использует полное имя текущего элемента для расчета пункта назначения. Однако затем он проверяет, является ли это объектом DirectoryInfo. Если он проверяет, является ли родительская папка исходным каталогом, это означает, что текущая папка, которая повторяется, является прямым потомком исходного каталога, поэтому мы не должны добавлять ее имя в целевой каталог, потому что мы хотим, чтобы эта папка была созданный в каталоге назначения, а не в папке его в каталоге назначения.

После этого все остальные папки будут работать нормально.

1 голос
/ 25 августа 2011

Я искал способ скопировать файлы, измененные после определенной даты / метки времени, чтобы заархивировать их. Таким образом, я мог сохранить только те файлы, над которыми я работал (при условии, что я знаю, когда я начал). (Да, я знаю, что для этого и нужен SCM, но бывают случаи, когда я просто хочу сделать снимок своей работы, не регистрируя ее.)

Используя подсказку лендимана и вещи, которые я нашел в другом месте, я обнаружил, что это работает:

$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)}
0 голосов
/ 24 июня 2012

У меня была похожая проблема, немного расширяющая это. Я хочу, чтобы решение работало для таких источников, как

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

тоже. Я нашел это решение:

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
    # /578202/spisok-isklychenii-v-powershell-copy-item-ne-rabotaet

    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
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...