62
votes

I'm trying to process a list of files that may or may not be up to date and may or may not yet exist. In doing so, I need to resolve the full path of an item, even though the item may be specified with relative paths. However, Resolve-Path prints and error when used with a non-existant file.

For example, What's the simplest, cleanest way to resolve ".\newdir\newfile.txt" to "C:\Current\Working\Directory\newdir\newfile.txt" in Powershell?

Note that System.IO.Path's static method use with the process's working directory - which isn't the powershell current location.

13
In PowerShell 2 and 3 you can use Resolve-Path - Daniel Little
That fails for non-existant paths, and I'm trying to create files, so that's an expected scenario. - Eamon Nerbonne
This should be really a feature of Resolve-Path out of the box. It actually does resolve it, but then throws an unwanted error... - Igor

13 Answers

89
votes

You want:

c:\path\exists\> $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\nonexist\foo.txt")

returns:

c:\path\exists\nonexists\foo.txt

This has the advantage of working with PSPaths, not native filesystem paths. A PSPAth may not map 1-1 to a filesystem path, for example if you mount a psdrive with a multi-letter drive name.

-Oisin

40
votes

When Resolve-Path fails due to the file not existing, the fully resolved path is accessible from the thrown error object.

You can use a function like the following to fix Resolve-Path and make it work like you expect.

function Force-Resolve-Path {
    <#
    .SYNOPSIS
        Calls Resolve-Path but works for files that don't exist.
    .REMARKS
        From http://devhawk.net/blog/2010/1/22/fixing-powershells-busted-resolve-path-cmdlet
    #>
    param (
        [string] $FileName
    )

    $FileName = Resolve-Path $FileName -ErrorAction SilentlyContinue `
                                       -ErrorVariable _frperror
    if (-not($FileName)) {
        $FileName = $_frperror[0].TargetObject
    }

    return $FileName
}
18
votes

I think you're on the right path. Just use [Environment]::CurrentDirectory to set .NET's notion of the process's current dir e.g.:

[Environment]::CurrentDirectory = $pwd
[IO.Path]::GetFullPath(".\xyz")
11
votes
Join-Path (Resolve-Path .) newdir\newfile.txt
6
votes

This has the advantage of not having to set the CLR Environment's current directory:

[IO.Path]::Combine($pwd,"non\existing\path")

NOTE

This is not functionally equivalent to x0n's answer. System.IO.Path.Combine only combines string path segments. Its main utility is keeping the developer from having to worry about slashes. GetUnresolvedProviderPathFromPSPath will traverse the input path relative to the present working directory, according to the .'s and ..'s.

1
votes

I've found that the following works well enough.

$workingDirectory = Convert-Path (Resolve-Path -path ".")
$newFile = "newDir\newFile.txt"
Do-Something-With "$workingDirectory\$newFile"

Convert-Path can be used to get the path as a string, although this is not always the case. See this entry on COnvert-Path for more details.

0
votes
function Get-FullName()
{
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline = $True)] [object[]] $Path
    )
    Begin{
        $Path = @($Path);
    }
    Process{
        foreach($p in $Path)
        {
            if($p -eq $null -or $p -match '^\s*$'){$p = [IO.Path]::GetFullPath(".");}
            elseif($p -is [System.IO.FileInfo]){$p = $p.FullName;}
            else{$p = [IO.Path]::GetFullPath($p);}
            $p;
        }
    }
}
0
votes

I ended up with this code in my case. I needed to create a file later in the the script, so this code presumes you have write access to the target folder.

$File = ".\newdir\newfile.txt"
If (Test-Path $File) {
    $Resolved = (Resolve-Path $File).Path
} else {
    New-Item $File -ItemType File | Out-Null
    $Resolved = (Resolve-Path $File).Path
    Remove-Item $File
}

I also enclosed New-Item in try..catch block, but that goes out of this question.

0
votes

I had a similar issue where I needed to find the folder 3 levels up from a folder that does not exist yet to determine the name for a new folder I wanted to create... It's complicated. Anyway, this is what I ended up doing:

($path -split "\\" | select -SkipLast 3) -join "\\"
-2
votes

You can just set the -errorAction to "SilentlyContinue" and use Resolve-Path

5 >  (Resolve-Path .\AllFilerData.xml -ea 0).Path
C:\Users\Andy.Schneider\Documents\WindowsPowerShell\Scripts\AllFilerData.xml

6 >  (Resolve-Path .\DoesNotExist -ea 0).Path

7 >
-2
votes

There is an accepted answer here, but it is quite lengthy and there is a simpler alternative available.

In any recent version of Powershell, you can use Test-Path -IsValid -Path 'C:\Probably Fake\Path.txt'

This simply verifies that there are no illegal characters in the path and that the path could be used to store a file. If the target doesn't exist, Test-Path won't care in this instance -- it's only being asked to test if the provided path is potentially valid.

-2
votes

Both most popular answers don't work correctly on paths on not existing drives.

function NormalizePath($filename)
{
    $filename += '\'
    $filename = $filename -replace '\\(\.?\\)+','\'
    while ($filename -match '\\([^\\.]|\.[^\\.]|\.\.[^\\])[^\\]*\\\.\.\\') {
        $filename = $filename -replace '\\([^\\.]|\.[^\\.]|\.\.[^\\])[^\\]*\\\.\.\\','\'
    }
    return $filename.TrimEnd('\')
}
-4
votes

Check if the file exists before resolving:

if(Test-Path .\newdir\newfile.txt) { (Resolve-Path .\newdir\newfile.txt).Path }