windows powershell delete files older than with exclusions

Hi all,

I'd like to delete files and sub-folders older than 14 days while excluding specific folders.

main path:  C:\Projects

under c:\Projects there are subfolders with project names, 1 folder for each project.

I'd like to KEEP the 1st level of all folders, meaning, keeping all projects folders while deleting only their content (files+folders) older than 14 days (all depth-levels), lastly, I'd like to be able to specify in the script certain paths\folders which will remain intact, including their contents.

Any chance someone here is able to write that script?

greatly appreciated!

thanks.
iNc0gAsked:
Who is Participating?

[Webinar] Streamline your web hosting managementRegister Today

x
 
footechConnect With a Mentor Commented:
I don't know if it's more efficient to just ignore the error condition with "-ea SilentlyContinue" or to order the deletions in a "leaf to root" manner so that the error is never encountered.  Kind of an interesting question...  If you equate the process to cutting down or pruning a tree, with one method you start at the top of the tree and you:
 - remove any unwanted leaves
 - once all the leaves are removed from a small branch, remove the branch
 - repeat the process, working your way down the branch (towards the trunk), and each time you encounter a spot where a new branch forms you have to decide whether to cut it off
 - eventually you would be left with a million leaves and branch pieces (each one technically a branch) on the ground.
The second method would be more of a bottom to top process, working from the root to the leaves:
 - as you work your way up (and out), at each branch you would decide whether to cut the branch off
- once a branch was removed you would no longer have to worry about any of its sub-branches or leaves (they're already gone)
Method 1 would be extremely inefficient for humans to do.  Imagine how many cuts it would take to remove an entire tree?
Method 2 would be much more efficient.  Just cut it off at the trunk and you're done.
But when we're talking about computers and filesystems, is there really any difference?  Does deleting a folder (with all it's files and subfolders) differ from deleting all the files, then the subfolders, and then the parent folder?
I'm not well-versed enough with filesystems to know the answer.

Anyway, I went with the ordered approach below.
$root = 'C:\Drivers\win'
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
$date = (Get-Date).AddDays(-14)
$out = @()
Get-ChildItem $root |
 Where { $_.PsIsContainer } |
 Get-ChildItem -Recurse | ForEach {
   $exclude = $false
   foreach ($path in $ExcludePaths)
   {
     If (!$_.PsIsContainer)
     {
       If ("$($_.FullName | Split-Path -Parent)\" -like "$(Join-Path $root $path)\*")
       {$exclude = $true}
     }
     ElseIf ($_.PsIsContainer)
     {
       If ("$($_.FullName)\" -like "$(Join-Path $root $path)\*")
       {$exclude = $true}
     }
   }
   If (!$exclude)
   {$out += $_}
 }
$filesnottodelete = $out | Where {!$_.PsIsContainer -and $_.LastWriteTime -ge $date} | Select -ExpandProperty Fullname | Sort length -Descending
$exdir = @()
foreach ($str in $filesnottodelete)
{
    # get list of parent paths
    while ($str -match "\\")
    {
        $str = $str | Split-Path
        $exdir += $str
    }
}
$exdir = $exdir | Select -Unique
$out |
 Where {$_.lastwritetime -lt $date -and $exdir -notcontains $_.Fullname} |
 Select -ExpandProperty Fullname |
 Sort length -Descending |
 Remove-Item

Open in new window

0
 
QlemoDeveloperCommented:
The objective is not completely clear. Do you want to delete the folders if ONLY older files are in them, or delete all files which are older? That makes a big diffence - the former deletes old projects, the latter old files.
0
 
Raheman M. AbdulSenior Infrastructure Support Analyst & Systems DeveloperCommented:
Try:

Replace pr3 with level 1 project folders if you want them not to delete
Replace new with level 2 or above project folders if you want them not to delete


Get-ChildItem C:\temp\projects -Exclude pr3 -Recurse | foreach { Get-ChildItem $_ -Recurse -Exclude new | Where {$_.creationtime -lt (Get-Date).AddDays(-14)} |Remove-Item -Recurse }
0
Problems using Powershell and Active Directory?

Managing Active Directory does not always have to be complicated.  If you are spending more time trying instead of doing, then it's time to look at something else. For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why

 
QlemoDeveloperCommented:
The code above will go thru the files and folders twice, and not allow to supply any level of folder to exclude.
cls
$ErrorActionPreference = 'Stop'
$root = 'C:\Projects'
$ExcludePaths = '\Project1', '\Project2\DoNotRemove'
$date = (Get-Date).AddDays(-14)
 
 get-childitem $root | where { $_.PsIsContainer } | get-childitem -recurse | % {
   $found = $false
   foreach ($path in $ExcludePaths) {if ($found = $found -or ($_.Path -like ($path+'\*'))) {break}}
   if (!$found) {$_}
 } | where { $_.LastWriteTime -lt $date } | Remove-Item -Recurse -WhatIf

Open in new window

This will simulate the deletion. Remove the -WhatIf to really perform the deletion.
0
 
iNc0gAuthor Commented:
Hi Guys! thank you very much for your responses.

Your scripts look great, the final touch (if possible) would be to make all project folders intact:
-Projects
     -Project1
     -Project2
     -Project3
     -Project4

Goals:
1) I'd like to keep ALL 1st level folders (project1,2,3,4.....etc) no matter what.

2) Delete only the contents of the project folders (files+subfolders in all depths) ONLY if the content is older than 14 days, in no situation should the 1st level folders be deleted, just their contents based on older than 14 days.

