3

I need to store a relative path, as an absolute path in a file. I've tried quite a few things, including:

$path = (resolve-path -path "C:\Folder\..\Folder2").Path.ToString()

Add-content "d:\textfile.txt" $path

Which leaves an empy file. So I'm stumped.

Lalzy
  • 77
  • 4
  • Are you sure `d:\textfile.txt` is empty? – Paolo Dec 17 '21 at 09:37
  • 2
    Since ``..\`` means to start at current directory parent, this should resolve to `C:\Folder2`. You can do that with `[System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd.Path, "C:\Folder\..\Folder2"))` – Theo Dec 17 '21 at 09:41
  • Yes I am 100% sure that the file is empty. From what I can find out, resolve-path returns an pathinfo object, which add-content can't write into a file(as it expects a string). Why .ToString() doesn't work I have no idea. – Lalzy Dec 17 '21 at 09:50
  • Oh, I assumed that the `..` weren't actually there and you just included them for the purposes of the question. Theo's suggestions should help – Paolo Dec 17 '21 at 09:53
  • 2
    You don't need `.ToString()` by the way... the value of `.Path` is already a string by itself. `Resolve-Path` indeed return a path info but `(Resolve-Path ..).Path` return a string so the `.ToString()` is redundant. – Sage Pourpre Dec 17 '21 at 09:54
  • Well, I've tried: (resolve-path -path "C:\Folder\..\Folder2").Path (resolve-path -path "c:\Folder\..\Folder2" | select -ExpandProperty Path (convert-Path "c:\Folder\..\Folder2") All of which result in an empty file(The file does get created, the output just isn't being written to it). – Lalzy Dec 17 '21 at 10:01
  • 1
    Missed Theo's comment for some reason. That solved it! – Lalzy Dec 17 '21 at 10:07

2 Answers2

3

The simplest way to resolve a relative and/or non-normalized path (one with components such as .. and .) to a normalized full path, directly as a string, is to use the Convert-Path cmdlet:

Convert-Path -LiteralPath "C:\Folder\..\Folder2"

In the context of your command (note that Add-Content appends to a preexisting target file; to write the given content only, use Set-Content):

Add-Content "d:\textfile.txt" (Convert-Path -LiteralPath "C:\Folder\..\Folder2")

Note:

  • Unlike Resolve-Path, Convert-Path returns a file-system-native path, which is generally preferable.

    • This means that file-system paths that are based on PowerShell-only drives (created with New-PSDrive) are resolved to the underlying native file-system location, as understood by outside processes too. (E.g., if MyDrive: is mapped to C:\Projects, Convert-Path -LiteralPath MyDrive:\Foo returns C:\Projects\Foo)
  • Like Resolve-Path, Convert-Path requires that the item the path refers to exist - which is an unfortunate limitation, discussed in GitHub issue #2993.


If the input path refers to a nonexistent file or directory:

.NET offers the [System.IO.Path]::GetFullPath() method, which offers the same functionality also for nonexistent paths.

The challenge is that relative paths are resolved to .NET's current (working) directory, which usually differs from PowerShell's - see this answer for more information.

In PowerShell (Core) 7+, you can work around this problem by specifying the reference path (to resolve the relative path against) explicitly:

# Note: The 'System.' prefix in the type literal is optional.
[IO.Path]::GetFullPath("C:\Folder\..\Folder2", $PWD.ProviderPath)

The automatic $PWD variable refers to PowerShell's current location (directory). Note the use of .ProviderPath, which again ensures uses of a file-system-native path. For maximum robustness - to guard agains the case where the current PowerShell location isn't a file-system location - use (Get-Location -PSProvider FileSystem).ProviderPath.

In Windows PowerShell, where this method overload isn't available, a more cumbersome approach is needed, as suggested by Theo:

# Note: If you don't need to *normalize* the path (resolving . and .. components), 
#       the [IO.Path]::Combine() call alone is enough.
[IO.Path]::GetFullPath(
  [IO.Path]::Combine($PWD.ProviderPath, "C:\Folder\..\Folder2")
)
  • [IO.Path]::Combine() conditionally combines the first path given with the second one, if the latter is relative - otherwise, the latter is used as-is.

  • [IO.Path]::GetFullPath() then ensures normalization of the resulting full path (to resolve any . and .. components); if that isn't required, just calling [IO.Path]::Combine() will do.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Is there any advantage to using Resolve-Path over Convert-Path? – Blaisem Nov 11 '22 at 11:45
  • 1
    @Blaisem, Resolve-Path is for _PowerShell_ paths and therefore supports all [providers](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Providers), not just file-system paths. However, for paths based on _PowerShell-only_ file-system drives, it will _not_ resolve to the underlying, _file-system-native path_, which is what you need for passing a path to the outside world (external program or .NET API). – mklement0 Nov 11 '22 at 14:56
0

If your path is not existent yet, I found either of the following to work successfully:

[IO.Path]::GetFullPath(".\abc")
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\abc.txt")
Doug
  • 6,446
  • 9
  • 74
  • 107
  • Because .NET's working directory can differ from PowerShell's (it will if you've executed `Set-Location` in your session), `[IO.Path]::GetFullPath(".\abc")` isn't robust. In PowerShell _Core_ you can fix that with `[IO.Path]::GetFullPath(".\abc", $PWD.ProviderPath)` . In _Windows PowerShell_, unfortunately, you'll either need `[IO.Path]::GetFullPath([IO.Path]::Combine($PWD.ProviderPath, ".\abc"))` (if path _normalization_ isn't needed, you can omit the `GetFullPath()` call) or use the "lineful" you're showing as the second option. – mklement0 Nov 11 '22 at 15:34