Best ways to Shrink and Optimize FSLogix Profile VHD files

This is the FSLogix Magic Hammer You Didn’t Know You Had!
by Jacques Bensimon

As you probably know, as a result of the Microsoft acquisition and of the generous licensing terms that followed, FSLogix Apps has quickly become the go-to profile management technology in many enterprises, especially for VDI and cloud deployments.  It also doesn’t hurt that it’s an elegantly engineered product that “just works”.  But (isn’t there always a “but”?) there’s a small rub that many FSLogix implementations quickly discover:  ever-growing storage requirements!  The reason is simple:  the individual VHD/VHDX virtual disk files into which user profiles are captured (I’ll just call them VHDs going forward), despite starting small and being thin-provisioned (aka dynamically expanding), never shrink on their own.  They grow as files are written to them during active use, but when files are modified, relocated, or deleted over multiple sessions, the virtual disk blocks that are freed in the process remain physically present in the VHDs.  It’s not at all unusual for a multi-GB VHD to be 4 or 5 times larger than the “in use” profile data it actually contains.

This has given rise to a brisk cottage industry of scripts designed to shrink VHDs in place, either individually or, in the more ambitious cases (for example Jim Moyle’s), in bulk and at scale.  What they all have in common (well, at least those I’ve looked at) is that they eventually come down to using a standard VHD “compact” operation, generally either DiskPart’s COMPACT VDISK command or PowerShell’s Optimize-VHD cmdlet (which requires the Hyper-V module).  The problem with these functions is that their results are unpredictable and frequently far from satisfactory, in some cases even increasing the size of the target VHDs.  The reason for these sub-optimal outcomes is that the standard compacting operations appear to only be able to eliminate consecutive free blocks at the tail end of the VHDs, generally not those that are interspersed among “in use” blocks.

So down I went the rabbit hole of trying to build a better mousetrap (who else gives you two clichés in one sentence, huh?  😉):  my first thought was to introduce a defragmentation phase into the process, especially of the free space, prior to the compacting operation (many defragmenters, including Windows’ own, can be scripted).  This in many cases did lead to somewhat better results, but still not nearly optimal, when it had any effect at all.  The issue here is that defragmenters stop short of attempting to move the MFT (Master File Table) and other critical NTFS structures, so if some of their blocks happen to be near the end of the target VHD, not much is gained in terms of removable trailing free blocks.  My next thought was to abandon the idea of “in place” compaction and instead create a new VHD with content identical to the original and thus suitable to replace it.  Along those lines, for the sake of speed, I first looked into the possibility of using a disk cloning approach, knowing from experience that some cloning products (in particular those from Acronis) are capable of only cloning “in use” disk blocks.  The procedure I followed in testing several cloners was always essentially the same: attach the source VHD, create and attach a new empty VHD, and clone from one to the other.  The proof-of-concept using Acronis “True Image” (their home product) actually had rather spectacular results:  a previously resistant VHDX of (external) size 4.75 GB but only containing 1.63 GB of data resulted (in less than 1 minute) in a VHDX of size 1.75 GB, only slightly more than the “in use” data, with similar results using other FSLogix sample profiles.  Problem is, “True Image” is not scriptable, it’s GUI only.  So I next tried Acronis “Cyber Backup”, which also has a cloning function and does come with a command-line tool, but that turned out to be a non-starter because the product does not “see” attached VHDs at all, only physical disks.  And, to make an already long story shorter, every other scriptable cloner I looked at (including AOMEI’s and R-Tools’) was incapable of ignoring free blocks during cloning, so their resulting VHDs were at least as large as the originals.

So, I was well into implementing my strategy of last resort, using good old RoboCopy to transfer the contents of the pre-attached source VHD to a pre-attached, pre-partitioned, and pre-formatted empty target VHD (which by the way is both theoretically and empirically the optimal shrinking strategy) when, I forget how or why, I came across this page describing the FSLogix command-line utility frx.exe, which is provided as part of the standard product installation!  And wouldn’t you know it, among its many functions is one (spotted by an eagle-eyed colleague) called migrate-vhd that, as its description says, “migrates contents of current VHD to a new VHD”.  Seriously?  This has been available all along?

Now cutting to the chase, this works fantastically, and requires no preliminary steps:  frx.exe migrate-vhd combines all the necessary VHD attach, create, partition, format, copy, and detach steps into a single fast operation.  Remember that 4.75 GB sample VHDX with 1.63 GB in use?  The following screenshots show the frx command that created a perfect** copy (Target.vhdx) of size only 1.72 GB in less than 45 seconds.  All that’s left to do is replace the original with the copy (a Move operation will be instantaneous, won’t involve any data movement, and will immediately reclaim the space taken up by the original VHD).

**Small caveat:  This frx copy process, much like RoboCopy and most other copy tools, does not recreate the (normally 18) junctions (see below) that are found in current Windows profiles.  If you have concerns about some old apps possibly relying on some of these junctions to find their way around the user profile (the purpose of these junctions is to redirect access to old profile subfolders to their new locations) and if it turns out that Windows does not recreate the junctions upon profile load (haven’t tested, but I suspect it probably doesn’t), it’s a relatively simple affair to script their recreation (using the MkLink command, for example), either immediately (after re-attaching the resulting VHD) or at logon.

I believe that incorporating frx.exe migrate-vhd into a new bulk VHD compacting script or using it to replace the guts of an existing script while keeping its main “plumbing” in place is definitely worth your giving it a try.  I may report on my own results in different environments at some point, … so be sure to subscribe to the newsletter at the bottom of this page to be alerted.

Happy shrinking!

Follow Jacques Bensimon on Twitter @JacqBens for more great Windows insights and tricks.