This is a function I wrote to circumvent the 260 char path MAX_LENGTH in Win32 API on which all of the cmdlets in PowerShell are based.
Usage: $short_path = Shorten-Path “some-long-path” “temporary-directory-path”
What the function will do is simply make a junction point in the temporary directory, and return a shorter path that points to the same place as the original long path, but through the junction point. This is only if the path is too long: if not, then it will return the original path.
# Returns a shortened path made with junctions to circumvent 260 path length in win32 API and so PowerShell
function Shorten-Path {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
HelpMessage="Path to shorten.")]
[string]$Path,
[Parameter(Mandatory=$true,
Position=1,
ValueFromPipeline=$false,
HelpMessage="Path to existing temp directory.")]
[string][ValidateScript({Test-Path -LiteralPath $_ -PathType Container})]$TempPath
)
begin {
# Requirements check
if (-not $script:junction) {$script:junction = @{};}
$max_length = 248; # this is directory max length; for files it is 260.
}
process {
# First check whether the path must be shortened.
# Write-Warning "$($path.length): $path"
if ($Path.length -lt $max_length) {
Write-Debug "Path length: $($Path.length) chars.";
return $Path;
}
# Check if there is allready a suitable symlink
$path_sub = $junction.keys | foreach { if ($Path -Like "$_*") {$_} } | Sort-Object -Descending -Property length | Select-Object -First 1;
if ($path_sub) {
$path_proposed = $Path -Replace [Regex]::Escape($path_sub), $junction[$path_sub];
if ($path_proposed.length -lt $max_length) {
# assert { Test-Path $junction[$path_sub] } "Assertion failed in junction path check $($junction[$path_sub]) for path $path_sub.";
return $path_proposed;
}
}
# No suitable symlink so make new one and update junction
$path_symlink_length = ($TempPath + '\' + "xxxxxxxx").length;
$path_sub = ""; # Because it is allready used in the upper half, and if it is not empty, we get nice errors...
$path_relative = $Path;
# Explanation: the whole directory ($Path) is taken, and with each iteration, a directory node is taken from
# $path_relative and put in $path_sub. This is done until there is nothing left in $path_relative.
while ($path_relative -Match '([\\]{0,2}[^\\]{1,})(\\.{1,})') {
$path_sub += $matches[1];
$path_relative = $matches[2];
if ( ($path_symlink_length + $path_relative.length) -lt $max_length ) {
$tmp_junction_name = $TempPath + '\' + [Convert]::ToString($path_sub.gethashcode(), 16);
# $path_sub might be very large. We can not link to a too long path. So we also need to shorten it (i.e. recurse).
$mklink_output = cmd /c mklink /D """$tmp_junction_name""" """$(Shorten-Path $path_sub $TempPath)""" 2>&1;
$junction[$path_sub] = $tmp_junction_name;
# assert { $LASTEXITCODE -eq 0 } "Making link $($junction[$path_sub]) for long path $path_sub failed with ERROR: $mklink_output.";
return $junction[$path_sub] + $path_relative;
}
}
# Path can not be shortened...
# assert $False "Path $path_relative could not be shortened. Check code!"
}
end {}
} |
# Returns a shortened path made with junctions to circumvent 260 path length in win32 API and so PowerShell
function Shorten-Path {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
HelpMessage="Path to shorten.")]
[string]$Path,
[Parameter(Mandatory=$true,
Position=1,
ValueFromPipeline=$false,
HelpMessage="Path to existing temp directory.")]
[string][ValidateScript({Test-Path -LiteralPath $_ -PathType Container})]$TempPath
)
begin {
# Requirements check
if (-not $script:junction) {$script:junction = @{};}
$max_length = 248; # this is directory max length; for files it is 260.
}
process {
# First check whether the path must be shortened.
# Write-Warning "$($path.length): $path"
if ($Path.length -lt $max_length) {
Write-Debug "Path length: $($Path.length) chars.";
return $Path;
}
# Check if there is allready a suitable symlink
$path_sub = $junction.keys | foreach { if ($Path -Like "$_*") {$_} } | Sort-Object -Descending -Property length | Select-Object -First 1;
if ($path_sub) {
$path_proposed = $Path -Replace [Regex]::Escape($path_sub), $junction[$path_sub];
if ($path_proposed.length -lt $max_length) {
# assert { Test-Path $junction[$path_sub] } "Assertion failed in junction path check $($junction[$path_sub]) for path $path_sub.";
return $path_proposed;
}
}
# No suitable symlink so make new one and update junction
$path_symlink_length = ($TempPath + '\' + "xxxxxxxx").length;
$path_sub = ""; # Because it is allready used in the upper half, and if it is not empty, we get nice errors...
$path_relative = $Path;
# Explanation: the whole directory ($Path) is taken, and with each iteration, a directory node is taken from
# $path_relative and put in $path_sub. This is done until there is nothing left in $path_relative.
while ($path_relative -Match '([\\]{0,2}[^\\]{1,})(\\.{1,})') {
$path_sub += $matches[1];
$path_relative = $matches[2];
if ( ($path_symlink_length + $path_relative.length) -lt $max_length ) {
$tmp_junction_name = $TempPath + '\' + [Convert]::ToString($path_sub.gethashcode(), 16);
# $path_sub might be very large. We can not link to a too long path. So we also need to shorten it (i.e. recurse).
$mklink_output = cmd /c mklink /D """$tmp_junction_name""" """$(Shorten-Path $path_sub $TempPath)""" 2>&1;
$junction[$path_sub] = $tmp_junction_name;
# assert { $LASTEXITCODE -eq 0 } "Making link $($junction[$path_sub]) for long path $path_sub failed with ERROR: $mklink_output.";
return $junction[$path_sub] + $path_relative;
}
}
# Path can not be shortened...
# assert $False "Path $path_relative could not be shortened. Check code!"
}
end {}
}
In $junction variable, all the junction points are stored, so you can remove them afterwards with:
foreach ($link in $junction.values) {
$rmdir_error = cmd /c rmdir /q """$link""" 2>&1;
if ( $LASTEXITCODE -ne 0 ) { Write-Warning "Removing link $link failed with ERROR: $rmdir_error." };
}
$junction.clear(); |
foreach ($link in $junction.values) {
$rmdir_error = cmd /c rmdir /q """$link""" 2>&1;
if ( $LASTEXITCODE -ne 0 ) { Write-Warning "Removing link $link failed with ERROR: $rmdir_error." };
}
$junction.clear();
I am also using the assert function which you can find here.