【问题标题】:Exclude list in PowerShell Copy-Item does not appear to be workingPowerShell Copy-Item 中的排除列表似乎不起作用
【发布时间】:2009-04-08 20:46:24
【问题描述】:

我有以下 PowerShell 脚本的 sn-p:

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

这可以将所有文件和文件夹从 t1 复制到 t2,但它只排除“根”/“一级”文件夹中的排除列表,而不排除子文件夹中的排除列表。

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

【问题讨论】:

    标签: powershell


    【解决方案1】:

    我认为最好的方法是在 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)}
    

    基本上,这里发生的事情是您将一个一个地浏览有效文件,然后将它们复制到新路径。最后的“加入路径”语句是为了在复制文件时也保留目录。该部分获取目标目录并将其与源路径之后的目录连接。

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

    我希望它有效!

    【讨论】:

    • 这就是我要发布的解决方案。 :) 我发现 -Include 和 -Exclude 不适用于 Select-String、Copy-Item 和其他一些命令。它确实适用于 Get-ChildItem (dir)。
    • 注意如果$source是一个路径对象,那么你应该写“$source.path.length”来获取长度。
    • 此解决方案依赖于使用不带通配符且不带斜杠的路径。我认为唯一的解决方案是将 Copy-Item 的行为声明为 Microsoft connect 上的错误。问题是不带 -name 的 gci 不会告诉相对路径,带 -name 你会得到相对路径,但你不知道相对于哪个源目录。
    • 这仍然是 copy-item - exclude 属性的问题吗?我的意思是上述答案有效,但现在是 2016 年。微软是否在 copy-item 中修复或添加了任何新功能?
    • 根据 MSFT 2016 年 3 月 16 日的消息,该问题已得到修复,并将在 PowerShell 的下一个公开版本中提供; windowsserver.uservoice.com/forums/301869-powershell/…
    【解决方案2】:

    由于 cmets 格式代码错误,我将作为答案发布,但这只是@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)
      }
    }
    

    【讨论】:

    • 谢谢!我正好遇到了这个问题。
    【解决方案3】:

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

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

    【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      请注意,语法规范要求使用 STRING ARRAY; ala 字符串[]

      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” 清除 $omissions 的存在,然后再在上面显示的示例中重铸它。

      就我测试过的可靠工作的管道而言......

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

      上面做了一个基本(选择“当前”)目录中源文件的目录列表,过滤掉潜在的问题项目,将文件转换为基本名称并强制cp(复制项目别名)重新访问“当前目录”中的“按名称”文件 - 从而重新获取文件对象并复制它。这将创建空目录,包括那些甚至可能包含排除文件的目录(当然少了排除)。另请注意,“ls”(get-childitem)不会 -recurse - 留给 cp。最后 - 如果您遇到问题并需要调试,请删除 -ErrorAction quietlycontinue 开关和参数,这隐藏了许多可能会中断脚本的麻烦。

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

      后者是一个更大的主题,所以我将把这个考虑留给你。

      【讨论】:

        【解决方案6】:

        exclude 参数不适用于目录。 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)}
        

        【讨论】:

        • 您的“where”子句实际上与正则表达式“.bak”上的“不匹配”,即“[any character]bak”。你的正则表达式需要是'\.bak',或者如果你想要直接匹配字符串,你应该使用'-notlike'。
        【解决方案7】:

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

        之后,其他所有文件夹都可以正常工作。

        【讨论】:

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

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

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

            • 如果您在嵌套文件夹中,则存在最后一个副本项不起作用的问题。
            【解决方案10】:

            下面的sn-p会将所有文件和文件夹从$source复制到$dest,不包括根文件夹和子文件夹中的.pdb.config文件:

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

            【讨论】:

              【解决方案11】:

              一种使用正则表达式将项目从一个文件夹复制到另一个文件夹的方法:

              $source = '.\source'
              $destination = '.\destination'
              $exclude = '.*\.pdf$|.*\.mp4$|\\folder1(\\|$)|\\folder2(\\|$)'
              
              $itemsToCopy = Get-ChildItem $source -Recurse |
                  Where-Object FullName -notmatch $exclude | Select-Object -Expand FullName
              
              $sourceFullNameLength = (Get-Item $source).FullName.Length
              
              foreach ($item in $itemsToCopy) {
                  $relativeName = $item.Substring($sourceFullNameLength + 1)
                  Copy-Item -Path $item -Destination "$destination\$relativeName"
              }
              

              【讨论】:

                【解决方案12】:

                我写这个是为了日常使用,并打包在脚本模块中,它维护了所有的目录结构并支持通配符:

                function Copy-Folder {
                    [CmdletBinding()]
                    param(
                        [Parameter(Mandatory)]
                        [String]$FromPath,
                
                        [Parameter(Mandatory)]
                        [String]$ToPath,
                
                        [string[]] $Exclude
                    )
                
                    if (Test-Path $FromPath -PathType Container) {
                        New-Item $ToPath -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
                        Get-ChildItem $FromPath -Force | ForEach-Object {
                            # avoid the nested pipeline variable
                            $item = $_
                            $target_path = Join-Path $ToPath $item.Name
                            if (($Exclude | ForEach-Object { $item.Name -like $_ }) -notcontains $true) {
                                if (Test-Path $target_path) { Remove-Item $target_path -Recurse -Force }
                                Copy-Item $item.FullName $target_path
                                Copy-Folder -FromPath $item.FullName $target_path $Exclude
                            }
                        }
                    }
                }
                

                只需拨打Copy-Folder -FromPath 'fromDir' -ToPath 'destDir' -Exclude *.pdb,*.config

                -FromPath-ToPath 可以省略,

                Copy-Folder -FromPath 'fromDir destDir -Exclude *.pdb,*.config

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-05-17
                  • 2014-01-12
                  • 2019-12-02
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多