Count deleted empty folders - powershell

I have a script right now that looks for all files certain day old and certain file extension and it deletes all of the files. This works fine and it counts fine
Then I have to delete all folders that correspond to being empty and that includes all sub folders too.
I also have to output this into a file and display each file deleted. The output would show 30 folders deleted but actually 48 were really deleted.
Now my question is i am trying to do a count of all the folders deleted. I have this script but it just counts the deepest folders not all the ones deleted.
Here is the part of the script i can not get to count
$TargetFolder = "C:\Users\user\Desktop\temp"
$LogFile = "C:\Summary.txt"
$Count = 0
Date | Out-File -filepath $LogFile
get-childitem $TargetFolder -recurse -force | Where-Object {$_.psIsContainer}| sort fullName -des |
Where-Object {!(get-childitem $_.fullName -force)} | ForEach-Object{$Count++; $_.fullName} | remove-item -whatif | Out-File -filepath $LogFile -append
$Count = "Total Folders = " + $Count
$Count | Out-File -filepath $LogFile -append

Although the sort call will correctly send each directory through the pipeline in nesting order, since they are not really being removed (remove-item -whatif), the parents will still contain their empty child directories and so will not pass the second condition (!(get-childitem $_.fullName -force)). Also note that Remove-Item does not produce any output, so the deleted directories will not appear in the log.
Adapting Keith Hill's answer to a similar question, here is a modified version of the original script that uses a filter to retrieve all empty directories first, then removes and logs each one:
filter Where-Empty {
$children = #($_ |
Get-ChildItem -Recurse -Force |
Where-Object { -not $_.PSIsContainer })
if( $_.PSIsContainer -and $children.Length -eq 0 ) {
$_
}
}
$emptyDirectories = #(
Get-ChildItem $TargetFolder -Recurse -Force |
Where-Empty |
Sort-Object -Property FullName -Descending)
$emptyDirectories | ForEach-Object {
$_ | Remove-Item -WhatIf -Recurse
$_.FullName | Out-File -FilePath $LogFile -Append
}
$Count = $emptyDirectories.Count
"Total Folders = $Count" | Out-File -FilePath $LogFile -Append
Note that -Recurse was added to the call to Remove-Item, as empty child directories will remain when using -WhatIf. Neither flag should be needed when performing an actual remove on an empty directory.

Not tested:
get-childitem $TargetFolder -recurse -force |
where-object{$_.psiscontainer -and -not (get-childitem $_.fullname -recurse -force | where-object {!($_.psiscontainer)}}|
sort fullName -des |
Where-Object {!(get-childitem $.fullName -force)} |
ForEach-Object{$Count++; $_.fullName} |
remove-item -whatif |
Out-File -filepath $LogFile -append

Related

recursively count files in folder and output to file

I want to count files for every folder on an E-drive, and output the folder path and file count to a text file using PowerShell (version 2).
I have found this script, but it outputs to console. How do I change it to output to a text file?
Set-Location -Path E:\
Get-ChildItem -recurse | Where-Object{ $_.PSIsContainer } | ForEach-Object{ Write-Host $_.FullName (Get-ChildItem $_.FullName | Measure-Object).Count }
I think it would be best to get an array of resulting objects where you can store both the directory path and the number of files it contains. That way, you can afterwards show it in the console and also save it to a structured CSV file you can open in Excel.
This is for PowerShell 2:
# to keep the property order in PS version < 3.0, create an
# Ordered Dictionary to store the properties first
$dict = New-Object System.Collections.Specialized.OrderedDictionary
# now loop over the folders
$result = Get-ChildItem -Path 'E:\' -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer } |
ForEach-Object {
# add the results in the temporary ordered dictionary
$dict.Add('Directory', $_.FullName)
$dict.Add('Files', #(Get-ChildItem -Path $_.FullName -Force -ErrorAction SilentlyContinue |
Where-Object { !$_.PSIsContainer }).Count)
# and output a PSObject to be collected in array '$result'
New-Object PSObject -Property $dict
$dict.Clear()
}
# output on screen
$result | Format-Table -AutoSize
#output to CSV file
$result | Export-Csv -Path 'D:\Test\FileCount.csv' -NoTypeInformation
The -Force switch makes sure you also count items that otherwise can't be accessed by the user, such as hidden or system files.
Get-ChildItem c:\tmp -recurse |
Where-Object{ $_.PSIsContainer } |
ForEach-Object {
"$($_.Fullname) $((Get-ChildItem $_.FullName | Where-Object{!$_.PSIsContainer}).count)"
} |
Out-File c:\tmp\out.txt
You can use the > operator for this:
Set-Location -Path E:\
(Get-ChildItem -recurse | Where-Object{ $_.PSIsContainer } | ForEach-Object{ Write-Host $_.FullName (Get-ChildItem $_.FullName | Measure-Object).Count }) >"OUTPUTFILEPATH.txt"

Out-File when copying items and removing them

I got a script to take backup and to remove files in generations (group). I need to add some logging of which files it copies and also which ones it deletes. In all my previous scrips, I been using Out-File, but in this case for the copy I can't get it to work.
If I add it to the Copy-Item part it creates the file but it simply wont write any input. What I am missing?
#$a = Get-Date
#$a.ToUniversalTime()
foreach ($file in (Get-ChildItem -File $localpath -Recurse | Where {$_.LastWriteTime -gt (Get-Date).AddDays(-1)})) {
Copy-Item -Path $file.FullName -Destination "C:\qlikview Storage\privatedata\backup\$file.$(get-date -f yyyy-MM-dd)"
}
$Groups = Get-ChildItem -Path "C:\qlikview Storage\privatedata\backup" |
Group-Object -Property Basename |
Where-Object {$_.Count -gt 2}
foreach ($g in $Groups) {
$g.Group |
sort LastWriteTime -Descending |
select -Skip 2 |
foreach {del $_.FullName -Force}
}
The #a is for later to add timestamps the logging to see how long it takes.
Am I thinking wrong assuming Out-File is the way to go?
Add the -Verbose switch to your Copy-Item and Remove-Item commands. This will dump the copied/removed files to the verbose stream.
Afterwards you can redirect the verbose stream to the output stream (4>&1) and log it the a file.
Example :
Copy-Item... -Verbose 4>&1 | Out-file log.txt
Additional info can be found in about_Redirection.

How to add print to console in PowerShell script?

I am new to PowerShell and I have created the following code to delete specific files and folders:
$myFolderPath = "C:\Test"
$myLimit = (Get-Date).AddDays(-14)
# Delete files according to filter.
Get-ChildItem -Path $myFolderPath -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $myLimit} | Remove-Item -Force
# Delete empty folders.
Get-ChildItem -Path $myFolderPath -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
Is it possible to print out the full path of each item that will be removed to the console before the actual Remove-Item operation will be performed?
I guess sth. has to be added here....
... | Remove-Item -Force
and here...
... | Remove-Item -Force -Recurse
but I cannot find out how to implement that in an elegant way (without code duplication).
You can replace the remove-Item-Parts with
Foreach-Object { $_.fullname; Remove-Item -Path $_.Fullname (-Recurse) -Force}
LotPings comment might be better idea, if that is what you want.
It does not get a lot of attention but Tee-Object could be a simple addition to the pipeline here. Redirect the output to a variable that you can print later.
...Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $myLimit} |
Tee-Object -Variable removed | Remove-Item -Force
$removed | Write-Host
All of the file objects piped will be sent to $removed and then to Remove-Item. Since you have more than one delete pipeline you can also use the -Append parameter so that all files are saved in one variable if you so desired.
However this does not mean they were deleted. Just they made it passed the pipe. If you really wanted to be sure you should be using another utility like robocopy which has logging features.

Could not redirect the file names that are being deleted to an output csv file using Powershell

