Search code examples
powershellforeachcopy-item

Foreach/copy-item based on name contains


I'm trying to create a list of file name criteria (MS Hotfixes) then find each file name containing that criteria in a directory and copy it to another directory. I think I'm close here but missing something simple.

Here is my current attempt:

#Create a list of the current Hotfixes.
Get-HotFix | Select-Object HotFixID | Out-File "C:\Scripts\CurrentHotfixList.txt"
#
#Read the list into an Array (dropping the first 3 lines).
$HotfixList = Get-Content "C:\Scripts\CurrentHotfixList.txt" | Select-Object -Skip 3
#
#Use the Hotfix names and copy the individual hotfixes to a folder
ForEach ($Hotfix in $HotfixList) {
        Copy-Item -Path "C:\KBtest\*" -Include *$hotfix* -Destination "C:\KBtarget"
}

If I do a Write-Host $Hotfix and comment out my Copy-Item line I get the list of hotfixes as expected.

If I run just the copy command and input the file name I am looking for - it works.

Copy-Item -Path "C:\KBtest\*" -Include *kb5016693* -Destination "C:\KBtarget"

But when I run my script it copies all the files in the folder and not just the one file I am looking for. I have several hotfixes in that KBtest folder but only one that is correct for testing.

What am I messing up here?


Solution

  • The simplest solution to your problem, taking advantage of the fact that -Include can accept an array of patterns:

    # Construct an array of include patterns by enclosing each hotfix ID
    # in *...*
    $includePatterns = (Get-HotFix).HotfixID.ForEach({ "*$_*" })
    
    # Pass all patterns to a single Copy-Item call.
    Copy-Item -Path C:\KBtest\* -Include $includePatterns -Destination C:\KBtarget
    

    As for what you tried:

    To save just the hotfix IDs to a plain-text file, each on its own line, use the following, don't use Select-Object -Property HotfixId (-Property is implied if you omit it), use Select-Object -ExpandProperty HotfixId:

    Get-HotFix | Select-Object -ExpandProperty HotFixID | Out-File "C:\Scripts\CurrentHotfixList.txt"
    

    Or, more simply, using member-access enumeration:

    (Get-HotFix).HotFixID > C:\Scripts\CurrentHotfixList.txt
    

    Using Select-Object -ExpandProperty HotfixID or (...).HotfixID returns only the values of the .HotfixID properties, whereas Select-Object -Property HotfixId - despite only asking for one property - returns an object that has a .HotfixID property - this is a common pitfall; see this answer for more information.

    Then you can use a Get-Content call alone to read the IDs (as strings) back into an array (no need for Select-Object -Skip 3):

    $HotfixList = Get-Content "C:\Scripts\CurrentHotfixList.txt"
    

    (Note that, as the solution at the top demonstrates, for use in the same script you don't need to save the IDs to a file in order to capture them.)

    This will likely fix your primary problem, which stems from how Out-File creates for-display string representations of the objects ([pscustomobject] instances) that Select-Object -Property HotfixID created:

    Not only is there an empty line followed by a table header at the start of the output (which your Select-Object -Skip 3 call skips), there are also two empty lines at the end.

    When these empty lines were read into $hotfix in your foreach loop, -Include *$hotfix* effectively became -Include **, which therefore copied all files.