PowerShell Copy-Item中的排除列表似乎不起作用


70

我有以下PowerShell脚本片段:

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

它可以将所有文件和文件夹从t1复制到t2,但仅排除“ root” /“ first-level”文件夹中的排除列表,而不是子文件夹中的排除列表。

如何使其排除所有文件夹中的排除列表?

Answers:


99

我认为最好的方法是在Copy-Item命令中使用Get-ChildItem和管道。

我发现这可行:

$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”语句是为了在复制文件时也保留目录。该部分采用目标目录,并将其与源路径之后的目录连接。

我从这里得到的想法,然后对其进行了一些修改以使其适用于本示例。

我希望它能起作用!


1
那只是我要发布的解决方案。:)我发现-Include和-Exclude不适用于Select-String,Copy-Item和其他一些命令。它确实适用于Get-ChildItem(dir)。
JasonMArcher

1
注意,如果$ source是路径对象,则应编写“ $ source.path.length”以获取长度。
AZ。

1
此解决方案取决于使用没有通配符且没有斜杠的路径。我认为唯一的解决方案是将Copy-Item的行为声明为Microsoft connect上的错误。问题在于,不带-name的gci不能告诉相对路径,而带-name的gci可以得到相对路径,但是您不知道相对于哪个源目录。
bernd_k 2012年

1
这仍然是复制项目的问题-排除属性吗?我的意思是上述答案有效,但现在是2016年。微软是否已在复制项目中修复或添加了任何新功能?
LP13

1
根据MSFT在2016年3月16日发布的消息,此问题已修复,将在PowerShell的下一个公共发行版中发布。windowsserver.uservoice.com/forums/301869-powershell/...
Signal15

12

我也遇到了这个问题,花了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

此外,它还具有大量功能,例如可恢复副本。文档在这里


1
robocopy也是我的最爱,但请注意您的脚本是否依赖退出代码。参见blogs.technet.microsoft.com/deploymentguys/2008/06/16/…–
SBF

6

由于注释的格式很糟糕,因此我将其张贴为答案,但这只是@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)
  }
}

1
谢谢!我恰好遇到了这个问题。
ferventcoder

5

exclude参数不适用于dirs。Bo脚本的变体可以达到目的:

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

1
您的“ where”子句实际上将在正则表达式“ .bak”(即“ [any character] bak”)上“不匹配”。您的正则表达式必须为'\ .bak',如果您想直接进行字符串匹配,则应使用'-notlike'。
瑞安·费舍尔

5

请注意,语法规范要求使用“字符串数组”;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元素数组。

如果您在家中尝试此操作,请确保使用Replace-Variable“遗漏”清除$ omissions的存在,然后在上述示例中重铸它。

至于我测试过的可靠运行的管道...

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

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

上面的操作在基本(选定的“当前”)目录中列出了源文件的目录,过滤出潜在的问题项,将文件转换为基本名称,并强制cp(复制项别名)重新访问文件“当前目录”中的“名称”-从而重新获取文件对象,然后将其复制。这将创建空目录,包括甚至可能包含排除文件的目录(当然要包括排除项)。还请注意,“ ls”(get-childitem)不会-递归-留给cp。最后-如果遇到问题并需要调试,请删除-ErrorAction静默继续开关和参数,这将隐藏许多可能会干扰脚本的麻烦。

对于那些注释与“ \”包含项有关的注释,请记住,您正在通过解释器(即PowerShell)在.NET子层上工作,例如在c#中,包含单个“ \”(或字符串中的多个单打),导致编译器要求您使用“ \\”来转义反斜杠,或者在字符串前加上@,例如@“ \”来更正条件;剩下的另一个选项是用单引号将字符串括起来,如'\'。所有这些都是由于字符组合(例如“ \ n”等)的ASCII插值。

后者是一个更大的主题,因此我将考虑这一点。


2

我正在寻找一种方法来复制在特定日期/时间戳后修改的文件,以便将其存档。这样,我就可以准确地保存工作的文件(假设我知道什么时候开始)。(是的,我知道这就是SCM的用途,但是有时候我只是想快照我的工作而无需检入。)

使用landyman的技巧,以及在其他地方找到的东西,我发现这可行:

$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

带有Join-Path的Get-ChildItem最适合我,但是我意识到它正在将根目录复制到其他根目录中,这很糟糕。

例如

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

  • 源目录:c:\ SomeFolder \ CopyInHere

  • 目标目录:d:\ PutItInHere

目标:将c:\ SomeFolder \ CopyInHere中的每个子项复制到d:\ PutItInHere的根目录,但不包括c:\ SomeFolder \ CopyInHere本身。
-例如,将CopyInHere的所有子级都作为它们的PutItInHere的子级

上面的示例在大多数情况下都执行此操作,但是会发生什么情况:它创建一个名为SubFolder的文件夹,并在名为SubFolder的文件夹中创建一个文件夹。

这是因为Join-Path为SubFolder子项计算了d:\ PutItInHere \ SubFolder的目标路径,因此在名为SubFolder的文件夹中创建了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
$sourcePath="I:\MSSQL\Backup\Full"
$excludedFiles=@("MASTER", "DBA", "MODEL", "MSDB")
$sourceFiles=(ls $sourcePath -recurse -file) | where-object { $_.directory.name -notin $excludedFiles }

这就是我所做的,我需要将一堆备份文件复制到网络上的另一个位置以进行客户端提取。我们不希望他们拥有上述系统数据库备份。


0

我有一个类似的问题,扩大这一点。我想要适用于以下来源的解决方案

$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
    # 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

以下代码段将所有文件和文件夹拷贝$source$dest,排除.pdb.config从根文件夹和子文件夹的文件:

Get-ChildItem -Path $source | Copy-Item -Destination $dest -Recurse -Container -Exclude @('*.pdb','*.config')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.