9

I have a file README.TXT. If I issue the following command:

PS> Get-Item ReadMe.txt

...then it returns "ReadMe.txt". I want to find out the actual name of the file on disk, including case. How do I get it to return "README.TXT"?

I ask because I'm trying to track down a problem with case-insensitive filenames on Windows versus case-sensitive files on a Unix box, and I want to get the actual case used on the Windows box.

More detail: I have a list of files (stored in a .CSPROJ file) which are in a different case from those stored on disk. I want to be sure that they match. For example: if the .CSPROJ file says "ReadMe.txt", but the file on disk is "README.TXT", sometimes editing the file in Visual Studio rewrites the file as "ReadMe.txt", which then confuses Perforce, because it's case-sensitive, and the filename no longer has the case it was expecting. I want to write a script that spots the mismatched filenames, so that I can do something about them before it causes a problem.

4

Here's what I came up with:

function Get-ActualFileName($file) {
    $parent = Split-Path $file
    $leaf = Split-Path -Leaf $file

    $result = Get-ChildItem $parent | where { $_ -like $leaf }
    $result.Name
}
  • Thanks for this. But I need this for the FullName. What can I change? – masterxilo Aug 20 '19 at 20:11
2

The following PowerShell function is based on another developer's elegant solution for C# at https://stackoverflow.com/a/11436327/854680.

Because Get-ChildItem is not used, it is much faster, which makes a difference when calling this function dozens or hundreds of times.

function Get-FileNameTrueCase {
    Param (
        [parameter(Mandatory=$true)]
        [string]
        $DirOrFile
    )

    if (-not ( (Test-Path $DirOrFile -PathType Leaf) -or (Test-Path $DirOrFile -PathType Container) ) ) {
        # File or directory not found
        return $DirOrFile
    }

    $TruePath = "";
    foreach ($PathPart in $DirOrFile.Split("\")) {
        if ($TruePath -eq "") {
            $TruePath = $PathPart.ToUpper() + "\";
            continue;
        }
        $TruePath = [System.IO.Directory]::GetFileSystemEntries($TruePath, $PathPart)[0];
    }
    return $TruePath;
}
  • Thanks for this variant which outputs a full path. Basically, this does the same as shift-right-click on a file in explorer, and selecting "copy as path". – masterxilo Aug 20 '19 at 20:12
1

The bulk of the file system work in PowerShell is done by some .NET classes in the System.IO namespace. If you know the path (like in your example, you've navigated to the file system location where your subject file is located), then you could use something like

(get-item $pwd).GetFiles('readme.txt')

Since the base .NET classes are case sensitive, the resulting output of your file names will be case sensitive as well.

For reference, the in the example, you are calling the GetFiles method of the System.IO.DirectoryInfo object that represents the current working directory ($pwd). You can change that to point to the directory containing the file you need to confirm the casing on.

The Name, BaseName, and FullName properties should all reflect the proper casing.

More info on the GetFiles method.

  • 1
    No, those return the path however you typed it. E.g.: cd C:\WINDOWS\SYSTEM32; (get-item $pwd).GetFiles(".\w32time.dll").FullName # Prints "C:\WINDOWS\SYSTEM32\.\w32time.dll" which is not only cased wrong, but also has a \.\ in it because that's how I typed it.... – Jaykul Sep 28 '13 at 4:21
1

Rather than specifying the exact name, just use a wildcard:

PS> Get-Item ReadMe.tx?

this should return README.TXT

  • Better hope a file Readme.tx0 does not exist – scobi Dec 31 '16 at 14:48
0

The fastest way I can think of is to replace all the slashes with *\ and then call Get-Item, and to make sure you get no dupes, pick the one that's case-insensitively equal to the one you specified.

$Path = Get-Item "$($Path.TrimEnd('\/') -replace "(?<!:)(\\|/)", '*$1')*" |
           Where { $_.FullName -ieq $Path } | Convert-Path
0

Here's my quick and dirty (no validation) approach:

Function Resolve-PathName { [CmdletBinding()]
Param(
    [Parameter(ValueFromPipelinebyPropertyName,ValueFromPipeline,Mandatory)]
    [Alias("Fullname")]
    [string] $File,
    [switch] $Cygpath
) Process {
    $cur = $File
    $stack = New-Object System.Collections.Stack

    while ( $cur.length -gt 0) {
        $stack.Push( (split-path $cur -Leaf) )
        $cur = split-path $cur
    }
    $cur = get-item ($stack.pop())
    while ( $stack.Count -gt 0) {
        $cur = $cur | Get-ChildItem -Filter ($stack.Pop())
    }
    if ($Cygpath) {
        cygpath $cur.Fullname
    } else {
        $cur.Fullname
    }
}}
  • Why should there be CygWin on a production machine? – Deer Hunter Nov 4 '15 at 8:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.