12

Using Powershell v2.0 I want to delete any files older than X days:

$backups = Get-ChildItem -Path $Backuppath | 
                Where-Object {($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like "backup*")}

foreach ($file in $backups)
{
    Remove-Item $file.FullName;
}

However, when $backups is empty I get: Remove-Item : Cannot bind argument to parameter 'Path' because it is null.

I've tried:

  1. Protecting the foreach with if (!$backups)
  2. Protecting the Remove-Item with if (Test-Path $file -PathType Leaf)
  3. Protecting the Remove-Item with if ([IO.File]::Exists($file.FullName) -ne $true)

None of these seem to work, what if the recommended way of preventing a foreach loop from being entered if the list is empty?

1
  • @Dan - Tried both ($backups > 0) and (@($backups).count -gt 0), but neither work as expected when there are no files.
    – SteB
    Dec 13, 2012 at 11:49

4 Answers 4

23

With Powershell 3 the foreach statement does not iterate over $null and the issue described by OP no longer occurs.

From the Windows PowerShell Blog post New V3 Language Features:

ForEach statement does not iterate over $null

In PowerShell V2.0, people were often surprised by:

PS> foreach ($i in $null) { 'got here' }

got here

This situation often comes up when a cmdlet doesn’t return any objects. In PowerShell V3.0, you don’t need to add an if statement to avoid iterating over $null. We take care of that for you.

For PowerShell $PSVersionTable.PSVersion.Major -le 2 see the following for original answer.


You have two options, I mostly use the second.

Check $backups for not $null. A simple If around the loop can check for not $null

if ( $backups -ne $null ) {

    foreach ($file in $backups) {
        Remove-Item $file.FullName;
    }

}

Or

Initialize $backups as a null array. This avoids the ambiguity of the "iterate empty array" issue you asked about in your last question.

$backups = @()
# $backups is now a null value array

foreach ( $file in $backups ) {
    # this is not reached.
    Remove-Item $file.FullName
}

Sorry, I neglected to provide an example integrating your code. Note the Get-ChildItem cmdlet wrapped in the array. This would also work with functions which could return a $null.

$backups = @(
    Get-ChildItem -Path $Backuppath |
        Where-Object { ($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like "backup*") }
)

foreach ($file in $backups) {
    Remove-Item $file.FullName
}
2
  • I've used the first one (it's easier to understand), I couldn't get the second one to work (I was probably doing something wrong).
    – SteB
    Dec 13, 2012 at 14:18
  • @SteB You're correct, my example was poorly explained (it still is), but I've provided an edit including your example code. For a better explanation of the behavior, please see this post on Keith Hill's blog, he is not only a PowerShell expert, but also a far better writer than me. Keith is active on StackOverflow, I would encourage you (or anyone interested in PS) to check out his stuff.
    – jscott
    Dec 13, 2012 at 14:43
2

I know this is an old post but I'd like to point out that the ForEach-Objec cmdlet doesn't suffer the same issue as using the ForEach key word. So you can pipe the results of DIR to ForEach and just reference the file using $_, like:

$backups | ForEach{ Remove-Item $_ }

You can actually forward the Dir command itself through the pipe and avoid even assigning the variable like:

Get-ChildItem -Path $Backuppath | 
Where-Object {
             ($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and `
             (-not $_.PSIsContainer) -and ($_.Name -like "backup*")
             } |
ForEach{ Remove-Item $_ }

I added line breaks for readability.

I understand some people like ForEach/In for readability. Sometimes ForEach-Object can get a little hairy, especially if you are nesting as it gets hard to follow the $_ reference. At any rate, for a small operation like this it's perfect. Many people also assert it's faster however I've found that to be only slightly.

1
  • 1
    +1 But with Powershell 3 (circa June 2012) the foreach statement no longer steps into $null, so the error described by OP no longer occurs. See the section "ForEach statement does not iterate over $null" in the Powershell Blog post New V3 Language Features.
    – jscott
    Mar 26, 2015 at 11:50
1

I've developed a solution by running the query twice, once to get the files and once to count the files by casting the get-ChilItem to return an array (casting $backups as an array after the fact doesn't seem to work).
At least it works as expected (performance shouldn't be as issue since there'll never be more than a dozen files), if anyone knows of a single-query solution, please post it.

$count = @(Get-ChildItem -Path $zipFilepath | 
                Where-Object {($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like $partial + "*")}).count;

if ($count -gt 0)
{
    $backups = Get-ChildItem -Path $zipFilepath | 
                Where-Object {($_.lastwritetime -lt (Get-Date).addDays(-$DaysKeep)) -and (-not $_.PSIsContainer) -and ($_.Name -like $partial + "*")};

    foreach ($file in $backups)
    {
        Remove-Item $file.FullName;
    }
}
2
  • 1
    I've edited the post for efficiency as it was easier than explaining in the comments. Just revert it if you don't like it.
    – Dan
    Dec 13, 2012 at 12:05
  • @Dan - Doh, can't believe I didn't spot that, thanks.
    – SteB
    Dec 13, 2012 at 14:08
0

Use the following to evaluate if the array has any contents:

if($backups.count -gt 0) { echo "Array has contents" } else { echo "Array is empty" }

If the variable doesn't exist Powershell will simply evaluate it as false, so no need to check if it exists.

3
  • Adding if ($backups.count -gt 0) stops the loop executing even when there's 1 item in $backups. $backups.count even on it's own doesn't output anything.
    – SteB
    Dec 13, 2012 at 11:30
  • @SteB Ah, I guess count isn't implemented for whatever object type is holding the data. I scan read and presumed it was an array.
    – Dan
    Dec 13, 2012 at 11:33
  • $count = @($backups).count; almost works, but when there are no files if f($count -gt 0) is true!
    – SteB
    Dec 13, 2012 at 11:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .