Public/Get-FslProfileReport.ps1
|
function Get-FslProfileReport { <# .SYNOPSIS Scans an FSLogix profile share and reports size, age and anomalies per container disk - without mounting anything. .DESCRIPTION Walks the standard FSLogix folder layout (<share>\<SID>_<username> or flip-flopped <username>_<SID>) and reports every VHD/VHDX it finds: file size, percent of the configured maximum, last-use age, and structural anomalies such as multiple disks in one folder or disks outside the naming convention. The scan is deliberately read-only and never mounts a disk: mounting an in-use production container can corrupt it. File size of a dynamic disk is a conservative lower bound of the space used inside it. .PARAMETER Path UNC path or local path of the profile share to scan. .PARAMETER MaximumSizeMB The configured container size limit (SizeInMBs) used to compute PercentOfMax. Defaults to the local FSLogix configuration when available, else 30000. .PARAMETER StaleDays Number of days without modification after which a disk is flagged stale. Defaults to 90. .EXAMPLE Get-FslProfileReport -Path \\fs01\fslogix$ .EXAMPLE Get-FslProfileReport -Path \\fs01\fslogix$ | Where-Object Stale | Measure-Object SizeGB -Sum #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory, Position = 0)] [string]$Path, [int]$MaximumSizeMB = 0, [int]$StaleDays = 90 ) if (-not (Test-Path -LiteralPath $Path)) { throw "Profile share path '$Path' not found or not reachable." } if ($MaximumSizeMB -le 0) { $MaximumSizeMB = 30000 # Policies hive wins over the local key, matching FSLogix precedence. foreach ($configKey in @('HKLM:\SOFTWARE\Policies\FSLogix\Profiles', 'HKLM:\SOFTWARE\FSLogix\Profiles')) { $localConfig = Get-ItemProperty -LiteralPath $configKey -ErrorAction SilentlyContinue if ($localConfig -and $localConfig.SizeInMBs -gt 0) { $MaximumSizeMB = [int]$localConfig.SizeInMBs break } } } $now = Get-Date $sidPattern = 'S-1-\d+(?:-\d+)+' foreach ($folder in (Get-ChildItem -LiteralPath $Path -Directory -ErrorAction SilentlyContinue)) { $sid = $null $userName = $null if ($folder.Name -match "^(?<sid>$sidPattern)_(?<user>.+)$") { # Default layout: <SID>_<username> $sid = $Matches['sid'] $userName = $Matches['user'] } elseif ($folder.Name -match "^(?<user>.+)_(?<sid>$sidPattern)$") { # FlipFlopProfileDirectoryName=1 layout: <username>_<SID> $sid = $Matches['sid'] $userName = $Matches['user'] } $folderFiles = Get-ChildItem -LiteralPath $folder.FullName -Recurse -File -ErrorAction SilentlyContinue $disks = @($folderFiles | Where-Object { $_.Extension -in @('.vhd', '.vhdx') }) if ($disks.Count -eq 0) { [pscustomobject]@{ PSTypeName = 'FSLogixDoctor.ProfileDisk' Folder = $folder.FullName Disk = $null UserName = $userName Sid = $sid SizeGB = 0 PercentOfMax = 0 LastModified = $folder.LastWriteTime AgeDays = [int]($now - $folder.LastWriteTime).TotalDays Stale = (($now - $folder.LastWriteTime).TotalDays -gt $StaleDays) DiskCount = 0 Anomaly = 'Folder contains no container disk (leftover from a failed create or a manual cleanup).' } continue } foreach ($disk in $disks) { $anomalies = @() if ($null -eq $sid) { $anomalies += 'Folder name does not match FSLogix naming convention.' } if ($disks.Count -gt 1) { $anomalies += "Folder contains $($disks.Count) disks - possible leftover from a re-created profile." } $sizeGB = [math]::Round($disk.Length / 1GB, 2) $percent = 0 if ($MaximumSizeMB -gt 0) { $percent = [math]::Round(($disk.Length / 1MB) / $MaximumSizeMB * 100, 1) } [pscustomobject]@{ PSTypeName = 'FSLogixDoctor.ProfileDisk' Folder = $folder.FullName Disk = $disk.FullName UserName = $userName Sid = $sid SizeGB = $sizeGB PercentOfMax = $percent LastModified = $disk.LastWriteTime AgeDays = [int]($now - $disk.LastWriteTime).TotalDays Stale = (($now - $disk.LastWriteTime).TotalDays -gt $StaleDays) DiskCount = $disks.Count Anomaly = ($anomalies -join ' ') } } } # Disks lying directly in the share root violate the expected layout. $rootFiles = Get-ChildItem -LiteralPath $Path -File -ErrorAction SilentlyContinue $rootDisks = @($rootFiles | Where-Object { $_.Extension -in @('.vhd', '.vhdx') }) foreach ($rootDisk in $rootDisks) { [pscustomobject]@{ PSTypeName = 'FSLogixDoctor.ProfileDisk' Folder = $Path Disk = $rootDisk.FullName UserName = $null Sid = $null SizeGB = [math]::Round($rootDisk.Length / 1GB, 2) PercentOfMax = 0 LastModified = $rootDisk.LastWriteTime AgeDays = [int]($now - $rootDisk.LastWriteTime).TotalDays Stale = (($now - $rootDisk.LastWriteTime).TotalDays -gt $StaleDays) DiskCount = 1 Anomaly = 'Disk sits in the share root instead of a per-user folder.' } } } |