views:

572

answers:

2

Hi there,

I'm using Powershell 1.0 to remove an item from an Array. Here's my script:

param (
    [string]$backupDir = $(throw "Please supply the directory to housekeep"), 
    [int]$maxAge = 30,
    [switch]$NoRecurse,
    [switch]$KeepDirectories
    )

$days = $maxAge * -1

# do not delete directories with these values in the path
$exclusionList = Get-Content HousekeepBackupsExclusions.txt

if ($NoRecurse)
{
    $filesToDelete = Get-ChildItem $backupDir | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
else
{
    $filesToDelete = Get-ChildItem $backupDir -Recurse | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}

foreach ($file in $filesToDelete)
{       
    # remove the file from the deleted list if it's an exclusion
    foreach ($exclusion in $exclusionList)
    {
        "Testing to see if $exclusion is in " + $file.FullName
        if ($file.FullName.Contains($exclusion)) {$filesToDelete.Remove($file); "FOUND ONE!"}
    }
}

I realize that Get-ChildItem in powershell returns a System.Array type. I therefore get this error when trying to use the Remove method:

Method invocation failed because [System.Object[]] doesn't contain a method named 'Remove'.

What I'd like to do is convert $filesToDelete to an ArrayList and then remove items using ArrayList.Remove. Is this a good idea or should I directly manipulate $filesToDelete as a System.Array in some way?

Thanks

+5  A: 

The best way to do this is to use Where-Object to perform the filtering and use the returned array.

You can also use @splat to pass multiple parameters to a command (new in V2). If you cannot upgrade (and you should if at all possible, then just collect the output from Get-ChildItems (only repeating that one CmdLet) and do all the filtering in common code).

The working part of your script becomes:

$moreArgs = @{}
if (-not $NoRecurse) {
  $moreArgs["Recurse"] = $true
}

$filesToDelete = Get-ChildItem $BackupDir @moreArgs |
                 where-object {-not $_.PsIsContainer -and 
                               $_.LastWriteTime -lt $(Get-Date).AddDays($days) -and
                              -not $_.FullName.Contains($exclusion)}

In PSH arrays are immutable, you cannot modify them, but it very easy to create a new one (operators like += on arrays actually create a new array and return that).

Richard
(one typo `PSIsContainere`) Yes, I would prefer `Where-Object` as well. However in the question there are two loops - the inner goes through `$exclusionList` so the condition should probably be something like `-not $($f=$_.Fullname; $exclusionList |?{$f.Contains($_)})`
stej
Thanks Richard - can I use an string array for $exclusion. If you look closer at the code you will see that I would have to call get-childitem for every exclusion. This would not perform well if I have a lot of exclusions.
Mark Allison
@stej: Will correct
Richard
@MarkAllison: `-exclude` parameter takes a `string[]` so you can pass multiple wildcards.
Richard
+2  A: 

I agree with Richard, that Where-Object should be used here. However, it's harder to read. What I would propose:

# get $filesToDelete and #exclusionList. In V2 use splatting as proposed by Richard.

$res = $filesToDelete | % {
    $file = $_
    $isExcluded = ($exclusionList | % { $file.FullName.Contains($_) } )
    if (!$isExcluded) { 
        $file
    }
}

#the  files are in $res

Also note that generally it is not possible to iterate over a collection and change it. You would get an exception.

$a = New-Object System.Collections.ArrayList
$a.AddRange((1,2,3))
foreach($item in $a) { $a.Add($item*$item) }

An error occurred while enumerating through a collection:
At line:1 char:8
+ foreach <<<< ($item in $a) { $a.Add($item*$item) }
    + CategoryInfo          : InvalidOperation: (System.Collecti...numeratorSimple:ArrayListEnumeratorSimple) [], RuntimeException
    + FullyQualifiedErrorId : BadEnumeration
stej