86°F Knoxville
Mon–Fri 9–5 ETAnswered by a human
Automation · RESOLVED

Shared PC Out of Disk: Stale User Profiles Eating Storage

Training room PC down to 2GB free. Windows Disk Cleanup found nothing. 47 old user profiles from past employees consuming 180GB.

Incident

Call center rolled in a ticket on their training room PC: "Low Disk Space" warnings popping all day, users couldn't save documents or kick off updates. The box was a 256GB machine with 2GB free. They'd already run Windows Disk Cleanup and clawed back about 500MB — not enough to matter. This was a shared workstation that rotating trainees had been logging into for roughly three years.

Investigation

C:\Users was the whole story. Windows was sitting at a normal 25GB, Program Files at 18GB, but the Users folder had ballooned to 189GB across 47 separate profiles. Every trainee who ever touched this PC had a domain profile on it, and the average was around 4GB each — cached Outlook OST files, Teams data, temp files, browser caches, the usual. Most of those users had either left the company or never came back to this machine after their initial training week.

  • Windows: 25GB (normal)
  • Program Files: 18GB (normal)
  • Users folder: 189GB (problem)
  • profile count: 47

Why not just delete

This is where people get themselves into trouble. You cannot just nuke C:\Users\username folders and call it done. Windows keeps registry entries under HKLM...\ProfileList that point at each profile, and if you delete the folder without cleaning the registry, the next login for that user creates a broken TEMP profile, or worse, corrupts the Default profile. The Settings app's "delete profile" UI shows the profiles but the delete often fails silently on domain accounts. And anyone who's run rmdir /s C:\Users\* in a panic knows it'll happily try to delete Default, Public, and All Users too.

  • manual folder delete: leaves registry orphans, breaks future logins
  • delete C:\Users*: destroys Default profile and system accounts
  • Settings app: often fails silently on domain-joined profiles
  • DelProf2: proper cleanup, handles registry + filesystem together

Solution

DelProf2 by Helge Klein is the industry standard for this job. It's free, it's been around forever, and it handles both the filesystem and the registry side of profile removal. Our script pulls it down automatically, applies an age threshold, and protects the accounts you can't afford to lose.

delprof2_old_profiles_delete.ps1View on GitHub →
$ErrorActionPreference = 'Stop'

<#
██╗     ██╗███╗   ███╗███████╗██╗  ██╗ █████╗ ██╗    ██╗██╗  ██╗
██║     ██║████╗ ████║██╔════╝██║  ██║██╔══██╗██║    ██║██║ ██╔╝
██║     ██║██╔████╔██║█████╗  ███████║███████║██║ █╗ ██║█████╔╝
██║     ██║██║╚██╔╝██║██╔══╝  ██╔══██║██╔══██║██║███╗██║██╔═██╗
███████╗██║██║ ╚═╝ ██║███████╗██║  ██║██║  ██║╚███╔███╔╝██║  ██╗
╚══════╝╚═╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚══╝╚══╝ ╚═╝  ╚═╝
================================================================================
 SCRIPT   : DelProf2 Delete Old Profiles v1.1.0
 AUTHOR   : Limehawk.io
 DATE      : December 2025
 USAGE    : .\delprof2_old_profiles_delete.ps1
================================================================================
 FILE     : delprof2_old_profiles_delete.ps1
 DESCRIPTION : Deletes Windows user profiles older than specified days
--------------------------------------------------------------------------------
 README
--------------------------------------------------------------------------------
 PURPOSE

 Deletes Windows user profiles older than a specified number of days.
 Uses DelProf2 utility downloaded directly from helgeklein.com.

 DATA SOURCES & PRIORITY

 1) SuperOps runtime variables
 2) Direct download from helgeklein.com

 REQUIRED INPUTS (SuperOps Runtime Variables)

 - $days_old : Number of days - profiles older than this are deleted (default: 30)

 SETTINGS

 - Always protects: gaia, administrator profiles
 - Downloads DelProf2 directly (no Chocolatey required)
 - Cleans up after execution

 BEHAVIOR

 1. Downloads/extracts DelProf2 if not cached
 2. Executes: delprof2.exe /u /d:X /ed:gaia /ed:administrator
 3. Cleans up cached files
 4. Reports results

 PREREQUISITES

 - Windows 10/11
 - Admin privileges required
 - Internet access for DelProf2 download

 SECURITY NOTES

 - DESTRUCTIVE OPERATION - profiles cannot be recovered
 - Downloads from official helgeklein.com source

 EXIT CODES

 - 0: Success
 - 1: Failure
--------------------------------------------------------------------------------
 CHANGELOG
--------------------------------------------------------------------------------
 2025-12-23 v1.1.0 Updated to Limehawk Script Framework
 2025-11-29 v1.0.0 Initial release - separated from combined script
================================================================================
#>

# ==== STATE ====
$errorOccurred = $false
$errorText = ""

# ==== SUPEROPS RUNTIME VARIABLES ====
$DaysOld = "$days_old"

