WindowsPath.psm1
#Requires -Version 7.0 using namespace 'System' using namespace 'System.IO' using namespace 'System.Management.Automation' Class WindowsPath { WindowsPath([string] $Path) { If (!!($WinPath = [WindowsPath]::GetFullPath($Path))) { $This | Add-Member ScriptProperty 'Path' { [CmdletBinding()][OutputType([string])] Param () $Script:WinPath }.GetNewClosure() } Else { Throw 'The string "{0}" is not a valid Windows path string.' -f $Path } } Static [string] GetFullPath([string] $PathToValidate) { If ([string]::IsNullOrWhiteSpace($PathToValidate) -or (& { $args[0] -imatch '[^ ]\s+([/\\]|$)' } $PathToValidate)) { Return $Null } $IPv6RegExp = '((([a-f\d]{1,4}:){7,7}[a-f\d]{1,4}|([a-f\d]{1,4}:){1,7}:|([a-f\d]{1,4}:){1,6}:[a-f\d]{1,4}|([a-f\d]{1,4}:){1,5}(:[a-f\d]{1,4}){1,2}|([a-f\d]{1,4}:){1,4}(:[a-f\d]{1,4}){1,3}|([a-f\d]{1,4}:){1,3}(:[a-f\d]{1,4}){1,4}|([a-f\d]{1,4}:){1,2}(:[a-f\d]{1,4}){1,5}|[a-f\d]{1,4}:((:[a-f\d]{1,4}){1,6})|:((:[a-f\d]{1,4}){1,7}|:)|fe80:(:[a-f\d]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([a-f\d]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])))' $IPv4RegExp = '((\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})' $HostNameRegExp = '([a-z\d\-]+(\.[a-z\d\-]+)*)' $ShareNameRegExp = '([^"/\\\[\]:\|\<\>\+=;,\?\*]+)' $DriveRegExp = '([^;~/\\\.:]+:)' $RelativeElmtRegExp = '(~|\.{1,2}|[^"/\\:\|\<\>\?\*]+)' $PathRegExp = '(([/\\]+[^"/\\:\|\<\>\?\*]*)*)' $RegExp = '(((?<Share>^(/{{2}}|\\{{2}})({0}|{1}|{2})[/\\]+{3})|((?<Drive>^{4})(?<Cwd>[^"/\\:\|\<\>\?\*]*))|(?<Relative>^{5}))(?<Path>{6}$))|(?<Path>^{6}$)' -f @($IPv6RegExp,$IPv4RegExp,$HostNameRegExp,$ShareNameRegExp,$DriveRegExp,$RelativeElmtRegExp,$PathRegExp) If ($PathToValidate -imatch $RegExp) { ${Function:Remove-MatchIntKeys} = { ForEach ($key in @($args[0].Value.keys)) { If ($key -inotin $args[1]) { $args[0].Value.Remove($key) } } } Remove-MatchIntKeys ([ref] $Matches) @('Share','Drive','Cwd','Relative','Path') $Matches.Where{ 'Share' -in $_.keys }.ForEach{ If ([Directory]::Exists($_.Share)) { $_.FullPath = Join-Path $_.Share $_.Path } ElseIf (& { $args[0] -imatch ('(?<Path>^{0}$)' -f $PathRegExp) } $_.Share) { $_.Path = Join-Path $_.Share $_.Path $_.Remove('Share') } } Switch ($Matches) { { 'Drive' -in $_.keys } { If (($PSDrive = Get-PSDrive -Name ($_.Drive -replace ':$') -PSProvider FileSystem -ErrorAction SilentlyContinue)) { $_.FullPath = Join-Path $PSDrive.Root ((($_.Cwd -or !$_.Path ? ($PSDrive.CurrentLocation ? (@($PSDrive.CurrentLocation,$_.Cwd) -join '\'):($_.Cwd)):'') -replace '[/\\]+$') + (($_.Path = $_.Path -replace '[/\\]+$') ? ('\' + $_.Path):$Null)) } } { 'Relative' -in $_.keys } { If ($_.Relative -in @('~','.','..')) { $_.FullPath = Join-Path ("$(Resolve-Path $_.Relative -ErrorAction Stop)" -split '::',2)[-1] $_.Path } Else { $_.FullPath = Join-Path $PWD.ProviderPath (Join-Path $_.Relative $_.Path) } } } If ('FullPath' -in @($Matches.keys)) { Remove-MatchIntKeys ([ref] $Matches) @('FullPath') } } If (!$Matches.FullPath -and (($Matches.Share -and $Matches.Path) -or $PathToValidate -imatch ('(?<Path>^{0}$)' -f $PathRegExp))) { $Matches.FullPath = Join-Path "$(Try { Resolve-Path '\' -ErrorAction Stop } Catch { & { [void] ($PWD.ProviderPath -imatch '(?<Share>\\{2}[^/\\]+\\+[^/\\]+(\\|$))') $Matches.Share } })" $Matches.Path } If ('FullPath' -in @($Matches.keys)) { $Index0,$Index1,$PathSegmentList = $Matches.FullPath?.Split([char[]](47,92),[StringSplitOptions]::RemoveEmptyEntries).Where{ $_ -ne '.'} Switch ($Matches) { { $_.FullPath -like '\\*' -or $_.FullPath -like '//*' } { $_.Prefix = '\\{0}\{1}\' -f $Index0,$Index1 } Default { $_.Prefix = "$Index0\" $PathSegmentList = @($Index1) + $PathSegmentList } } $CurrentSegmentIndex = 1 While ($CurrentSegmentIndex -lt $PathSegmentList.Count) { If ($CurrentSegmentIndex -gt 0 -and $PathSegmentList[$CurrentSegmentIndex] -eq '..' -and $PathSegmentList[$CurrentSegmentIndex - 1] -ne '..') { $PathSegmentList[$CurrentSegmentIndex - 1] = $PathSegmentList[$CurrentSegmentIndex] = $Null $PathSegmentList = $PathSegmentList.Where{ $_ } $CurrentSegmentIndex-- } Else { $CurrentSegmentIndex++ } } $Matches.FullPath = Join-Path $Matches.Prefix (($PathSegmentList.Where({ $_ -ne '..' }, 'SkipUntil') -Join '\') -replace '[/\\]+$') } Return $Matches.FullPath } [string] ToString() { Return $This.Path } } Class ValidateWindowsPathAttribute : ValidateArgumentsAttribute { [void] Validate([object] $PathToValidate, [EngineIntrinsics] $EngineIntrinsics) { [WindowsPath]::New($PathToValidate) } } Class ValidateWindowsFileNameAttribute : ValidateArgumentsAttribute { [void] Validate([object] $FileNameToValidate, [EngineIntrinsics] $EngineIntrinsics) { If (!("$FileNameToValidate" -imatch '^[^"/\\:\|\<\>\?\*]+[^"/\\:\|\<\>\?\*\s]+$' -and $Matches[0] -inotmatch '^\.{1,2}$')) { Throw 'The string "{0}" is not a valid Windows file name string.' -f $FileNameToValidate } } } |