Difference between two arrays, with a twist

by scottbass at 2012-09-11 06:17:43

Hi,

I need to write a script that deletes "zombie directories". The directories are named like <stuff><process id><stuff>, for example TD12345_PCNAME. I need to delete the directories which don’t have a matching process id that’s running.

Simplifying this:

# looking for an exact match works
$dirs = (1,2,3,4,5)
$proc = (2,4)
$dirs | ?{$procs -notcontains $
}

# but looking for a regex match doesn't
$dirs="A1A","B2B","C3C","D4D","E5E"
$ids=2,4
$dirs | ?{$ids -notmatch $}
" "
$ids | ?{$dirs -notmatch $
}


In this example, I want to delete A, C, and E directories.

How should I approach this? In this particular example, I could derive the directory from the process ID, i.e load an array with "TD" + PID + "PCNAME", then use the above example that works (-notcontains). But, I’d prefer a generic approach that returns "all objects in array one without a regular expression match in array two"?

Note it doesn’t have to be an array, as long as it’s something I can easily load from Get-Process and Get-ChildItem. So, perhaps a collection with Add and Remove methods is the answer?

Finally, does Powershell have the builtin functionality for the sysinternals handle.exe? I might want to make this bullet proof by checking for open handles on the directory, but would prefer not to have to install handle.exe on each target machine.

Thanks,
Scott
by DonJ at 2012-09-11 07:34:59
So, part of what you’re running into is the two completely different operators. -Contains and -NotContains (along with -In in v3) are designed to look for objects within a collection. -Match and -Notmatch aren’t designed to deal with collections in the same way.

foreach ($id in $ids) { $dirs | where { $id -notmatch $
}}

Is probably roughly what you’re after. You’ve goto unwind one of the arrays yourself. Collections are nearly enough the same thing as an array in PowerShell - you’re going to get the same effect.

I’m not aware of anything internal in PowerShell that’ll get you open handles; something in WMI potentially could.
by scottbass at 2012-09-11 16:58:03
Hi,

That didn’t do what I wanted, but 1) it inspired me, and 2) I found this (Googled "Powershell array remove item"): http://technet.microsoft.com/en-us/libr … spx?ppud=4

Here is my current code, which is close:

$dirs = New-Object System.Collections.ArrayList
$ids = @()
$del = @()

# sample data
2,4 | % {$ids+=$
}
"A1A","B2B","C3C","D4D","E5E","F12F" | % {[Void]$dirs.Add($)}

# does this work? (nope)
# foreach ($id in $ids) { $dirs | where { $id -notmatch $
}}

# check before
$dirs
" "
$ids
" "

# find matches. can't remove from $dirs while enumerating
foreach ($id in $ids) {
foreach ($dir in $dirs) {
if ($dir -match $id) {$del+=$dir}
}
}

# are the matches correct?
"Matches: ",$del," "

# delete and check
$del | % {$dirs.Remove($)}
"Final: These are the directories to delete…",$dirs

# one final check of performance
$dirs.Clear()
$ids = @()
$del = @()

1…200 | % {[Void]$dirs.Add($
)}
1…100 | % {$ids+=$}

foreach ($id in $ids) {
foreach ($dir in $dirs) {
if ("$dir" -match "$id") {$del+=$dir}
}
}

# delete and check (should be > 100)
$del | % {$dirs.Remove($
)}
"Final: These are the directories to delete…",$dirs


In general performance won’t be an issue; both the directory list and matching process ids will be small, but I did want to check anyway. However, it did uncover a logic problem - if a process id matches another process id, the code doesn’t do the right thing. For example, process ids 1024 and 21024. I’ll have to modify the regex to handle this.

If the code can be optimized or "tightened up", please let me know.

Thanks again for the help!!!
by scottbass at 2012-09-12 04:38:36
Here is my final working code, subject to any suggested improvements. Thanks for the help!

$dirs = New-Object System.Collections.ArrayList
$ids = @()
$del = @()

# sample data
2,4 | % {$ids+=$}
"A1A","B2B","C3C","D4D","E5E","F12F" | % {[Void]$dirs.Add($
)}

# does this work? (nope)
# foreach ($id in $ids) { $dirs | where { $id -notmatch $_ }}

# check before
$dirs
" "
$ids
" "

# find matches. can't remove from $dirs while enumerating
foreach ($id in $ids) {
foreach ($dir in $dirs) {
if ($dir -match "^(\w+?)(\d+)(\w+)$") {
if ($matches[2] -eq $id) {$del+=$dir}
}
}
}

# are the matches correct?
"Matches: ",$del," "

# delete and check
$del | % {$dirs.Remove($)}
"Final: These are the directories to delete…",$dirs," "

# one final check of performance
$dirs.Clear()
$ids = @()
$del = @()

1…1024 | % {[Void]$dirs.Add("X"+$
+"X")}
1…1000 | % {$ids+=$}

foreach ($id in $ids) {
foreach ($dir in $dirs) {
if ($dir -match "^(\w+?)(\d+)(\w+)$") {
if ($matches[2] -eq $id) {$del+=$dir}
}
}
}

# delete and check (should be > end of ids)
$del | % {$dirs.Remove($
)}
"Final: These are the directories to delete…",$dirs