1
\$\begingroup\$

I would like to have a function Chain-Paths to accomplish the following: given a base directory (Base) and a location, possibly relative to that given Base (Chained), output the result of joining both paths. If however Chained is already an absolute path, just return that. If both paths are relative, keep the result relative as well.

The above, reformulated in test cases (works if run from a user directory, e.g. C:\Users\ojdo):

$testCases = @(
    @{
        Base = "..\..\Windows\Fonts"
        Chained = "..\system32\notepad.exe"
        Expected = "..\..\Windows\system32\notepad.exe"
    }
    @{
        Base = "C:\Windows\Fonts"
        Chained = "..\system32\notepad.exe"
        Expected = "C:\Windows\system32\notepad.exe"
    }
    @{

        Base = "AppData\Roaming"
        Chained = "C:\Windows\system32\notepad.exe"
        Expected = "C:\Windows\system32\notepad.exe"
    }
)

foreach ($case in $testCases) {
    $expectedResult = $case.Expected
    $actualResult = (Chain-Paths $case.Base $case.Chained)
    $testResult = if($actualResult -eq $expectedResult) {"PASS"} else {"FAIL"}

    Write-Host ("{0}: {1} == {2}" -f $testResult, $actualResult, $expectedResult)
}

This is the function I have come up with:

function Chain-Paths($Base, $Chained)
{
    if (Split-Path $Chained -IsAbsolute) {
        return $Chained
    } else {
        $joinedPath = Join-Path $Base $Chained
        if (Split-Path $Base -IsAbsolute) {
            return (Resolve-Path $joinedPath).Path
        } else {
            return Resolve-Path $joinedPath -Relative
        }
    }
}

My questions:

  • Am I overcomplicating things?
  • Is there a way to simplify things by using a .net function?
  • Is the (...).Path really required?
  • Any stylistic recommendations (apart from missing docstring)?
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You can use the System.IO.Path.Combine Method.

This method is intended to concatenate individual strings into a single string that represents a file path. However, if an argument other than the first contains a rooted path, any previous path components are ignored, and the returned string begins with that rooted path component.

$combined = [IO.Path]::Combine($Base, $Chained)

Also, it seems more natural to use System.IO.Path.IsPathRooted() method instead of Split-Path to see if the path is absolute.

$isRelative = ![IO.Path]::IsPathRooted($combined)

There is a way to pass parameter values by appending a colon after the parameter name in cmdlet. You can also pass values to the switch type parameter this way.

Verb-Noun -ParameterName:Value

Resolve-Path basically outputs a PathInfo object, but when the -Relative switch is set the output will be string. Both types have a ToString () method, so you can get a path string without using an if statement. (The ToString() method of the PathInfo object outputs the value of the Path property)

function Chain-Path($Base, $Chained) {
    $combined = [IO.Path]::Combine($Base, $Chained)
    $isRelative = ![IO.Path]::IsPathRooted($combined)
    (Resolve-Path $combined -Relative:$isRelative).ToString()
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.