Shore Up
A single clipboard collecting fuel-gauge readings from a row of storage tanks, each gauge showing how full it is, gathered into one summary sheet.
Windows

Generate a Disk-Space Report Across Servers with PowerShell Remoting

Ketan Aagja10 min read
No ratings yet

Before you run this

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.

  • Privileges: The report query itself needs an account with local administrator rights on the target servers (querying Win32_LogicalDisk remotely does). In a domain, that's typically a domain account in each server's local Administrators group. Run your PowerShell session as that account, or pass a credential with -Credential. You do not need to run the console "as Administrator" on your workstation just to query, but the account you connect with must be admin on the targets.
  • The one configuration change here is enabling WinRM on any target that doesn't already have it (Enable-PSRemoting). That is a real change to the server — it starts the WinRM service, sets it to automatic, and creates a firewall rule. On domain-joined servers WinRM is very often already on via Group Policy. Only enable it where it's off, and know how to reverse it (shown at the end).
  • Test first: Read the script before running it. Run it against one non-production server first, confirm the numbers match what you see in that server's Disk Management or Get-Volume, then widen the scope. Don't point it at your whole fleet on the first run.
  • Not destructive: Nothing here deletes or edits data. The only thing you may need to undo is having enabled remoting on a host that shouldn't have it.

What I'm assuming

  • Windows Server 2019/2022 targets, domain-joined.
  • You run this from a domain-joined Windows 10/11 or Server workstation using Windows PowerShell 5.1 (the default powershell.exe). It works the same in PowerShell 7, but 5.1 is what ships everywhere.
  • WinRM/PSRemoting is available on the targets (default for domain servers with the standard remote-management GPO). If not, see the prerequisite step.
  • You're inside the same AD domain and forest, so Kerberos handles authentication.

If your servers are standalone/workgroup, cross-domain, or you connect by IP, WinRM authentication gets more involved (TrustedHosts, HTTPS listeners). That's a different setup — see Microsoft Learn, "about_Remote_Troubleshooting."

Prerequisite: confirm remoting works

Before the report, prove you can reach a target. From your workstation:

# Replace with a real server name
Test-WSMan -ComputerName server01.example.com

If that returns product/protocol version info, WinRM is answering. Then confirm you can actually run a command:

Invoke-Command -ComputerName server01.example.com -ScriptBlock { $env:COMPUTERNAME }

If WinRM is not enabled on a target, enable it on that server (locally or via a management tool), in a maintenance window:

# Run this ON the target server, in an elevated PowerShell session
Enable-PSRemoting -Force

-Force skips the confirmation prompts; it starts the WinRM service, sets it to automatic start, and adds the inbound firewall rule for the current network profiles. Don't run it blindly across the fleet — enable only where Test-WSMan failed and you've decided that host should accept remoting.

Build your server list

Keep the list in a plain text file, one hostname per line — easy to edit and to reuse in scheduled tasks:

server01.example.com
server02.example.com
server03.example.com

Save it as C:\path\to\servers.txt (substitute your own path). If you'd rather pull live from AD, you can generate the list with the ActiveDirectory module (Get-ADComputer), but a curated text file is the boring, reliable choice and won't accidentally sweep in a decommissioned box.

The report script

Save this as Get-DiskReport.ps1. It reads the server list, queries each host's fixed disks in parallel, calculates free space, and flags anything under a threshold.

# --- Settings you edit ---
$ServerListPath = 'C:\path\to\servers.txt'      # your server list
$ReportFolder   = 'C:\path\to\reports'          # where output lands
$LowFreePercent = 15                            # flag disks below this % free
# -------------------------

$servers = Get-Content -Path $ServerListPath | Where-Object { $_ -and $_.Trim() }

# This block runs ON each remote server. DriveType 3 = local fixed disk.
$diskBlock = {
    Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' |
        Select-Object DeviceID, VolumeName, Size, FreeSpace
}

# Invoke-Command runs the block on all servers at once and tags each
# result with the source computer name in PSComputerName.
$raw = Invoke-Command -ComputerName $servers -ScriptBlock $diskBlock -ErrorAction SilentlyContinue

$report = $raw | ForEach-Object {
    $sizeGB = [math]::Round($_.Size / 1GB, 1)
    $freeGB = [math]::Round($_.FreeSpace / 1GB, 1)
    # Guard against divide-by-zero on odd/empty volumes
    $pctFree = if ($_.Size -gt 0) { [math]::Round(($_.FreeSpace / $_.Size) * 100, 1) } else { 0 }

    [PSCustomObject]@{
        Server     = $_.PSComputerName
        Drive      = $_.DeviceID
        Label      = $_.VolumeName
        SizeGB     = $sizeGB
        FreeGB     = $freeGB
        PctFree    = $pctFree
        LowSpace   = ($pctFree -lt $LowFreePercent)
    }
} | Sort-Object PctFree

