views:

1137

answers:

2

I have a share that is a "junk drawer" for end-users. They are able to create folders and subfolders as they see fit. I need to implement a script to delete files created more than 31 days old. I have that started with Powershell. I need to follow up the file deletion script by deleting subfolders that are now empty. Because of the nesting of subfolders, I need to avoid deleting a subfolder that is empty of files, but has a subfolder below it that contains a file. I have seen thanswers to deleteing folders by date, and empty subfolders which did not take into account the nesting.

Example: - FILE3a is 10 days old. FILE3b is 45 days old. - I want to clean up the structure removing files older than 30 days, and delete empty subfolders.

C:\Junk\subfolder1a\subfolder2a\FILE3a

C:\Junk\subfolder1a\subfolder2a\subfolder3a

C:\Junk\subfolder1a\subfolder2B\FILE3b

Desired result: Delete FILE3b, subfolder2B, & subfolder3a.
Leave in place subfolder1a, subfolder2a, and FILE3a

I can recursively clean up the files. How do i clean up the subfolders without deleting subfolder1a? (The "Junk" folder will always remain).

Thank you.

+2  A: 

To remove Files older than 30 days:

get-childitem -recurse | `
    ? {$_.GetType() -match "FileInfo"} | `
    ?{ $_.LastWriteTime -lt [datetime]::now.adddays(-30) }  | `
    rm -whatif

(just remove the -whatif to actually perform)

Follow up with:

 get-childitem -recurse | `
     ? {$_.GetType() -match "DirectoryInfo"} | `
     ?{ $_.GetFiles().Count -eq 0 -and $_.GetDirectories().Count -eq 0 } | `
     rm -whatif
John Weldon
If you want to match on a type, you can use the -is operator e.g. $_ -is [IO.FileInfo].
Keith Hill
+4  A: 

I would do this in two passes - deleting the old files first and then the empty dirs:

Get-ChildItem -recurse | Where {!$_.PSIsContainer -and `
$_.LastWriteTime -lt (get-date).AddDays(-31)} | Remove-Item -whatif

Get-ChildItem -recurse | Where {$_.PSIsContainer -and `
@(Get-ChildItem -Lit $_.Fullname -r | Where {!$_.PSIsContainer}).Length -eq 0} |
Remove-Item -recurse -whatif

This type of operation demos the power of nested pipelines in PowerShell which the second set of commands demonstrates. It uses a nested pipeline to recursively determine if any directory has zero files under it.

Keith Hill
Nice! +1 I didn't know about nested pipelines.
John Weldon
Yeah, they can be pretty darn useful. My hope is that for the next version of PoSh, we can dispense with the Where PSIsContainer tests. It would be soo much nicer if I could just request that directly from Get-ChildItem e.g. Get-ChildItem -Recurse -Container or Get-ChildItem -Recurse -Leaf. It could also be a very nice perf optimization for the provider to do that sort of filtering. One can dream. :-)
Keith Hill
Thank you! This solution is exactly what I was after. The recursion checking the subfolders for remaining files was eluding me - not to mention the syntax and nesting. I am new to PowerShell - what is the '@' / how does that function?
ScriptSearcher
The @(<some commands in here including pipelined commands>) syntax ensures that no matter whether we get no result ($null) or a single result or an array of results - the enternally visible result using @() *will* always be an array with either 0, 1 or N items in it. That is how I'm sure that the result will have a Length property and that the Length property is for an array (as opposed to a string, which might get returned if the result is a single, string value). Dynamic languages like this can be pretty powerful but they make you think. :-)
Keith Hill
The deletion pipeline actually contains a slight bug. I tried it within a folder containing subfolders surrounded by `[]`.The fix is pretty simple, `@($_ | Get-ChildItem -Recurse | Where { !$_.PSIsContainer })` (instead of `Get-ChildItem $_.FullName -Recurse`).
Alexander Groß
Or just specify `$_.FullName` to the `-LiteralPath` parameter. Piping effectively does the same thing. Thanks for the heads up.
Keith Hill