Building a Windows Storage Dashboard, Part 1Building a Windows Storage Dashboard, Part 1

Create a powerful monitoring solution that transforms PowerShell from a command-line tool into a visual dashboard for tracking disk usage, SMART data, and storage trends.

Brien Posey, Technology Analyst

June 20, 2025

7 Min Read
Windows storage dashboard

[Editor's Note: This is Part 1 of a three-part series on building a Windows storage dashboard.]

Windows PowerShell is probably best known as a command-line tool for automating Windows tasks and for monitoring key system processes. However, you can leverage PowerShell's various capabilities to build a dashboard that helps you keep tabs on system storage. In fact, I have created a script to do exactly that. This particular script is designed to run locally but can be easily extended to monitor remote systems.

In this article, I will show you how to gather and display some basic storage metrics. In Part 2, I showed you a technique for gathering historical data that's related to your storage. In Part 3, I will wrap things up by showing you how to incorporate that historical data into the dashboard that I am going to be creating in this article.

Getting Started

So let's get started. My initial dashboard works by querying the system to see which hard disks are installed. The dashboard displays a bar chart displaying each disk's total capacity, as well as how much space has been used so far. The dashboard also contains a drop-down menu where you can select any of the disks within your system.

Upon making your selection, the dashboard will display the SMART (Self-Monitoring, Analysis, and Reporting Technology) information for the disk, as well as the five largest files. As previously noted, I will eventually be extending this dashboard to include historical storage consumption trends, but Figure 1 shows what the dashboard looks like right now.

Related:Building a Windows Storage Dashboard, Part 2: Collecting Historical Data

nitial version of Windows storage dashboard

Figure 1: This is the initial version of the storage dashboard.

Key Features of the Dashboard

The script is relatively long, so I won't go through it line by line. However, I do want to point out some of the key elements. I will provide you with the full code at the end of this article.

Initially, the script creates all of the various GUI objects that appear on screen. The first "real" task that the script performs is to create a list of the disks that are installed in the system. The list of disks is filtered so that only local drives are included, and the results are stored in a variable called $LogicalDrives. The line of code that retrieves the list of disks is:

$LogicalDrives = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3"

The next thing that the script does is to create an array called $DriveMap. I used a ForEach loop to populate the $DriveMap array with information about each of the disks listed within the $LogicalDrives variable. For each of these disks, I am determining the disk size, free space, and the space that has been used. These values are stored as gigabytes and rounded to two decimal places. The values are stored in $SizeGB, $FreeGB, and $UsedGB. As a part of this ForEach loop, I am also adding the used space and free space to the chart, and I am adding the device ID (the drive letter) to the drop-down menu.

Related:Popular Tips and Tutorials for Windows System Administrators in 2025

When you select a disk from the drop-down menu, the script computes the size, free space, and used space of that disk, in a manner that is nearly identical to what was used before. This information is written to a summary label that causes some basic usage data to be displayed. Here is the block of code that handles this task:

$CurrentDiskDeviceID = $Drive.DeviceID
    $CurrentDiskSizeGB = [math]::Round($Drive.Size / 1GB, 2)
    $CurrentDiskFreeGB = [math]::Round($Drive.FreeSpace / 1GB, 2)
    $CurrentDiskUsedGB = [math]::Round($SizeGB - $FreeGB, 2)
    $SummaryLabel.Text = "Summary Info for $CurrentDiskDeviceID : Total Size: $CurrentDiskSizeGB GB, Free Space: $CurrentDiskFreeGB GB, Used: $CurrentDiskUsedGB GB"

The next thing that the script does is to compile a list of the five largest files on the selected disk. It does this by recursively running the Get-ChildItem cmdlet. The results are then sorted in descending order and filtered so that only the first five results are displayed. The resulting files and their sizes are displayed within the $TopFilesOutput text box. Here is the code that compiles the list of the five largest files:

       $Files = Get-ChildItem -Path "$SelectedDrive\" -Recurse -File -ErrorAction SilentlyContinue |
                 Sort-Object Length -Descending |
                 Select-Object FullName, @{n="SizeMB";e={[math]::Round($_.Length / 1MB, 2)}} -First 5
        ForEach ($File in $Files) {
                $TopFilesOutput.Text += "$($File.FullName)`n" + [Environment]::NewLine + "Size: $($File.SizeMB) MB" + [Environment]::NewLine + [Environment]::NewLine
        }

The last thing that the script does is to retrieve the SMART status for the selected disk. This is handled by a function called Get-SmartStatus, which performs several Get-CimInstance queries. The results are written to the $SmartInfoOutput text box.

Full Source Code for Script

In Part 2 of this series, I showed you how to go about compiling historical disk consumption data. For now though, here is the full source code for the script that I have described in this article:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms.DataVisualization
Add-Type -AssemblyName System.Drawing

# Create Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Disk Usage Monitor with SMART Info"
$Form.Width = 1920
$Form.Height = 1080

# Chart setup
$Chart = New-Object System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Location = New-Object System.Drawing.Point(20, 20)
$Chart.Size = New-Object System.Drawing.Size(1000,400)

$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$Chart.ChartAreas.Add($ChartArea) | Out-Null

# Series for Used Space
$UsedSeries = New-Object System.Windows.Forms.DataVisualization.Charting.Series
$UsedSeries.Name = "UsedSpace"
$UsedSeries.ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::StackedBar
$UsedSeries.Color = [System.Drawing.Color]::LightBlue
$Chart.Series.Add($UsedSeries) | Out-Null

# Series for Free Space
$FreeSeries = New-Object System.Windows.Forms.DataVisualization.Charting.Series
$FreeSeries.Name = "FreeSpace"
$FreeSeries.ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::StackedBar
$FreeSeries.Color = [System.Drawing.Color]::LightGreen
$Chart.Series.Add($FreeSeries) | Out-Null

$Form.Controls.Add($Chart) | Out-Null

$SelectDiskLabel = New-Object System.Windows.Forms.Label
$SelectDiskLabel.AutoSize = $True
$SelectDiskLabel.Location = New-Object System.Drawing.Point(1050, 20)
$SelectDiskLabel.Font = New-Object System.Drawing.Font("Arial", 14)
$SelectDiskLabel.Text = "Select a Disk"
$Form.Controls.Add($SelectDiskLabel) | Out-Null

# ComboBox for selecting drives
$ComboBox = New-Object System.Windows.Forms.ComboBox
$ComboBox.Location = New-Object System.Drawing.Point(1050, 50)
$ComboBox.Font = New-Object System.Drawing.Font("Arial", 14)
$ComboBox.Width = 300
$Form.Controls.Add($ComboBox) | Out-Null

# Label for top files
$FilesLabel = New-Object System.Windows.Forms.Label
$FilesLabel.AutoSize = $True
$FilesLabel.Location = New-Object System.Drawing.Point(1050, 100)
$FilesLabel.Font = New-Object System.Drawing.Font("Arial", 14)
$FilesLabel.Text = "Top 5 Largest Files:"
$Form.Controls.Add($FilesLabel) | Out-Null

# Label for Summary Info
$SummaryLabel = New-Object System.Windows.Forms.Label
$SummaryLabel.AutoSize = $True
$SummaryLabel.Location = New-Object System.Drawing.Point(20, 450)
$SummaryLabel.Font = New-Object System.Drawing.Font("Arial", 14)
$Form.Controls.Add($SummaryLabel) | Out-Null

# Label for SMART Info
$SmartLabel = New-Object System.Windows.Forms.Label
$SmartLabel.AutoSize = $True
$SmartLabel.Location = New-Object System.Drawing.Point(20, 500)
$SmartLabel.Font = New-Object System.Drawing.Font("Arial", 14)
$SmartLabel.Text = "SMART Info:"
$Form.Controls.Add($SmartLabel) | Out-Null



$TopFilesOutput = New-Object System.Windows.Forms.TextBox
$TopFilesOutput.Multiline = $True
$TopFilesOutput.Location = New-Object System.Drawing.Point(1050, 150)
$TopFilesOutput.Size = New-Object System.Drawing.Size(850,400)
$TopFilesOutput.ReadOnly = $True
$TopFilesOutput.Font = New-Object System.Drawing.Font("Arial", 14)
$TopFilesOutput.ScrollBars = "Vertical"
$Form.Controls.Add($TopFilesOutput) | Out-Null


$SmartInfoOutput = New-Object System.Windows.Forms.TextBox
$SmartInfoOutput.Multiline = $True
$SmartInfoOutput.Location = New-Object System.Drawing.Point(20, 550)
$SmartInfoOutput.Size = New-Object System.Drawing.Size(1000,200)
$SmartInfoOutput.ReadOnly = $True
$SmartInfoOutput.Font = New-Object System.Drawing.Font("Arial", 14)
$SmartInfoOutput.ScrollBars = "Vertical"
$Form.Controls.Add($SmartInfoOutput) | Out-Null

# Populate chart and ComboBox
$LogicalDrives = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3"
$DriveMap = @{}

ForEach ($Drive in $LogicalDrives) {
    $DeviceID = $Drive.DeviceID
    $SizeGB = [math]::Round($Drive.Size / 1GB, 2)
    $FreeGB = [math]::Round($Drive.FreeSpace / 1GB, 2)
    $UsedGB = [math]::Round($SizeGB - $FreeGB, 2)

    # Add to chart
    $UsedPoint = $UsedSeries.Points.Add($UsedGB)
    $UsedPoint.AxisLabel = "$DeviceID"
    $UsedPoint.ToolTip = "$DeviceID Used: $UsedGB GB"

    $FreePoint = $FreeSeries.Points.Add($freeGB)
    $FreePoint.ToolTip = "$DeviceID Free: $FreeGB GB"

    # Add to ComboBox
    $ComboBox.Items.Add($DeviceID)
    $DriveMap[$deviceID] = $Drive
}

# SMART info helper
Function Get-SMARTStatus {
    Param ($DriveLetter)

    # Match partition to disk
    $Partition = Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk.DeviceID='$driveLetter'} WHERE AssocClass=Win32_LogicalDiskToPartition"
    $Disk = Get-CimInstance -Query "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='$($partition.DeviceID)'} WHERE AssocClass=Win32_DiskDriveToDiskPartition"

    If ($Disk) {
        Return @"
Model: $($Disk.Model)
Serial: $($Disk.SerialNumber)
Interface: $($Disk.InterfaceType)
Status: $($Disk.Status)
"@
    } Else {
        Return "SMART info is not available for $driveLetter."
    }
}

