File Share Bloat: Clean Up the Stale Stuff

There’s a bad practice among on-prem orgs where they offer their end-users a dumping ground for files that need to be shared. Orgs that haven’t rolled out SharePoint Online or OneDrive, consist of stubborn end-users who insist on using file shares.

While SMB Shares are being phased out (the future is now, old man), there’s still a need to support it. Without proper internal best practices, a SMB share can turn into the lawless Wild West, and we’re going to be the only sheriff around these parts with this script.

The this script is derived from a request I received: Clean up a shared folder used for temporary file storage; delete anything that still lives here after 30 days.

This, in conjunction with internal policy, has somewhat helped tame the lawless wasteland that is my file share.

Review the breakdown below, or just opt to use the full script here on GitHub.

The Script

We’re going to handle this recursively. First delete the files older than 30 days, then cleanup any orphaned folders.

First, let’s start with my favorite function: Write-Log. This comes from early on in my scripting days, and is just something I’ve copy pasta’d into almost every script. Logging is important! How else are we going to know our script is working?

   function Write-Log {
        param(
            [string]$Message,
            [string]$Level = "Info",
            [string]$LogFile = "\\Path\FileDeletionLog - $(Get-Date -format yyyy-MM-dd).log"
        )
        $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $LogEntry = "$Timestamp [$Level] $Message"
        Add-Content -Path $LogFile -Value $LogEntry -Force

    }

Next, let’s define our targeted share and create an array of potential exclusions because there’s always an exception when it comes to end-users. Say I want to delete everything except items in folder \\Share\ABC\XYZ; these exclusions can be full folder paths or individual files. Ensure you are using UNC paths for on-prem setups, do not shorthand the drive letter!

$folderpath = "\\SHARE\"

$exclusions = @(
    <#enter exclusion paths here. example: \\share\folder or \\share\folder\file #>
)

Let’s now man-handle our variables. First we’ll normalize all of the exclusion paths for consistency, then we’ll actually exclude the individual files.

$exclusions = $exclusions | ForEach-Object { $_.ToLower() }

$files = Get-ChildItem -Path $folderpath -Recurse -Force | Where-Object {
    $currentPath = $_.FullName.ToLower()
    -not ($exclusions | Where-Object { $currentPath -like "$_*" })
}

Now to pull the trigger on deletion. We’re going to run a foreach loop for the files captured by our variable, check to see if the LastWriteTime property is older than 30 days then delete each file. As always, test! test! test! Then remove the -WhatIf switch.

foreach($file in $files){
    If ($file.LastWriteTime -lt (Get-Date).AddDays(-30)){
        Remove-Item $file.FullName  -Recurse -WhatIf ### Remove -WhatIF when ready to deploy
        Write-Host $file
        Start-Sleep -Seconds 1 
        $output = "Deleting file: " + $file.FullName
        Write-Log -Message $output  
    }
}

If we stop now we’ll be left with a bunch of empty folders, which will just cause confusion amongst our end-users. Let’s blitz those out!

$folders = Get-ChildItem -Path $folderpath -Force -Recurse -Directory| Where-Object { (Get-ChildItem -Path $_.FullName -Recurse -File | Measure-Object).Count -eq 0 }
foreach($folder in $folders){
    Remove-Item $folder.FullName -Recurse -Force -ErrorAction SilentlyContinue -WhatIf ### Remove -WhatIF when ready to deploy
    Start-Sleep -Seconds 1 
    $output = "deleting folder: " + $folder.FullName
    Write-Log -Message $output
}

Slap all of these pieces together or plug-and-play with your own method. I recommend setting this up on a schedule via Task Scheduler, or be my guest and run it manually.

Just remember, as always,: This script is provided “as is” without any warranty of any kind, express or implied. Use it at your own risk. The authors and contributors are not responsible for any damage, data loss, or other issues that may arise from using this software. You are solely responsible for any actions taken based on this code.