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.