Shore Up
A storage tank fitted with a fuel-style gauge whose needle has dropped near empty, and a warning envelope flying out of it toward a small mailbox.
Windows

Monitor Free Disk Space and Email an Alert with PowerShell

Ketan Aagja8 min read
No ratings yet

Before you run this

This script reads the free space on the fixed local disks of the machine it runs on and emails you when any of them drops below a percentage you set. It is a lightweight stand-in for a monitoring platform — good for a handful of servers, not a replacement for one across a fleet.

Privileges: reading disk space with Get-CimInstance Win32_LogicalDisk works fine as an ordinary user, so the script itself needs no elevation. Registering it as a scheduled task, and running it as SYSTEM or a service account, does need an elevated (Administrator) PowerShell session.

It changes nothing on disk. It only reads counters and sends mail. There is nothing destructive here and nothing to reverse except the scheduled task you create at the end, which I show how to remove.

Test it first. Read the script before you run it. Run it once by hand on a test or non-production server with the -Test switch (below), which forces a report email for every disk regardless of threshold, so you can confirm the mail path works before you rely on it. Don't schedule it until you've seen one message land in the inbox.

One honest caveat about mail: this guide uses Send-MailMessage, which is the well-established built-in way and still works in PowerShell 5.1 and 7. Microsoft marks it obsolete because it can't guarantee a secure connection to modern mail services, and does not plan to improve it. For internal relays it is fine. If you must send through a hardened or cloud provider, the current path is the Microsoft.Graph module or a library such as MailKit — I'm naming those rather than walking through them.

Assumptions

  • Windows Server 2019 or 2022, Windows PowerShell 5.1 (the version shipped in the box).
  • An internal SMTP relay (smtp.example.com) that accepts mail from this server's IP without authentication — the common setup for internal alerting. I note where to add credentials if yours requires them.
  • You want a daily check. Adjust the trigger for hourly if you prefer.

The script

Save this as C:\Scripts\disk-space-alert.ps1. Edit the four mail settings at the top — they are obvious placeholders; replace example.com, the from/to addresses, and the relay name with your own.

#Requires -Version 5.1

# disk-space-alert.ps1
# Emails an alert for any fixed local disk below a free-space threshold.

param(
    [int]$ThresholdPercent = 15,   # alert when free space falls below this %
    [switch]$Test                  # report ALL disks and always send (for testing)
)

# --- EDIT THESE ---
$smtpServer = "smtp.example.com"        # your internal relay
$smtpPort   = 25
$mailFrom   = "diskalert@example.com"
$mailTo     = "sysadmin@example.com"
# ------------------

# DriveType 3 = fixed local disk (excludes network, removable, and optical drives)
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3"

$report = foreach ($d in $disks) {
    if (-not $d.Size -or $d.Size -eq 0) { continue }   # skip disks with no reported size
    $freePct = [math]::Round(($d.FreeSpace / $d.Size) * 100, 1)
    [pscustomobject]@{
        Drive   = $d.DeviceID
        FreeGB  = [math]::Round($d.FreeSpace / 1GB, 1)
        SizeGB  = [math]::Round($d.Size / 1GB, 1)
        FreePct = $freePct
    }
}

# Which disks trip the alert
if ($Test) {
    $low = $report
} else {
    $low = $report | Where-Object { $_.FreePct -lt $ThresholdPercent }
}

if (-not $low) {
    Write-Host "All fixed disks above $ThresholdPercent% free. No mail sent."
    return
}

$hostName = $env:COMPUTERNAME
$body = ($low | Format-Table -AutoSize | Out-String)
$subject = "Low disk space on $hostName"

Send-MailMessage -SmtpServer $smtpServer -Port $smtpPort `
    -From $mailFrom -To $mailTo `
    -Subject $subject -Body $body

Two lines worth explaining: the DriveType=3 filter keeps this to real fixed disks, so a mounted network share or an empty DVD drive never triggers a false alert; and the $d.Size -eq 0 guard avoids a divide-by-zero on any disk that reports no size.

If your relay needs authentication or TLS

Send-MailMessage accepts -UseSsl and -Credential. Don't hard-code a password in the script. Store the credential once, as the account that will run the task, with DPAPI-protected XML:

