Automating VHDX Cleanup : A Simple PowerShell Script to Reclaim Disk Space on RDS Environments

In many Remote Desktop Services (RDS) deployments, user profiles are stored in VHD/VHDX files to enable User Profile Disks (UPD) or FSLogix. While this approach streamlines profile management, it often introduces a common challenge: disk space bloat. Users store large files, forget to empty the Recycle Bin, and rarely clear their Downloads folder.

Over time, these profile containers grow significantly, consuming large amounts of disk space on storage systems and leading to performance issues or even outages.

The Challenge

Imagine having hundreds or thousands of VHDX files, each representing a user’s RDS session. If each one contains 1-2 GB of junk data — Downloads, old installers, Recycle Bin contents — you’re looking at terabytes of wasted space.

Manual cleanup isn’t scalable. But automation is.

My PowerShell Script to the Rescue

To address this issue, I wrote a PowerShell script that automates the cleanup of specific directories inside VHD/VHDX profile files , such as:

  • $RECYCLE.BIN
  • Downloads
  • Any other folder you want to target

The script:

  1. Scans a specified folder for .vhd and .vhdx files.
  2. Mounts each VHDX file silently (if not already mounted).
  3. Cleans up target directories inside the virtual disk.
  4. Dismounts the disk and logs the success or failure of each operation.
# Example directories to clean
$recycle = '$RECYCLE.BIN', 'Downloads'

You can easily add more folders to this list based on your organization’s needs.

Why This Matters for Businesses

This script is particularly useful for organizations using FSLogix or User Profile Disks (UPDs) in their RDS or AVD (Azure Virtual Desktop) environments. It enables you to:

  • Reclaim valuable disk space automatically
  • Avoid user profile bloating and long login times
  • Extend the life of your storage infrastructure
  • Ensure consistency across all user profiles

And the best part? It’s easy to schedule this as a weekly or monthly task using Task Scheduler or your preferred automation platform.

👉 Here is the Script :

  • You need to define folder that contain VHDX files in $VHD_Directory variable.
  • You need to define list of directories to clean up in $recycle variable.
 # ================= variables to define here ===============

# Directory that contain VHDX Files
$VHD_Directory = "C:\Temp\"

# Specifie Directorys to be cleaned (a letter will be automatically added to PATH , example S:\Downloads)
$recycle = '$RECYCLE.BIN' , 'Downloads' 

# ===========================================================

# Define Error Action
$ErrorActionPreference = 'SilentlyContinue'

# Get list of vhd , vhdx files from original directory
$VHD_Files = Get-ChildItem $VHD_Directory -Recurse -Include *.vhd , *.vhdx -Force | Select-Object Name
# $VHD_Files


foreach ($VhdFile  in $VHD_Files) 
{
    # file path
    $FilePath = $VHD_Directory + $VhdFile.Name
    # $FilePath

    # run the Mount-VHD cmdlet to mount your vhd/vhdx file.
    try 
    {
        if( (Get-DiskImage -ImagePath $FilePath | Select-Object Attached).Attached -eq $false )
        {
            if(Mount-DiskImage -ImagePath $FilePath -ErrorVariable MountError)
            {
                Start-Sleep 2
                
                # get Letter for mounted disk
                $Mounted_Drive = Get-DiskImage -ImagePath $FilePath | Get-Disk | Get-Partition | Get-Volume | Select-Object DriveLetter
                # $Mounted_Drive

                foreach($dir in $recycle)
                {
                    if(Test-Path "$($Mounted_Drive.DriveLetter):\$($dir)\")
                    {
                        # Delete all objects in $recycle
                        Remove-Item  -Path "$($Mounted_Drive.DriveLetter):\$($dir)\*" -Recurse -Force -ErrorVariable DeleteError -ErrorAction SilentlyContinue
                    }
                    else
                    {
                        Write-Host "Error : Directory $($Mounted_Drive.DriveLetter):\$($dir)\ not Found " -ForegroundColor Yellow
                    }

                }
                
                # Close opned window for mounted volume
                $shell = New-Object -ComObject Shell.Application
                $window = $shell.Windows() | Where-Object { $_.LocationURL -eq ("file:///" + $Mounted_Drive) }
                $window | ForEach-Object { $_.Quit() }

                # unmount virtual hard disk
                Dismount-DiskImage -ImagePath $FilePath -ErrorVariable DismountError

                Write-Host "VHDX File -- $($FilePath) -- Cleaned Successfully" -ForegroundColor Green
                
            }
        }
        else
        {
            Write-Host "The current file -- $($FilePath) -- is already mounted" -ForegroundColor Yellow
        }
    }
    catch 
    {
        if(&MountError)
        {
            Write-Host "Error while Trying to Mount file : $($FilePath)" -ForegroundColor Red
        }
        if(&DeleteError)
        {
            Write-Host "Error while Trying to Delete files from : $($Mounted_Drive.DriveLetter):\$($dir)\ " -ForegroundColor Red
        }
        if(&DismountError)
        {
            Write-Host "Error while Trying to Dismount file : $($FilePath)" -ForegroundColor Red
        }
    }

}

How to Automate It

To run this script on a regular basis:

  1. Save it as a .ps1 file.
  2. Use Task Scheduler or Intune/Endpoint Manager to schedule it.
  3. Run it with administrative privileges to allow VHDX mounting.

Final Thoughts

Disk space management is often an overlooked aspect of RDS administration , until it becomes a problem. With this script, you can stay ahead of the curve by cleaning up virtual profiles proactively and automatically.

If your organization is struggling with RDS storage management, feel free to adapt or improve the script to match your needs. It’s simple, effective, and can save both time and money.

Thanks

Aymen EL JAZIRI (Microsoft MVP)
Aymen EL JAZIRI (Microsoft MVP)

Hi, I’m Aymen El Jaziri , a passionate System Administrator and Microsoft MVP, with years of hands-on experience in managing and securing modern IT infrastructures.
This blog is where I share technical guides, automation scripts, product reviews, and real-world solutions that help IT professionals simplify their day-to-day work and stay ahead in a fast-evolving cloud ecosystem.
Whether you’re here to troubleshoot an issue, improve your automation game, or learn new best practices , welcome in my blog !
Let’s build a stronger, smarter IT community together.
Feel free to connect with me on LinkedIn for more content, discussions, or collaboration opportunities.

Thanks

Aymen

Articles: 154