How do I get a path with the correct (canonical) c

2020-04-06 01:04发布

I have a script that accepts a directory as an argument from the user. I'd like to display the name of the directory path as it is displayed in Windows. I.e.,

PS C:\SomeDirectory> cd .\anotherdirectory
PS C:\AnotherDirectory> . .\myscript.ps1 "c:\somedirectory"
C:\SomeDirectory

How do I retrieve "C:\SomeDirectory" when given "c:\somedirectory"?

标签: powershell
4条回答
不美不萌又怎样
2楼-- · 2020-04-06 01:31

The accepted answer only gets the correct case of the file. Parent paths are left with the case provided by the user. Here's my solution.

$getPathNameSignature = @'
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern uint GetLongPathName(
    string shortPath, 
    StringBuilder sb, 
    int bufferSize);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError=true)]
public static extern uint GetShortPathName(
   string longPath,
   StringBuilder shortPath,
   uint bufferSize);
'@
$getPathNameType = Add-Type -MemberDefinition $getPathNameSignature -Name GetPathNameType -UsingNamespace System.Text -PassThru


function Get-PathCanonicalCase
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        # Gets the real case of a path
        $Path
    )

    if( -not (Test-Path $Path) )
    {
        Write-Error "Path '$Path' doesn't exist."
        return
    }

    $shortBuffer = New-Object Text.StringBuilder ($Path.Length * 2)
    [void] $getPathNameType::GetShortPathName( $Path, $shortBuffer, $shortBuffer.Capacity )

    $longBuffer = New-Object Text.StringBuilder ($Path.Length * 2)
    [void] $getPathNameType::GetLongPathName( $shortBuffer.ToString(), $longBuffer, $longBuffer.Capacity )

    return $longBuffer.ToString()
}

I've integrated the above code into Resolve-PathCase, part of the Carbon PowerShell module. Disclaimer: I'm the owner/maintainer of Carbon.

查看更多
家丑人穷心不美
3楼-- · 2020-04-06 01:36

This should work:

function Get-PathCanonicalCase {
    param($path)

    $newPath = (Resolve-Path $path).Path
    $parent = Split-Path $newPath

    if($parent) {
        $leaf = Split-Path $newPath -Leaf

        (Get-ChildItem $parent| Where-Object{$_.Name -eq $leaf}).FullName
    } else {
        (Get-PSDrive ($newPath -split ':')[0]).Root
    }
}
查看更多
▲ chillily
4楼-- · 2020-04-06 01:42

I found a different and simpler approach using PowerShell wild cards.

 $canonicalCasePath = Get-ChildItem -Path $wrongCasingPath.Replace("\","\*") | Where FullName -IEQ $wrongCasingPath | Select -ExpandProperty FullName
  • The first part of the pipe replaces all backslashes in the path by backslash and asterisk \\* and return all matching files
  • The where part makes sure that only the desired file is returned and not any other potential match. IEQ is case insesitive equal
  • The last select part extracts canonical case path of the file
查看更多
beautiful°
5楼-- · 2020-04-06 01:43

Using Christian's GetDirectories suggestion, here's another solution that's not quite as involved:

function Get-PathCanonicalCase
{
    param( $path )

    $newPath = (Resolve-Path $path).Path
    $root = [System.IO.Path]::GetPathRoot( $newPath )
    if ( $newPath -ne $root ) # Handle case where changing to root directory
        { $newPath = [System.IO.Directory]::GetDirectories( $root, $newPath.Substring( $root.Length ) )[ 0 ] }
    $newPath
}

EDIT: Thanks for all the help.

Btw, all I wanted this for was to use in a little utility script overriding the default cd alias, allowing me to specify some 'root' directories that are searched if the path doesn't exist relative to the current directory. I.e., it allows me to cd Documents, cd trunk, cd Release-10.4 regardless of my current location. And it annoyed me to have the prompt in the case that I entered it, instead of its actual case.

# Usage: 
# Set up in $profile - define the functions and reassign 'cd'.  Example:
# -----
#  . .\Set-LocationEx.ps1 "c:\dev\Code", "c:\dev\Code\releases", "$HOME" -Verbose
# if (test-path alias:cd) { remove-item alias:cd > $null }
# Set-Alias cd Set-LocationEx
# -----

param( [parameter(Mandatory = $true)][string[]]$roots )

Set-StrictMode -Version Latest

Write-Verbose "Set-LocationEx roots: $(Join-String -Strings $roots -Separator ', ')"

function Set-LocationEx
{
    param( [Parameter( Mandatory="true" )]$path )

    process
    { 
        $verbose = ( $PSCmdlet.MyInvocation.BoundParameters.ContainsKey( "Verbose" ) -and $PSCmdlet.MyInvocation.BoundParameters[ "Verbose" ].IsPresent )
        if ( $verbose )
            { Write-Verbose( "$(Join-String -Strings $roots -Separator ', ')" ) }
        if ( !( Test-Path $path ) ) 
        {
            foreach ( $p in $roots )
            { 
                $newPath = Join-Path $p $path
                if ( $verbose ) { Write-Verbose "Looking for $newPath" }
                if ( Test-Path $newPath ) 
                { 
                    $newPath = Get-PathCanonicalCase( $newPath )
                    if ( $verbose ) { Write-Verbose "Found $newPath" }
                    Push-Location $newPath
                    return
                } 
            }
        }
        if ( Test-Path $path )
            { $path = Get-PathCanonicalCase( $path ) }
        Push-Location $path
    }
}

function Get-LocationExRoots
{
    process
    {
        Write-Output (Join-String -Strings $roots -NewLine)
    }
}

function Get-PathCanonicalCase
{
    param( $path )

    $newPath = (Resolve-Path $path).Path
    $root = [System.IO.Path]::GetPathRoot( $newPath )
    if ( $newPath -ne $root ) # Handle root directory
        { $newPath = [System.IO.Directory]::GetDirectories( $root, $newPath.Substring( $root.Length ) )[ 0 ] }
    $newPath
}
查看更多
登录 后发表回答