# ShrinkVHD5.ps1 - Chawn Limited - 29-September-2024 - Use at own Risk - No Liability Accepted # Run on a machine with FSLogix frx.exe installed # Pass Parameters to ShrinkVHD5.ps1 # $EXCPath - Path to Redirections.xml - e.g. "\\server\share\redirections.xml" - This can be your standard redirections.xml or any well formed xml file specifically for shrinking with additional excluded folders # $VHDPath - Path to the user's VHD to be shrunk - e.g. "\\server\share\user1.domain\user1.domain.vhdx" # $Backup - Backup original VHD - don't delete the original e.g. "Y" (optional) # e.g. .\ShrinkVHD5.ps1 "\\server\share\redirections.xml" "\\server\share\user1.domain\user1.domain.vhdx" "Y" #Parameters param( [String]$EXCPath="", [String]$VHDPath = "", [String]$Backup = "" ) # Get Time $d1=get-Date # Write Command Write-Host `n "Exlusions XML Path: " $EXCPath `n "VHD to Shrink: " $VHDPath `n "Keep Backup: " $Backup `n # Check Pre-Reqs %{ if (Get-Item $Env:ProgramFiles"\FSLogix\Apps\frx.exe" -erroraction SilentlyContinue) {Write-Host -ForegroundColor Green "FSLogix frx.exe is available"} Else {Write-Host -ForegroundColor Red "Please install the FSLogix Agent" Break} } # is it a VHD or a VHDX $ExtPath=$VHDPath.LastIndexOf(".") $ExtPath=$ExtPath $ExtPath=$VHDPath.Substring($ExtPath) $tempVHD=$VHDPath.LastIndexOf(".") $tempVHD=$VHDPath.Substring(0,$tempVHD) $tempVHD=$tempVHD + "_temp" + $Extpath $logpath=(Get-Location).path $logdate=get-date -Format ddMMyyyy $logfile=$logpath + "\FSL-Shrink-" + $logdate + ".log" Function LogWrite { Param ([string]$logstring) Add-content $Logfile -value $logstring } Function RemoveDrive { try {Get-PSDrive -Name $MAPLetter | Remove-PSDrive -Force -erroraction stop} catch {write-host -ForegroundColor Red "Error encountered. Cannot remove " $FSPath + $_} } Function TestVHD { $global:VHD=Get-DiskImage -ImagePath $VHDPath -erroraction SilentlyContinue; if ($VHD) {Write-Host -ForegroundColor Green "VHD is accessible: " $VHDPath} else {Write-Host -ForegroundColor Red "VHD is NOT accessible: " $VHDPath LogWrite ($VHDPATH + "," + "InUse" + ",-1" + ",-1") break} } Function TestVHD2 { try {$global:VHD2=Get-DiskImage -imagePath $VHDPath -erroraction SilentlyContinue} catch {write-host -ForegroundColor Red "Error encountered. " $VHDPath " is probably in use. Exit:" $_ Break} if ($VHD2.Attached) {Write-Host -ForegroundColor Red "Cannot not mount " $VHDPath Write-Host $VHDPath " is likely in use" Break} } Function TestXML { if (Get-Item $EXCPath -erroraction SilentlyContinue) {Write-Host -ForegroundColor Green "XML Redirections file is available"} Else {Write-Host -ForegroundColor Red "XML Redirections file is NOT available - Exit" Write-Host "Please validate the path to the XML file" Break} } Function MountVHD { $fs=Mount-DiskImage -imagePath $VHDPath -NoDriveletter -Passthru -erroraction SilentlyContinue; $disk=get-disk | Where-Object {$_.location -eq $VHDPath}; $disk=$disk | Get-Partition | Add-PartitionAccessPath -AssignDriveLetter -PassThru | get-volume %{ if (($disk.DriveLetter).count -eq 0) {Write-Host -ForegroundColor Red "Could not mount " $VHDPath Write-Host $VHDPath " is likely in use" Break} Else {Write-Host -ForegroundColor Green $VHDPath " Mounted." $global:MNTLetter=$disk.DriveLetter} } } Function RepairVHD { $DHealth=(get-volume -driveletter $MNTLetter).HealthStatus if ($DHealth -ne "Healthy") {write-host -ForegroundColor Red "Profile disk not healthy. Will attempt repair" repair-volume -Driveletter $MNTLetter -ErrorAction SilentlyContinue} $DHealth=(get-volume -driveletter $MNTLetter).HealthStatus if ($DHealth -ne "Healthy") {write-host -ForegroundColor Red "Could not repair Profile disk." UnMountVHD} Else {Write-Host -ForegroundColor Green "Profile disk healthy."} } Function UnMountVHD { try {disMount-DiskImage -imagePath $VHDPath -erroraction stop} catch {write-host -ForegroundColor Red "Error encountered. Cannot dismount VHD." + $_ Break} } # Test if the VHD is accessible - Not in use TestVHD # Validate that the XML Redirections file is available TestXML # Backup the NTFS Permissions on the VHD - they will get lost in the process write-host `n "Create ACL for: " $VHDPath $ACL=Get-Acl $VHDPath # Create Backup if ($Backup -eq "Y") { $Backup=get-date -Format "dd-MM-yyyy-hhmm" $Backup=$VHDPath + "_" + $Backup Write-Host "Backup " $VHDPath " to " $Backup try{Copy-Item -Path $VHDPath -Destination $Backup -Force -ErrorAction SilentlyContinue} catch {write-host -ForegroundColor Red "Error encountered. Cannot backup " $VHDPath Break} } # Mount the VHD write-host `n "Mounting VHD: " $VHDPath MountVHD # Check the disk health - if not healthy repair it write-host `n "Checking Disk Health: " $VHDPath RepairVHD # Read the Excludes in redirections.xml and remove folders and subfolders from the VHD [xml]$XmlDocument = Get-Content -Path $EXCPath $Excludes=$XmlDocument.FrxProfileFolderRedirection.Excludes foreach ($Exclude in $Excludes.Exclude) { $delfolder=$Exclude.'#text'.ToLower() write-host $delfolder if ( -not ($delfolder -eq "appdata\local" -or $delfolder -eq "appdata\roaming")) { $fldr=$MNTLetter + ":\Profile\" + $delfolder write-host "Deleting " $fldr Remove-Item -Path $fldr -Recurse -Force -ErrorAction SilentlyContinue } } # Dismount the VHD write-host `n "Unmounting VHD: " $VHDPath UnMountVHD # remove empty space from the VHD write-host "Removing Empty Space from " $VHDPath & "C:\Program Files\FSLogix\Apps\frx.exe" migrate-vhd -src $VHDPath -dest $tempVHD -dynamic 1 -vhdx-sector-size 4096 # delete the original vhd write-host `n "Deleting " $VHDPath try {Remove-Item -Path $VHDPath -Force -ErrorAction SilentlyContinue} catch {write-host -ForegroundColor Red "Error encountered. Cannot delete " $VHDPath Break} # rename the optimised VHD back to the original name rename-item -path $tempVHD -newname $VHDPath # Assign NTFS Permissions to the user VHD - they get lost along the way write-host `n "Restore NTFS permissions to " $VHDPath set-acl -path $VHDPath -AclObject $ACL # Get stats TestVHD2 # Get Time $d2=get-Date $dur=$d2-$d1 Write-Host Write-Host `n "Operation Completed" %{ Write-Host Write-Host "`t`t`tBefore `t`tAfter" Write-Host "Filesize(MB):" `t`t ($VHD.fileSize/1048576) `t`t ($VHD2.fileSize/1048576) Write-Host "DiskSize(MB):" `t`t ($VHD.Size/1048576) `t`t ($VHD2.Size/1048576) Write-Host "DiskType:" `t`t $VHD.VHDType `t $VHD2.VHDType Write-Host "Log Sector Size:" `t $VHD.LogicalSectorSize `t`t $VHD2.LogicalSectorSize Write-Host "Phys Sector Size:" `t $VHD.PhysicalSectorSize `t`t $VHD2.PhysicalSectorSize Write-Host write-Host "Duration: " $dur.minutes " mins" $dur.seconds " secs" } logwrite ($vhdpath + "," + "OK" + "," + ($VHD.fileSize/1048576) + "," + ($VHD2.fileSize/1048576) )