Exclude list in PowerShell Copy-Item does not appear to be working

前端 未结 10 866
旧时难觅i
旧时难觅i 2020-12-02 11:40

I have the following snippet of PowerShell script:

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


        
相关标签:
10条回答
  • 2020-12-02 12:39

    I think the best way is to use Get-ChildItem and pipe in the Copy-Item command.

    I found that this worked:

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

    Basically, what is happening here is that you're going through the valid files one by one, then copying them to the new path. The 'Join-Path' statement at the end is so that the directories are also kept when copying over the files. That part takes the destination directory and joins it with the directory after the source path.

    I got the idea from here, and then modified it a bit to make it work for this example.

    I hope it works!

    0 讨论(0)
  • 2020-12-02 12:41

    Note that the syntax spec calls for a 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>]
    

    If you're not explicit in your array generation, you end up with an Object[] - and that is ignored in many cases, leaving the appearance of "buggy behavior" because of type-safety. Since PowerShell can process script-blocks, evaluation of other than a type-specific variable (so that a valid string could be determined) would leave an opening for the potential of an injection mode attack on any system whose execution policy were lax.

    So this is unreliable:

    PS > $omissions = @("*.iso","*.pdf","*.zip","*.msi")
    PS > $omissions.GetType()
    
    Note the result....
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Object[]                                 System.Array
    

    And this works.... for example:

    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
    

    Note that even a "single" element would still require the same cast, so as to create a 1-element array.

    If you're trying this at home, be sure to use the Replace-Variable "omissions" to clean out the existence of $omissions before recasting it in the examples shown above.

    And as far as a pipeline that works reliably that I've tested....

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

    The above does a directory listing of the source files in the base (selected "current") directory, filters out potential problem items, converts the file to the basename and forces cp (copy-item alias) to re-access the file "by name" in the "current directory" - thus reacquiring the file object, and copies it. This will create empty directories, including those that may even contain excluded files (less the exclusions of course). Note also that "ls" (get-childitem) does NOT -recurse - that is left to cp. Finally - if you're having problems and need to debug, remove the -ErrorAction silentlycontinue switch and argument, which hides a lot of nuisances that might interrupt the script otherwise.

    For those whose comments were related to "\" inclusions, keep in mind that you're working over the .NET sub-layer via an interpreter (i.e. PowerShell), and in c# for example, the inclusion of a single "\" (or multiple singles in a string), results in the compiler demanding you correct the condition by using either "\\" to escape the backslash, or precede the string with an @ as in @"\"; with the other remaining option being the enclosure of the string in single quotes, as '\'. All of this is because of ASCII interpolation of character combinations like "\n" etc.

    The latter is a much bigger subject, so I'll leave you with that consideration.

    0 讨论(0)
  • 2020-12-02 12:43

    I had this problem, too, and spent 20 minutes with applying the solutions here, but kept having problems.
    So I chose to use robocopy - OK, it's not powershell, but should be available everywhere where powershell runs.

    And it worked right out of the box:

    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
    

    Plus, it has tons of features, like resumable copy. Docs here.

    0 讨论(0)
  • 2020-12-02 12:46

    Get-ChildItem with Join-Path was working mostly for me, but I realized it was copying root directories inside the other root directories, which was bad.

    For example

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

    • Source Directory: c:\SomeFolder\CopyInHere

    • Destination Directory: d:\PutItInHere

    Goal: Copy every childitem Inside c:\SomeFolder\CopyInHere to the root of d:\PutItInHere, but not including c:\SomeFolder\CopyInHere itself.
    - E.g. Take all the children of CopyInHere and make them Children of PutItInHere

    The above examples do this most of the way, but what happens is It Creates a folder Called SubFolder, and Creates a Folder in Folder called SubFolder.

    That's because Join-Path Calculates a destination path of d:\PutItInHere\SubFolder for the SubFolder child item, so SubFolder get's created in a Folder called SubFolder.

    I got around this by Using Get-ChildItems to bring back a collection of the items, then using a loop to go through it.

    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
    }
    

    What this does in short, is it uses the current item's full name for the destination calculation. However it then checks to see if it is a DirectoryInfo object. If it is it checks if it's Parent Folder is the Source Directory, that means the current folder being iterated is a direct child of the source directory, as such we should not append it's name to the destination directory, because we want that folder to be created in the destination directory, not in a folder of it's in the destination directory.

    Following that, every other folder will work fine.

    0 讨论(0)
提交回复
热议问题