Public/Get-FslOrphanedDisk.ps1
|
function Get-FslOrphanedDisk { <# .SYNOPSIS Finds profile container disks whose owner no longer exists (or is disabled) - the gigabytes you can reclaim from a profile share. .DESCRIPTION Takes the output of Get-FslProfileReport (or scans a share itself) and checks each disk's SID against the identity infrastructure: - Without extra modules, the SID is translated via the OS. A SID that no longer translates means the account was deleted: state 'Orphaned'. - When the ActiveDirectory module is available, disabled accounts are also detected: state 'Disabled'. The function never deletes anything. Pipe the output to a report first; removal stays a deliberate human decision. .PARAMETER InputObject FSLogixDoctor.ProfileDisk objects from Get-FslProfileReport. .PARAMETER Path Alternatively, a profile share path to scan directly. .PARAMETER KnownUser Offline mode: a list of valid usernames. Disks whose user is not in the list are flagged 'Orphaned' without touching AD - useful for testing and for air-gapped analysis of a share listing. .EXAMPLE Get-FslProfileReport -Path \\fs01\fslogix$ | Get-FslOrphanedDisk Full pipeline: scan the share, then classify every disk. .EXAMPLE Get-FslOrphanedDisk -Path \\fs01\fslogix$ | Where-Object State -ne 'OK' | Measure-Object SizeGB -Sum How many GB the share could reclaim. #> [CmdletBinding(DefaultParameterSetName = 'Pipeline')] [OutputType([pscustomobject])] param( [Parameter(ParameterSetName = 'Pipeline', Mandatory, ValueFromPipeline)] [PSTypeName('FSLogixDoctor.ProfileDisk')] [pscustomobject]$InputObject, [Parameter(ParameterSetName = 'Path', Mandatory)] [string]$Path, [string[]]$KnownUser ) begin { $adAvailable = $null -ne (Get-Command -Name 'Get-ADUser' -ErrorAction SilentlyContinue) $useKnownList = $PSBoundParameters.ContainsKey('KnownUser') $classify = { param([pscustomobject]$disk) if ($null -eq $disk.Disk) { return } # empty folders are reported by Get-FslProfileReport $state = 'OK' $detail = '' if ($useKnownList) { if (-not $disk.UserName) { $state = 'Unknown' $detail = 'No username could be derived from the folder name - verify manually before touching this disk.' } elseif ($KnownUser -contains $disk.UserName) { $state = 'OK' $detail = 'User is in the provided KnownUser list.' } else { $state = 'Orphaned' $detail = "User '$($disk.UserName)' is not in the provided KnownUser list." } } elseif ($null -eq $disk.Sid) { $state = 'Unknown' $detail = 'No SID could be derived from the folder name.' } elseif ($disk.Sid -like 'S-1-12-1-*') { # Entra-ID-only (cloud) SIDs resolve exclusively on Entra-joined # hosts; a translation failure on a domain-joined file server says # nothing about the account, so never call these orphaned here. $account = Resolve-FslAccount -Sid $disk.Sid if ($account.Exists) { $detail = "Entra ID account '$($account.Account)' resolved on this host." } else { $state = 'Unknown' $detail = 'Entra ID (cloud) SID - resolvable only from an Entra-joined host. Verify the account in Entra ID before treating this disk as orphaned.' } } else { $account = Resolve-FslAccount -Sid $disk.Sid if (-not $account.Exists) { $state = 'Orphaned' $detail = "SID '$($disk.Sid)' no longer resolves to an account." } elseif ($adAvailable -and $disk.Sid -like 'S-1-5-21-*') { try { $adUser = Get-ADUser -Identity $disk.Sid -Properties Enabled -ErrorAction Stop if (-not $adUser.Enabled) { $state = 'Disabled' $detail = "Account '$($account.Account)' exists but is disabled." } else { $detail = "Account '$($account.Account)' exists and is enabled." } } catch { # The SID translated, so the account exists - an AD lookup # miss (local account, other forest) must not degrade that. $detail = "Account '$($account.Account)' exists (AD lookup not possible: $($_.Exception.Message))" } } else { $detail = "Account '$($account.Account)' exists (enable/disable state needs the ActiveDirectory module)." } } $reclaimable = 0 if ($state -in @('Orphaned', 'Disabled')) { $reclaimable = $disk.SizeGB } [pscustomobject]@{ PSTypeName = 'FSLogixDoctor.OrphanCandidate' Disk = $disk.Disk Folder = $disk.Folder UserName = $disk.UserName Sid = $disk.Sid SizeGB = $disk.SizeGB LastModified = $disk.LastModified State = $state Detail = $detail ReclaimableGB = $reclaimable } } } process { if ($PSCmdlet.ParameterSetName -eq 'Pipeline') { & $classify $InputObject } } end { if ($PSCmdlet.ParameterSetName -eq 'Path') { foreach ($disk in (Get-FslProfileReport -Path $Path)) { & $classify $disk } } } } |