# Handler when drive selected
$ComboBox.Add_SelectedIndexChanged({
    $SelectedDrive = $ComboBox.SelectedItem
    if ($selectedDrive) {


    $CurrentDiskDeviceID = $Drive.DeviceID
    $CurrentDiskSizeGB = [math]::Round($Drive.Size / 1GB, 2)
    $CurrentDiskFreeGB = [math]::Round($Drive.FreeSpace / 1GB, 2)
    $CurrentDiskUsedGB = [math]::Round($SizeGB - $FreeGB, 2)
    $SummaryLabel.Text = "Summary Info for $CurrentDiskDeviceID : Total Size: $CurrentDiskSizeGB GB, Free Space: $CurrentDiskFreeGB GB, Used: $CurrentDiskUsedGB GB"

        # Top files
        $FilesLabel.Text = "Top 5 Largest Files:`n"
        $Files = Get-ChildItem -Path "$SelectedDrive\" -Recurse -File -ErrorAction SilentlyContinue |
                 Sort-Object Length -Descending |
                 Select-Object FullName, @{n="SizeMB";e={[math]::Round($_.Length / 1MB, 2)}} -First 5
        ForEach ($File in $Files) {
                $TopFilesOutput.Text += "$($File.FullName)`n" + [Environment]::NewLine + "Size: $($File.SizeMB) MB" + [Environment]::NewLine + [Environment]::NewLine
        }

        # SMART info
       $SmartInfoOutput.Text = (Get-SMARTStatus -DriveLetter $SelectedDrive)
    }
})

# Show the form
$Form.ShowDialog() | Out-Null

About the Author

Brien Posey

Technology Analyst

Brien Posey is a bestselling technology author, a speaker, and a 20X Microsoft MVP. In addition to his ongoing work in IT, Posey has spent the last several years training as a commercial astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space.

https://brienposey.com/

You May Also Like