3) The ability to exclude certain projects/paths inside projects

any chance this is possible ?  ^.^

Many thanks again!
0
 
Raheman M. AbdulSenior Infrastructure Support Analyst & Systems DeveloperCommented:
Make a copy of your project folders and run my script on them
I have tried and it worked for me.
0
 
QlemoDeveloperCommented:
Obviously I was ignoring the "keep all 1st level folders" stuff ... But that is all what was missing.
cls
$ErrorActionPreference = 'Stop'
$root = 'C:\Projects'
$ExcludePaths = '\Project1', '\Project2\DoNotRemove'
$date = (Get-Date).AddDays(-14)
 
 get-childitem $root | where { $_.PsIsContainer } |
  get-childitem | where { $_.PsIsContainer } |
  get-childitem -recurse | % {
   $found = $false
   foreach ($path in $ExcludePaths) {if ($found = $found -or ($_.Path -like ($path+'\*'))) {break}}
   if (!$found) {$_}
 } | where { $_.LastWriteTime -lt $date } | Remove-Item -Recurse -WhatIf

Open in new window

0
 
iNc0gAuthor Commented:
I ran the scripts on some test folders, here are some results:

@Qlemo
********
I used folder C:\drivers\win with 4 subfolders in it: AUDIO, DISPLAY6, INTELINF, MONITOR
I specified 2 paths to exclude from deletion:
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
while root folder is: $root = 'C:\drivers\win'

ran the script with -whatif and it excluded AUDIO folder but performed the "remove file" on c:\drivers\win\INTELINF\infinst_autol" and "remove-directory" of all of infinst_autol sub-folders while it was supposed to exclude it from deletion.

the 4th folder after INTELINF called "MONITOR" got skipped from deletion for some reason.

@marahman3001
***************
ran the script with -whatif:
Get-ChildItem C:\drivers\win -Exclude Audio -Recurse | foreach { Get-ChildItem $_ -Recurse -Exclude INTELINF\infinst_autol | Where {$_.creationtime -lt (Get-Date).AddDays(-14)} |Remove-Item -Recurse -WhatIf }

Open in new window


it managed to exclude AUDIO folder but performed remove-file and remove-directory on
"INTELINF\infinst_autol" which was supposed to be excluded.

it then continued to the 4th folder "MONITOR" (as should) and performed the remove file operation on it since it doesn't contain any subfolders.
0
 
QlemoConnect With a Mentor DeveloperCommented:
Sorry for that rubbish :/.
cls
$ErrorActionPreference = 'Stop'
$root = 'C:\Drivers\win'
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
$date = (Get-Date).AddDays(-14)
 