Am trying to delete files older than x days and would like to know which file is being deleted.
Am using below powershell script, it doesnt work
$limit = (Get-Date).AddDays(-365)
$path = $args[0]
# Delete files older than the $limit.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force | select Name,LastWriteTime | Export-CSV -NoTypeInformation -Path $args[1]
Am passing first argument as path where files are there.
Second argument is the output file which should contain the file and date modified values of those which gets deleted.
The above code works fine for deletion, but doesnt redirects the file names and the last modified values which got deleted.
If I use below code, it only redirects the file names and last modified values but files doesnt get deleted.
Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | select Name,LastWriteTime | Export-CSV -NoTypeInformation -Path $args[1] | Remove-Item -Force
Using below command to run it -
./OlderFiles_Cleansing.ps1 'C:\Dev\PS' 'C:\dev\CleanedFiles_01062016.csv'
What am I missing?
Neither the Export-Csv nor the Remove-Item Cmdlet return the collection you pipe in and so make it impossible to work with the items further in the pipeline.
You can do following though - split the command:
$filesToDelete = Get-ChildItem -Path $path -Recurse -Force -Attributes !Directory | Where-Object CreationTime -lt $limit
$filesToDelete | select Name,LastWriteTime | Export-CSV -NoTypeInformation -Path $args[1]
$filesToDelete | Remove-Item -Force
Note I have improved the way of detecting that an item is a file using the
Attributes param and so could simplify the Where pipe part

Delete X number of files X Old days -Powershell

Get-ChildItem –Path “H:\backups” –Recurse | Where-Object{$_.CreationTime –lt (Get-Date).AddDays(-4)} | Remove-Item
I have this Script that can delete files older than 4 days. However I want to control that it should delete X number of files+folder 4 days old and also want to get the logs so that I see it later.
Below is the script, Is it fine?
$Now = Get-Date
$Days = "4"
$TargetFolder = "H:\backups"
$LastWrite = $Now.AddDays(-$days)
$Files = get-childitem $TargetFolder -include *.* -recurse -force
Where {$_.CreationTime -le "$LastWrite"}
foreach ($i in Get-ChildItem $TargetFolder -recurse)
{
if ($i.CreationTime -lt ($(Get-Date).AddDays(-10)))
{
Remove-Item $Files -recurse -force
}
}
Write-Output $Files >> c:\delete.log
I believe this would work, allowing you to pass a path and a number of days you'd want to keep as parameters:
function RemoveOld($path, $filefilter, $daystokeep)
{
$findfiles = #(Get-ChildItem -Path $path -Include $filefilter)
$toRemove = $findfiles | Where-Object CreationTime -le (Get-Date).AddDays(-$daystokeep)
$toRemove >> c:\temp\delete.log
$toRemove | Remove-Item
}
RemoveOld -path "H:\backups\*" -filefilter "*" -daystokeep 4
May I also suggest, rather than deleting files older than a given time-frame, remove the files older than the most recent X number of files. In this way you'd always keep some number of files and not end up in a situation where all your files are deleted if the backup somehow fails to run for several days. It could be an option depending on how your folder structure is set up (modify the filefilter parameter so you don't delete more than desired).
function RemoveOlder($path, $filefilter, $filestokeep)
{
$findfiles = #(Get-ChildItem -Path $path -Include $filefilter)
if ($findfiles.Count -gt $filestokeep) {
$findfiles | Sort-Object LastWriteTime -Descending | Select-Object -Last ($findfiles.Count - $filestokeep) | Remove-Item
}
}
RemoveOlder -path "H:\backups\*" -filefilter "*" -filestokeep 4
On your original command, add this:
-Verbose 4>&1 | Out-File -FilePath D:\delete.log -Append
4 is Verbose output so this is redirecting Verbose to stdout 1, then redirecting both to a log file. The final command would be:
Get-ChildItem –Path “H:\backups” –Recurse `
| Where-Object{$_.CreationTime –lt (Get-Date).AddDays(-4)} `
| Remove-Item -Verbose 4>&1 | Out-File -FilePath C:\delete.log -Append
Update: If you want the command to run without interaction and continue on errors etc. for a scheduled task, add additional parameters to Remove-Item:
Get-ChildItem –Path “H:\backups” –Recurse `
| Where-Object{$_.CreationTime –lt (Get-Date).AddDays(-4)} `
| Remove-Item -Verbose -Force -ErrorAction SilentlyContinue -Confirm:$false 4>&1 `
| Out-File -FilePath C:\delete.log -Append