Home > Software engineering >  Foreach/copy-item based on name contains
Foreach/copy-item based on name contains

Time:09-07

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?

CodePudding user response:

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.

CodePudding user response:

first, you do not need to create and import those textfiles:

get-hotfix | ?{$_.hotfixid -notmatch 'KB5016594|KB5004567|KB5012170'} | %{
    copy-item -path "C:\kbtest\$($_.HotfixId).exe" -Destination "C:\kbTarget"
}

This filters for the hotfixes you do not want, if you do not need it remove:

?{$_.hotfixid -notmatch 'KB5016594|KB5004567|KB5012170'}

I assume that those files are exe files in my example.

  • Related