get-childitem $root | where { $_.PsIsContainer } |
  get-childitem | where { $_.PsIsContainer } |
  get-childitem -recurse | % {
   $found = $false
   foreach ($path in $ExcludePaths) {if ($found = $found -or ($_.FullName -like "$root$path\*")) {break}}
   if (!$found) {$_}
 } | where { $_.LastWriteTime -lt $date } | Remove-Item -Recurse -WhatIf

Open in new window

0
 
footechCommented:
Here you go.  I borrowed some of the ideas from Qlemo.
$ErrorActionPreference = 'Stop'
$root = 'C:\drivers\win'
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
$date = (Get-Date).AddDays(-14)

Get-ChildItem $root |
 Where { $_.PsIsContainer } |
 Get-ChildItem -Recurse | ForEach {
   $found = $false
   foreach ($path in $ExcludePaths)
   {
     if (($_.FullName | Split-Path -Parent) -like (Join-Path $root $path))
     {$found = $true}
   }
   if (!$found)
   {$_}
 } | Where { $_.LastWriteTime -lt $date } | Remove-Item -Recurse -WhatIf

Open in new window

0
 
Raheman M. AbdulSenior Infrastructure Support Analyst & Systems DeveloperCommented:
Try just infinst_autol instead of INTELINF\infinst_autol
0
 
iNc0gAuthor Commented:
@Qlemo - We're making some progress :-)  ran your script again, results are:
Exclusions - working OK

Deletions - working ok EXCEPT for folder "MONITOR" which is the last folder, for some reason it does not "touch" that folder or mention it on the script output while in reality it should delete all content of MONITOR folder and leave the folder empty.

@footech - Ran your script, results are:
Exclusions - only the 1st mentioned exclusion is actually excluded ('\AUDIO')
the 2nd exclusion '\INTELINF' or '\INTELINF\infinst_autol' doesn't get excluded.

Deletions - Working OK, including the last folder "MONITOR" as should.

@marahman3001 - Did what you suggested, no go, still getting deleted.


Thank you all very much for your hard efforts!! waiting to hear from you ^.^
0
 
QlemoDeveloperCommented:
Had to remove one level of get-childitem - the MONITOR folder does not have any subfolders, I presume, and my code only looked for subfolders.
cls
$ErrorActionPreference = 'Stop'
$root = 'C:\Drivers\win'
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
$date = (Get-Date).AddDays(-14)
 
get-childitem $root | where { $_.PsIsContainer } |
  get-childitem -recurse | % {
   $found = $false
   foreach ($path in $ExcludePaths) {if ($found = $found -or ($_.FullName -like "$root$path\*")) {break}}
   if (!$found) {$_}
 } | where { $_.LastWriteTime -lt $date } | Remove-Item -Recurse -WhatIf
 

Open in new window

0
 
iNc0gAuthor Commented:
@Qlemo - Some folders have subfolders of several depth levels and some doesn't have at all.  should your script do the job in that case ?
0
 
QlemoDeveloperCommented:
The new one - yes. The old one did the folder, subfolders and files of
   c:\drivers\win\monitor\one        
   c:\drivers\win\monitor\one\two\three\four
but NO files in
   c:\drivers\win\monitor
Your requirement is to leave the folder MONITOR itself, but remove anything it contains if old.
0
 
iNc0gAuthor Commented:
@Qlemo - 2 problems now:
1. $ExcludePaths only excludes the 1st mentioned exclusion and ignores the 2nd .
2. $ExcludePaths is unable to exclude 2nd level subfolders such as: \AUDIO\Folder , only manages to exclude the root folder \AUDIO .
0
 
footechCommented:
Think I've got it all worked out.
$root = 'C:\Drivers\win'
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
$date = (Get-Date).AddDays(-14)

