# SHrinkVHD4.ps1 - Chawn Limited - 7-February-2020 - Use at own Risk - No Liability Accepted # Pass Parameters to shrinkVHD.ps1 # $FSPath - UNC path to the root of the SMB FSLogix Share - e.g. "\\server\share" - This is mapped using......... # $MapLetter - Drive letter to map to the FS Logix share - e.g. "Z" # $Mapuser - Username to map the FSLogix Share - e.g. "DOMAIN\FSAdmin" # $pwd - Cleartext password of the $Mapuser - e.g. "P@55w0rd" # $EXCPath - Relative Path to redirections.xml using the $MapLetter - e.g. "Z:\redirections.xml" - This can be your standard redirections.xml or any well formed xml file specifically for shrinking with additional folders - e.g. "Z:\shrink.xml" # $VHDPath - Relative Path to the user's VHD to be shrunk using the $MapLetter - e.g. "Z:\user1.domain\user1.domain.vhd" # $Backup - Backup original VHD - don't delete the original e.g. "Y" (optional) # e.g. .\shrinkVHD4.ps1 "\\server\share" "Z" "DOMAIN\FSAdmin" "P@55w0rd" "Z:\redirections.xml" "Z:\user1.domain\user1.domain.vhd" "Y" #Parameters param( [String]$FSpath = "", [String]$MAPLetter = "", [String]$mapuser = "", [String]$pwd = "", [String]$EXCPath="", [String]$VHDPath = "", [String]$Backup = "" ) # Get Time $d1=get-Date Write-Host `n "FSLogix Share: " $FSPath `n "FSLogix Share Letter: " $MapLetter `n "FSLogix Admin: " $mapuser `n "FSLogix Admin PWD: ********" `n "Exlusions XML Path: " $EXCPath `n "VHD to Shrink: " $VHDPath `n "Keep Backup: " $Backup `n # Check Pre-Reqs %{ if ((Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V).count -eq 0) {Write-Host -ForegroundColor Red "Microsoft-Hyper-V is NOT installed. Exit" Break} Else {Write-Host -ForegroundColor Green "Microsoft-Hyper-V IS installed."} } %{ if ((Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell).count -eq 0) {Write-Host -ForegroundColor Red "Microsoft-Hyper-V-Management-PowerShell is NOT installed." Break} Else {Write-Host -ForegroundColor Green "Microsoft-Hyper-V-Management-PowerShell IS installed."} } %{ 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 $MAPDrv=$MAPLetter + ':' Function RemoveDrive{ try {Get-PSDrive -Name $MAPLetter | Remove-PSDrive -Force -erroraction stop} catch {write-host -ForegroundColor Red "Error encountered. Cannot remove " $FSPath + $_} } $encpwd=$pwd | ConvertTo-SecureString -AsPlainText -Force $credential = New-Object System.Management.Automation.PsCredential($mapuser, $encpwd) # check if driveletter is already in use - try and clear it if (get-PSDrive -name $MAPLetter -erroraction silentlycontinue) {write-host -ForegroundColor Red "Drive Z appears to be mapped already" RemoveDrive} # map to FSLogix Share try {New-PSDrive -name $MAPLetter -PSProvider FileSystem -Root $FSpath -persist -scope Global -Credential $credential -erroraction stop} catch {write-host -ForegroundColor Red "Count not map " $FSPath " Exit:" $_ write-host "Try unmapping Z manually using the command: net use Z: /d" write-host "or using the command: net use \\server\share /d" Break} Function TestVHD{ try {$global:VHD=get-vhd -Path $VHDPath -erroraction SilentlyContinue} catch {write-host "Error encountered. " $VHDPath " is probably in use. Exit:" $_ RemoveDrive Break} if ($VHD.Attached) {Write-Host -ForegroundColor Red "Cannot not mount " $VHDPath $VHD Write-Host $VHDPath " is likely in use" RemoveDrive Break} } Function TestVHD2{ try {$global:VHD2=get-vhd -Path $VHDPath -erroraction SilentlyContinue} catch {write-host -ForegroundColor Red "Error encountered. " $VHDPath " is probably in use. Exit:" $_ RemoveDrive Break} if ($VHD2.Attached) {Write-Host -ForegroundColor Red "Cannot not mount " $VHDPath Write-Host $VHDPath " is likely in use" RemoveDrive 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" RemoveDrive Break} } Function MountVHD{ $fs=Mount-VHD -Path $VHDPath -NoDriveletter -Passthru -erroraction SilentlyContinue | Get-Disk | Get-Partition | Add-PartitionAccessPath -AssignDriveLetter -PassThru | get-volume %{ if (($fs.DriveLetter).count -eq 0) {Write-Host -ForegroundColor Red "Could not mount " $VHDPath Write-Host $VHDPath " is likely in use" RemoveDrive Break} Else {Write-Host -ForegroundColor Green $VHDPath " Mounted." $global:MNTLetter=$fs.DriveLetter} } } Function UnMountVHD{ try {Dismount-VHD $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 RemoveDrive Break} } # Mount the VHD write-host `n "Mounting VHD: " $VHDPath MountVHD # 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\locallow" -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 # 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 RemoveDrive 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 # Remove the Mapped Drive Get-PSDrive -Name $MAPLetter | Remove-PSDrive -Force 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 "Fragmented:" `t`t $VHD.FragmentationPercentage `t`t $VHD2.FragmentationPercentage 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.seconds " seconds" }