I was wondering if someone could help me understand why does System.IO.FileInfo
behaves differently on Windows than on Linux when handling relative paths.
Example
- On Linux
PS /home/user/Documents> ([System.IO.FileInfo]'./test.txt').FullName
/home/user/Documents/test.txt
- On Windows
PS C:\Users\User\Documents> ([System.IO.FileInfo]'.\test.txt').FullName
C:\Users\User\test.txt
EDIT
To clarify on the above, there is no difference on how System.IO.FileInfo
handles relative paths on Windows or Linux. The issue is related to [System.IO.Directory]::GetCurrentDirectory()
not being updated by Push-Location
or Set-Location
.
A simple example:
PS /home/user> [System.IO.Directory]::GetCurrentDirectory()
/home/user
PS /home/user> cd ./Documents/
PS /home/user/Documents> [System.IO.Directory]::GetCurrentDirectory()
/home/user
And assuming this is a expected behavior, what would be an optimal way to approach our param(...)
blocks on scripts and functions to accept both cases (absolute and relative). I used to type constraint the path parameter to System.IO.FileInfo
but now I can see it is clearly wrong.
This is what I came across, but I'm wondering if there is a better way.
I believe Split-Path -IsAbsolute
will also bring problems if working with Network Paths, please correct me if I'm wrong.
param(
[ValidateScript({
if(Test-Path $_ -PathType Leaf) {
return $true
}
throw 'Invalid File Path'
})]
[string]$Path
)
if(-not(Split-Path $Path -IsAbsolute))
{
[string]$Path = Resolve-Path $Path
}
The following should be able to handle:
- UNC Paths
- Work on Windows and Linux
- Be efficient
- Handle Relative Paths
Starting from the base that $Path
is valid thanks to the ValidateScript attribute
, we only need to determine if the path we are dealing with is UNC, Relative or Absolute.
UNC paths must always be fully qualified. They can include relative directory segments (. and ..), but these must be part of a fully qualified path. You can use relative paths only by mapping a UNC path to a drive letter.
We can assume a UNC path must always start with \\
, so this condition should suffice to determine if $Path
will be manipulated or not:
if(-not $Path.StartsWith('\\'))
Lastly, in the begin
block, updating the environment's current directory each time our script or function runs with:
[Environment]::CurrentDirectory = $pwd.ProviderPath
By doing so, ([System.IO.FileInfo]$Path).FullName
should give us the absolute path of our parameter, be it UNC, Relative or Absolute.
param(
[ValidateScript({
if(Test-Path $_ -PathType Leaf) {
return $true
}
throw 'Invalid File Path'
})] [string]$Path
)
begin
{
[Environment]::CurrentDirectory = $pwd.ProviderPath
}
process
{
if(-not $Path.StartsWith('\\'))
{
$Path = ([System.IO.FileInfo]$Path).FullName
}
$Path # => Should be Absolute
}
CodePudding user response:
Feels a bit duplicate, but since you asked..
I'm sorry I don't know about Linux, but in Windows:
You can add a test first to see if the path is relative and if so, convert it to absolute like:
$Path = '.\test.txt'
if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\] ') {
$Path = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
}
I added $Path -match '^\\[^\\] '
to also convert relative paths starting with a backslash like \ReadWays.ps1
meaning the path starts at the root directory. UNC paths that start with two backslashes are regarded as absolute.
Apparently (I really have no idea why..) the above does not work on Linux, because there, when using a UNC path, the part ![System.IO.Path]::IsPathRooted('\\server\folder')
yields True
.
It seems then you need to check the OS first and do the check differently on Linux.
$Path = '\\server\share'
if ($IsWindows) { # $IsWindows exists in version 7.x. Older versions do `$env:OS -match 'Windows'`
if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\] ') {
$Path = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
}
}
else {
if ($Path -notlike '\\*\*') { # exclude UNC paths as they are not relative
if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\] ') {
$Path = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
}
}
}