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

时间:2009-04-08 20:46:24

标签: powershell

我有以下PowerShell脚本片段:

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

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

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

10 个答案:

答案 0 :(得分:86)

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

我发现这很有效:

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

我从here得到了这个想法,然后对它进行了一些修改以使其适用于此示例。

我希望它有效!

答案 1 :(得分:7)

我也有这个问题,花了20分钟在这里应用解决方案,但一直有问题。
所以我选择使用robocopy - 好吧,它不是powershell,但是应该可用powershell运行的每个地方。

它开箱即用:

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

e.g。

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

另外,它有很多功能,比如可恢复的副本。 文档here

答案 2 :(得分: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)}

答案 3 :(得分:5)

由于评论格式代码严重,我会发布答案,但这只是@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)
  }
}

答案 4 :(得分:3)

请注意语法规范要求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 [] - 在很多情况下会被忽略,因为类型安全而会出现“buggy behavior”。由于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(copy-item别名)重新访问在“当前目录”中输入“按名称”文件 - 从而重新获取文件对象并复制它。这将创建空目录,包括那些甚至可能包含排除文件的目录(当然少了排除项)。另请注意,“ls”(get-childitem)不会 - 保留 - 留给cp。最后 - 如果您遇到问题并需要调试,请删除-ErrorAction silentntlycontinue开关和参数,否则会隐藏许多可能会中断脚本的麻烦。

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

后者是一个更大的主题,所以我会留下你的考虑。

答案 5 :(得分:1)

我一直在寻找一种方法来复制在特定日期/时间戳之后修改的文件,以便将它们存档。这样我就可以准确地保存我所处理的文件(假设我知道什么时候开始)。 (是的,我知道这就是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)}

答案 6 :(得分:1)

使用Join-Path的Get-ChildItem主要是为我工作,但我意识到它是在其他根目录中复制根目录,这很糟糕。

例如

  • 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为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对象。如果是,它检查它的父文件夹是否是源目录,这意味着正在迭代的当前文件夹是源目录的直接子项,因此我们不应该将它的名称附加到目标目录,因为我们希望该文件夹是在目标目录中创建,而不是在目标目录中的文件夹中。

然后,每个其他文件夹都可以正常工作。

答案 7 :(得分:1)

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

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

答案 8 :(得分: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
            }
        }
    }
}

答案 9 :(得分:0)

以下代码段会将所有文件和文件夹从$source复制到$dest,但根文件夹和子文件夹中的.pdb.config文件除外:

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