Reputation: 19063
I'd like to convert a path to a relative path in a PowerShell script. How do I do this using PowerShell?
For example:
Path to convert: c:\documents\mynicefiles\afile.txt
Reference path: c:\documents
Result: mynicefiles\afile.txt
And
Path to convert: c:\documents\myproject1\afile.txt
Reference path: c:\documents\myproject2
Result: ..\myproject1\afile.txt
Upvotes: 53
Views: 38138
Reputation: 27756
Since Powershell 7.4.0 you can use new Resolve-Path
parameter -RelativeBasePath
to do this in one line. The Set-Location
workaround has finally become obsolete:
$pathToConvert = 'c:\documents\mynicefiles\afile.txt'
$referencePath = 'c:\documents'
Resolve-Path -Relative -Path $pathToConvert -RelativeBasePath $referencePath
Output:
.\mynicefiles\afile.txt
NOTE:
The arguments for -Path
and -RelativeBasePath
must both be existing paths! There is another restriction, which appears to be a bug in the current implementation though - the arguments must be filesystem paths.
The advantage of the .NET method System.IO.Path.GetRelativePath
is that it works with non-existing paths as well.
Upvotes: 4
Reputation: 1686
Sometimes I have multiple "roots" from which I want to generate relative file paths. I have found Resolve-Path -Relative
unusable in this kind of situation. Changing a global setting, current location, in order to generate a relative file path seems error-prone and (if you're writing parallel code) possibly not thread-safe.
The following should work in early or recent versions of Powershell and Powershell Core, doesn't change your current directory, even temporarily, and is OS-independent and thread-safe.
It doesn't address the second example from OP (inserting ..
as required.)
function Get-RelativePath {
param($path, $relativeTo)
# strip trailing slash
$relativeTo = Join-Path `
(Split-Path -Parent $relativeTo) `
(Split-Path -Leaf $relativeTo)
$relPath = Split-Path -Leaf $path
$path = Split-Path -Parent $path
do {
$leaf = Split-Path -Leaf $path
$relPath = Join-Path $leaf $relPath
$path = Split-Path -Parent $path
} until (($path -eq $relativeTo) -Or ($path.Length -eq 0))
$relPath
}
An example:
PS> $configRoot = 'C:\Users\P799634t\code\RMP\v2\AWD'
PS> $queryPath = 'C:\Users\P799634t\code\RMP\v2\AWD\config_queries\LOAD_UNQ_QUEUE_QUERY2.sql'
PS> Write-Host (Get-RelativePath $queryPath $configRoot)
config_queries\LOAD_UNQ_QUEUE_QUERY2.sql
It behaves reasonably when one file path is not a sub-path of the other:
PS> $root = 'C:\Users\P799634t\code\RMP\v2\AWD'
PS> $notRelated = 'E:\path\to\origami'
PS> Write-Host (Get-RelativePath $notRelated $root)
E:\path\to\origami
Upvotes: 1
Reputation: 531
Using the built-in System.IO.Path.GetRelativePath is simpler than the accepted answer:
[System.IO.Path]::GetRelativePath($relativeTo, $path)
Upvotes: 18
Reputation: 4333
There is a good answer here, but it changes your current directory (it reverts back), but if you really need to isolate that process, code example below can help. It does the same thing, just in a new PowerShell instance.
function Get-RelativePath($path, $relativeTo) {
$powershell = (Get-Process -PID $PID | Get-Item)
if ([string]::IsNullOrEmpty($powershell)) {
$powershell = "powershell.exe"
}
& $powershell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "& { Set-Location `"$relativeTo`"; Resolve-Path `"$path`" -Relative}"
}
It's really slow though, you should really use the other version unless you absolutely have to use this.
Upvotes: 1
Reputation: 11188
A quick and easy way would be :
$current -replace [regex]::Escape($root), '.'
Or if you want the relative path from your actual current location
$path -replace [regex]::Escape((pwd).Path), '.'
This assumes all your paths are valid.
Upvotes: 0
Reputation: 4038
Here is an alternative approach
$pathToConvert1 = "c:\documents\mynicefiles\afile.txt"
$referencePath1 = "c:\documents"
$result1 = $pathToConvert1.Substring($referencePath1.Length + 1)
#$result1: mynicefiles\afile.txt
And
$pathToConvert2 = "c:\documents\myproject1\afile.txt"
#$referencePath2 = "c:\documents\myproject2"
$result2 = "..\myproject" + [regex]::Replace($pathToConvert2 , ".*\d+", '')
#$result2: ..\myproject\afile.txt
Note: in the second case ref path wasn't used.
Upvotes: -4
Reputation: 19063
I found something built in, Resolve-Path:
Resolve-Path -Relative
This returns the path relative to the current location. A simple usage:
$root = "C:\Users\Dave\"
$current = "C:\Users\Dave\Documents\"
$tmp = Get-Location
Set-Location $root
Resolve-Path -relative $current
Set-Location $tmp
Upvotes: 82