# Show worst-first in the console
$report | Format-Table -AutoSize

# Write timestamped CSV and HTML
if (-not (Test-Path $ReportFolder)) { New-Item -Path $ReportFolder -ItemType Directory | Out-Null }
$stamp = Get-Date -Format 'yyyy-MM-dd_HHmm'

$report | Export-Csv -Path (Join-Path $ReportFolder "disk-report_$stamp.csv") -NoTypeInformation
$report | ConvertTo-Html -Title 'Disk Space Report' |
    Out-File -FilePath (Join-Path $ReportFolder "disk-report_$stamp.html") -Encoding utf8

A few notes on the choices:

  • Win32_LogicalDisk with DriveType=3 returns only local fixed disks, skipping CDs, network drives, and removable media. Get-CimInstance is the current, WSMan-based way to read WMI — I'm not using the deprecated Get-WmiObject.
  • -ErrorAction SilentlyContinue on Invoke-Command means one unreachable server won't kill the whole run. That's a trade-off: unreachable hosts silently drop out. To see which failed, keep the error stream — see verification below.
  • The size math uses PowerShell's built-in 1GB constant (1073741824 bytes), so SizeGB/FreeGB are binary gigabytes, matching what Explorer shows.

Run it:

# If your logged-in account is admin on the targets:
.\Get-DiskReport.ps1

# If you need a different admin credential, wire -Credential into the
# Invoke-Command call in the script (Get-Credential prompts securely).

Verify it worked

  1. Check the console table printed by Format-Table — one row per fixed disk per server, sorted with the fullest disks (lowest PctFree) at the top.

  2. Confirm the files exist:

    Get-ChildItem C:\path\to\reports\disk-report_*.* | Sort-Object LastWriteTime -Descending | Select-Object -First 2
    
  3. Spot-check one server's numbers. On that server directly, run Get-Volume (or open Disk Management) and confirm the free-space figure matches the report row. If they line up, trust the rest.

  4. Find servers that didn't answer. Compare the servers in your list against the Server column in the report:

    $reported = $report.Server | Sort-Object -Unique
    Compare-Object $servers $reported
    

    Anything showing only on the input side didn't respond — investigate WinRM or credentials on those hosts with the Test-WSMan check above.

Scheduling it

To run daily, register a scheduled task under a service account that's a local admin on the targets (Task Scheduler, or Register-ScheduledTask). Point the action at:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\path\to\Get-DiskReport.ps1"

Store the service account credential in the task itself so it runs unattended. From there you can add an email step (Send-MailMessage is deprecated — use your mail platform's supported method or an SMTP client) to deliver the HTML report.

Undo

The report itself makes no changes to undo. The only reversible change is having enabled remoting. If you turned on WinRM on a host purely for this and want it off again, run this on that server in an elevated session:

# Stops and disables the WinRM listener created by Enable-PSRemoting
Disable-PSRemoting -Force

Note that Disable-PSRemoting disables the session configurations and remote access but does not stop the WinRM service or remove the firewall rule it created — if you need a full teardown to the prior state, stop/disable the WinRM service and remove the firewall rule you added, and check the "Enable-PSRemoting" and "Disable-PSRemoting" pages on Microsoft Learn for the exact residual steps for your build. On servers where remoting was already enabled by policy, leave it alone.

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.

Monitor Free Disk Space and Email an Alert with PowerShell

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.

8 min read

Daily Group Policy Failure Report from the Event Logs

This guide builds a scheduled PowerShell script that reads the Group Policy operational event log, collects the error events from the last 24 hours, and writes them to an HTML file (optionally emailed). It is a reporting tool: it never edits, links, or unlinks a GPO, and it makes no change to any policy or to the events themselves. The only thing it creates on the system is a report file and — if you follow the last section — a scheduled task.

9 min read

Automate Reporting of Expiring IIS Certificates Across a Farm

This guide builds a read-only report. The script reaches each IIS server in your farm over PowerShell remoting, reads the HTTPS (SSL) bindings, looks up the bound certificate in the local machine store, and emails you a list of anything expiring within a threshold you set. It does not install, bind, delete, or renew any certificate, and it makes no change to IIS or the certificate store. The only thing that persists is a scheduled task, if you choose to create one — and I show you how to remove that at the end.

8 min read

A Daily Report of Failed RDP Logons Across Your Servers

This guide builds a read-only PowerShell script that pulls failed logon events (Security log Event ID 4625 ) from a list of servers, keeps the ones that look like RDP attempts, and writes them to a dated HTML/CSV report you can review each morning. It optionally emails that report. It creates nothing and deletes nothing on the target servers — it only reads their Security logs.

10 min read