Get-ChildItem $root |
 Where { $_.PsIsContainer } |
 Get-ChildItem -Recurse | % {
   $exclude = $false
   foreach ($path in $ExcludePaths)
   {
     If (!$_.PsIsContainer)
     {
       If ("$($_.FullName | Split-Path -Parent)\" -like "$(Join-Path $root $path)\*")
       {$exclude = $true}
     }
     ElseIf ($_.PsIsContainer)
     {
       If ("$($_.FullName)\" -like "$(Join-Path $root $path)\*")
       {$exclude = $true}
     }
   }
   if (!$exclude)
   {$_}
 } | where { $_.LastWriteTime -lt $date } | Remove-Item -Recurse -WhatIf

Open in new window

0
 
iNc0gAuthor Commented:
footech - Ran your script, it ignores files age which resides in 3rd level and above folder depth.

c:\drivers\win\INTELINF\infinst_autol\ia64\ENU\FileNotOlderThan14Days.txt
the above path got deleted even though it contains the .txt file which is not older than 14 days (modified it intentionally).
0
 
footechCommented:
Yikes!  I see what is happening.  A parent folder (which doesn't meet any of the exclude criteria) is being deleted (along with all children) before the children are examined.  Try this out.  It's not real polished and probably contains some cruft that could be removed, but just checking functionality now.
$root = 'C:\Drivers\win'
$ExcludePaths = '\AUDIO', '\INTELINF\infinst_autol'
$date = (Get-Date).AddDays(-14)
$out = @()
Get-ChildItem $root |
 Where { $_.PsIsContainer } |
 Get-ChildItem -Recurse | ForEach {
   $exclude = $false
   foreach ($path in $ExcludePaths)
   {
     If (!$_.PsIsContainer)
     {
       If ("$($_.FullName | Split-Path -Parent)\" -like "$(Join-Path $root $path)\*")
       {$exclude = $true}
     }
     ElseIf ($_.PsIsContainer)
     {
       If ("$($_.FullName)\" -like "$(Join-Path $root $path)\*")
       {$exclude = $true}
     }
   }
   if (!$exclude)
   {$out += $_}
 }
$folderstodelete = $out | ? {$_.psiscontainer -and $_.lastwritetime -lt $date} | Select -ExpandProperty fullname | sort length -Descending
$filesnottodelete = $out | ? {!$_.psiscontainer -and $_.lastwritetime -ge $date} | Select -ExpandProperty fullname | sort length -Descending
$filestodelete = $out | ? {!$_.psiscontainer -and $_.lastwritetime -lt $date} | Select -ExpandProperty fullname | sort length -Descending
$exdir = @()
foreach ($str in $filesnottodelete)
{
    # get list of parent paths
    while ($str -match "\\")
    {
        $str = $str | split-path
        $exdir += $str
    }
}
$exdir = $exdir | Select -Unique
$folderstodelete | sort -Descending | ? {$exdir -notcontains $_} | Remove-Item -Recurse -WhatIf
$filestodelete | Remove-Item -WhatIf

Open in new window

0
 
iNc0gAuthor Commented:
And we have a winner!
@footech - your script worked.
regarding the cruft - it seems like it tried deleting the data a 2nd time therefor received:

Remove-Item : Cannot find path 'C:\Temp\project1\ossv\manageability\InstallHostAgentPlugins.exe' because it does not exist.
At C:\scripts\ftp_delete.ps1:40 char:29
+ $filestodelete | Remove-Item <<<<
    + CategoryInfo          : ObjectNotFound: (C:\Temp\project...gentPlugins.exe:String) [Remove-Item], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

Open in new window


several times.

any chance to clean up the cruft without impairing the script?  it finally works I am afraid to get in a loop again ;-)
0
 
footechCommented:
The simplest would be to just swap lines 39 and 40.  I'll try to look at cleaning it up a bit more later.
0
 
QlemoDeveloperCommented:
Another simple way is to set the error action of remove-item to continue without throwing an error:
$folderstodelete | sort -Descending | ? {$exdir -notcontains $_} | Remove-Item -Recurse -WhatIf -EA SilentlyContinue
$filestodelete | Remove-Item -WhatIf -EA SilentlyContinue

Open in new window

That is the "brute force" approach ;-).
0
All Courses

From novice to tech pro — start learning today.