Private/Resolve-KadenFinalPath.ps1
|
function Resolve-KadenFinalPath { <# .SYNOPSIS Follow a path through links so confinement sees the final location. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path ) $full = [System.IO.Path]::GetFullPath($Path) $root = [System.IO.Path]::GetPathRoot($full) if ([string]::IsNullOrEmpty($root)) { return $full } $relative = $full.Substring($root.Length) $current = $root foreach ($segment in @($relative -split '[\\/]' | Where-Object { -not [string]::IsNullOrEmpty($_) })) { if ($current -eq '/' -or $current -eq '\') { $current = $current + $segment } else { $current = Join-Path $current $segment } if (-not (Test-Path -LiteralPath $current)) { continue } $guard = 0 while ($guard -lt 8) { $item = Get-Item -LiteralPath $current -Force -ErrorAction SilentlyContinue if (-not $item -or -not $item.LinkType) { break } $target = [string]@($item.Target)[0] if ([string]::IsNullOrWhiteSpace($target)) { break } if (-not [System.IO.Path]::IsPathRooted($target)) { $parent = Split-Path -Parent $current $target = [System.IO.Path]::GetFullPath((Join-Path $parent $target)) } $current = [System.IO.Path]::GetFullPath($target) $guard++ } $item = Get-Item -LiteralPath $current -Force -ErrorAction SilentlyContinue if ($item -and $item.LinkType) { throw 'KDN-TXN-PATH-ESCAPE: The managed path still resolves through a link, so its final location is not confirmed.' } } return [System.IO.Path]::GetFullPath($current) } |