# ==== CONSTANTS ====
$ProtectedProfiles = @("gaia", "administrator")
$DelProf2Url = "https://helgeklein.com/downloads/DelProf2/current/Delprof2%201.6.0.zip"
$DelProf2CacheDir = Join-Path $env:TEMP "delprof2_cache"

# ==== APPLY DEFAULTS ====

Set-StrictMode -Version Latest

if ([string]::IsNullOrWhiteSpace($DaysOld) -or $DaysOld -eq '$days_old') {
    $DaysOld = "30"
}

$DaysOldInt = 0
if (-not [int]::TryParse($DaysOld, [ref]$DaysOldInt)) {
    $DaysOldInt = 30
}

# ==== VALIDATION ====
if ($DaysOldInt -lt 1) {
    $errorOccurred = $true
    $errorText = "- DaysOld must be at least 1."
}

if ($errorOccurred) {
    Write-Host ""
    Write-Host "[ ERROR OCCURRED ]"
    Write-Host "--------------------------------------------------------------"
    Write-Host $errorText
    exit 1
}

# ==== RUNTIME OUTPUT ====
Write-Host ""
Write-Host "[ INPUT VALIDATION ]"
Write-Host "--------------------------------------------------------------"
Write-Host "Days Old  : $DaysOldInt"
Write-Host "Protected : $($ProtectedProfiles -join ', ')"

Write-Host ""
Write-Host "[ OPERATION ]"
Write-Host "--------------------------------------------------------------"

try {
    # Ensure cache directory exists
    if (-not (Test-Path $DelProf2CacheDir)) {
        New-Item -Path $DelProf2CacheDir -ItemType Directory -Force | Out-Null
    }

    $delprof2Exe = Join-Path $DelProf2CacheDir "DelProf2.exe"

    # Download and extract DelProf2 if not present
    if (-not (Test-Path $delprof2Exe)) {
        Write-Host "Downloading DelProf2..."
        $zipPath = Join-Path $DelProf2CacheDir "DelProf2.zip"
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $webClient = New-Object System.Net.WebClient
        $webClient.DownloadFile($DelProf2Url, $zipPath)
        $webClient.Dispose()

        Write-Host "Extracting..."
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $DelProf2CacheDir)
        Remove-Item $zipPath -Force -ErrorAction SilentlyContinue

        if (-not (Test-Path $delprof2Exe)) {
            throw "DelProf2.exe not found after extraction"
        }
        Write-Host "DelProf2 ready"
    } else {
        Write-Host "Using cached DelProf2"
    }

    # Build arguments
    $delprof2Args = @("/u", "/d:$DaysOldInt")
    foreach ($profile in $ProtectedProfiles) {
        $delprof2Args += "/ed:$profile"
    }

    Write-Host "Executing: DelProf2.exe $($delprof2Args -join ' ')"

    # Execute
    $result = & $delprof2Exe $delprof2Args 2>&1
    Write-Host $result

    # Cleanup
    Write-Host "Cleaning up..."
    Remove-Item $DelProf2CacheDir -Recurse -Force -ErrorAction SilentlyContinue

} catch {
    $errorOccurred = $true
    $errorText = $_.Exception.Message
}

if ($errorOccurred) {
    Write-Host ""
    Write-Host "[ ERROR OCCURRED ]"
    Write-Host "--------------------------------------------------------------"
    Write-Host $errorText
}

Write-Host ""
Write-Host "[ RESULT ]"
Write-Host "--------------------------------------------------------------"
if ($errorOccurred) {
    Write-Host "Status : Failure"
} else {
    Write-Host "Status : Success"
    Write-Host "Profiles older than $DaysOldInt days deleted."
}

Write-Host ""
Write-Host "[ SCRIPT COMPLETED ]"
Write-Host "--------------------------------------------------------------"

if ($errorOccurred) { exit 1 } else { exit 0 }

Safety features

Age threshold means we only touch profiles older than a chosen number of days — we run at 30 days so nobody active gets clipped. The /ed: exclude flag protects admin and service accounts by name. DelProf2 also skips any profile that's currently logged in, so you're not going to yank the floor out from under someone using the machine. Registry ProfileList entries get cleaned the right way, so future logins still work.

  • age threshold: only profiles older than X days
  • protected list: excludes admin and service accounts by name
  • registry cleanup: removes ProfileList entries properly
  • logged-in users: skipped automatically

Outcome

Ran with the 30-day threshold. Of the 47 profiles on the box, 44 were eligible and got removed. Kept three: the local admin, the current trainer, and our IT support account. Total recovery was 176GB, which put the machine back at 178GB free (69% of the drive). Whole thing finished in under two minutes. We scheduled a monthly DelProf2 run through the RMM so this won't creep back, and rolled the same policy out to the other eight shared workstations on that site.

  • profiles removed: 44 of 47
  • space recovered: 176GB
  • free space after: 178GB (69%)
  • time to run: under 2 minutes

Key takeaways