# Run once, as the account the scheduled task will use
Get-Credential | Export-Clixml -Path C:\Scripts\smtp-cred.xml

Then near the top of the script, load it and pass it in:

$cred = Import-Clixml -Path C:\Scripts\smtp-cred.xml
# ...and add to the Send-MailMessage call:
#   -UseSsl -Credential $cred

Note the DPAPI catch: Export-Clixml encrypts for the user and machine that created it. A file exported by your admin account cannot be read by SYSTEM. If you go the credential route, run the export as the exact service account the task uses (see scheduling below), not as yourself.

Test it by hand

Before scheduling anything, force a message:

C:\Scripts\disk-space-alert.ps1 -Test

With -Test it reports every disk and always sends, so you get an email even on a healthy server. Confirm it arrives and reads sensibly. Then try a realistic threshold — pick a number a real disk currently sits below to prove the filter fires, e.g. -ThresholdPercent 99.

Schedule it

From an elevated PowerShell session, register a daily task. Running as SYSTEM is the simplest choice when the relay needs no auth:

$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\disk-space-alert.ps1"

$trigger = New-ScheduledTaskTrigger -Daily -At 7:00am

Register-ScheduledTask -TaskName "Disk Space Alert" `
    -Action $action -Trigger $trigger `
    -User "SYSTEM" -RunLevel Highest `
    -Description "Emails an alert when a fixed disk drops below the free-space threshold."

If you stored SMTP credentials, run the task as that specific service account instead of SYSTEM — add -User "DOMAIN\svc-diskalert" and you'll be prompted, or supply -Password. Check Microsoft Learn for Register-ScheduledTask for the exact credential parameters, as they differ for gMSA and standard accounts.

-Argument above uses -ExecutionPolicy Bypass for this one invocation only; it does not change the machine's policy.

Verify it worked

Confirm the task exists and check when it last ran and with what result:

Get-ScheduledTask -TaskName "Disk Space Alert"
Get-ScheduledTaskInfo -TaskName "Disk Space Alert"

Get-ScheduledTaskInfo shows LastRunTime and LastTaskResult — a result of 0 means the task exited cleanly. To force a real run and watch it end to end:

Start-ScheduledTask -TaskName "Disk Space Alert"

Then check the mailbox. If nothing arrives, run the script by hand with -Test again in the foreground and read any error from Send-MailMessage — that separates a mail problem from a scheduling problem.

Undo

To remove the schedule cleanly:

Unregister-ScheduledTask -TaskName "Disk Space Alert" -Confirm:$false

Then delete the files you created — C:\Scripts\disk-space-alert.ps1 and, if you made one, C:\Scripts\smtp-cred.xml. Since the script only reads counters and sends mail, there is nothing else to roll back; removing the task and the files returns the server to exactly where it started.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.

Email a Daily Windows Event Log Error Summary with PowerShell

This guide builds a small PowerShell script that reads the last 24 hours of Critical and Error events from the System and Application logs, writes them to an HTML file, and emails that file to you. It's a read-only report: it does not clear, modify, or delete any event log. The only things it creates on the system are the HTML report files in a folder you choose and — in the last section — a scheduled task.

10 min read

Generate a Disk-Space Report Across Servers with PowerShell Remoting

This guide builds a read-only disk-space report. It uses PowerShell remoting ( Invoke-Command over WinRM) to query each server's local fixed disks and returns size, free space, and percent free as one combined table you can export to CSV or HTML. It does not write to, resize, or clean up any disk — it only reads WMI/CIM data.

10 min read

Monitor a Windows Service and Auto-Restart It with PowerShell

This guide builds a small watchdog: a PowerShell script that checks one named Windows service, and if it is not running, tries to start it and writes the outcome to a log file and the Windows event log. You then schedule it with Task Scheduler so it runs every few minutes. The script starts a stopped service — that is its whole point — so run it only against a service you actually want kept running. It does not delete or reconfigure anything, and starting a service is reversible (you can stop it again), so there is no destructive, one-way step here.

10 min read

Automatically Remove Stale User Profiles on an RDS Host

On a busy Remote Desktop Session Host, local profiles pile up fast — every user who ever logged in leaves a folder under C:\Users , and a system drive fills quietly until logons start failing. This guide sets up an unattended, standard-supported cleanup of profiles that haven't been used in N days.

8 min read