Determine whether the current item is a directory or a file

English support forum

Moderators: Stefan2, Hacker, petermad

Post Reply
User avatar
Galizza
Senior Member
Senior Member
Posts: 271
Joined: 2018-09-07, 05:21 UTC
Location: Galiza (Spain)

Determine whether the current item is a directory or a file

Post by *Galizza »

 
Hi, I'd like a way to tell if the currently focused item is a file or a directory so I can use this information in scripts. Currently, I use the cm_CopyFullNamesToClip command and then check if the last character in the clipboard is a backslash. But I imagine there must be an easier way and ideally one that also works within archives (Zip), although I haven't found it searching the forum.

Thanks :!:
 
 
 Looking through a glass onion
 
User avatar
beb
Power Member
Power Member
Posts: 762
Joined: 2009-09-20, 08:03 UTC
Location: Odesa, Ukraine

Re: Determine whether the current item is a directory or a file

Post by *beb »

2Galizza
Interesting question.

I've been using the following approach, and it works perfectly for me (however, without archives and clipboard yet):

User command (usercmd.ini):

Code: Select all

[em_test_tc_params_files_folders]
cmd=pwsh -c "%commander_path%\Plugins\app\PowerShell\echoParamFiles.ps1"
param=%WL '%P' '%T' %Q
User button:

Code: Select all

TOTALCMD#BAR#DATA
em_test_tc_params_files_folders

WCMICON2.DLL,21
tc params %WL %P %T %Q files and/or folders

0
-1
PowerShell script:
echoParamFiles.ps1

Code: Select all

$time = [diagnostics.stopwatch]::StartNew()
$path = [IO.DirectoryInfo]$pwd.path

if ($path){
'current and parent path'|Write-Host -f DarkGray;$path.FullName;$path|split-path}
''
if ($args){
'cmd=pwsh: Total Commander parameters breakdown...'|Write-Host -f Yellow
"param=%WL '%P' '%T' %Q [%Y|%y]"|Write-Host -f Gray
''
'%WL list file'|Write-Host -f Cyan
$list = [IO.FileInfo]$args[0];$list.Name
''
'%P source path'|Write-Host -f Cyan
$source = [IO.DirectoryInfo]$args[1];$source.FullName
''
'%T target path'|Write-Host -f Cyan
$target = [IO.DirectoryInfo]$args[2];$target.FullName}
''
if (-not($args)){$warning = 'no parameters have been passed'}
if ($args -and $list.length -le 2){$warning = '%WL list file is empty'}
if ($warning){$message = 'working directory $pwd'
'{0}, using {1} contents instead...' -f $warning,$message|Write-Host -f Yellow
$files = Get-ChildItem -path $path -force -file # add -recurse if needed
$folders = Get-ChildItem -path $path -force -directory}
if ($list.length -gt 2){$message = '%WL list file'
'{0} contents...' -f $message|Write-Host -f Yellow
$lines = [IO.File]::ReadLines($list)
$files = foreach ($line in $lines){
if ((Get-Item $line -force) -is [IO.FileInfo]){[IO.FileInfo]$line}}
$folders = foreach ($line in $lines){
if ((Get-Item $line -force) -is [IO.DirectoryInfo]){[IO.DirectoryInfo]$line}}
}

# working with files and folders
'{0} files in {1}' -f $files.count,$message|Write-Host -f Cyan
# do something with files: here, display full path
foreach ($file in $files){$file.FullName}
'{0} folders in {1}' -f $folders.count,$message|Write-Host -f Cyan
# do something with folders: here, display full path
foreach ($folder in $folders){$folder.FullName}
''
$time.Stop()
'{0} files and {1} folders in {2} for {3:mm\:ss\.fff}' -f
$files.count,$folders.count,$message,$time.Elapsed|Write-Host -f DarkCyan
'by {0}' -f $MyInvocation.MyCommand.Name
sleep -s 33
From the beginning, it wasn't intended for archives and the clipboard.
As the entry steps to make it work with those:
  • Archives: I need to add %Z to the input parameters
  • Clipboard: I need to put somewhere inside the script something like this: $clipboard = (Get-Clipboard)|foreach {$_}
  • Then I would need to build new internal logic for the script to process the new data accordingly, respecting its specifics in different scenarios.
That will be relatively easy with the clipboard, since it's just another array of strings, where each string can be checked against the file system and properties.
And I've just made and tried a primitive standalone test script while writing this message, and it works with the cm_CopyFullNamesToClip perfectly:
test_clipboard_for_files_and_or_folders.ps1:

Code: Select all

$time = [diagnostics.stopwatch]::StartNew()

$clipboard = (Get-Clipboard)|foreach {$_}
$candidates_for_files_folders = $clipboard|foreach {if (Test-Path -Literal $_){$_}}
$files = $candidates_for_files_folders|foreach {if ((Get-Item $_ -force) -is [IO.FileInfo]){[IO.FileInfo]$_}}
$folders = $candidates_for_files_folders|foreach {if ((Get-Item $_ -force) -is [IO.DirectoryInfo]){[IO.DirectoryInfo]$_}}

# primitive output
'files:'|Write-Host -f Yellow
$files
$files.count ? 'files count {0}' -f $files.count : 'none'|Write-Host -f Cyan
$files|foreach{$_.FullName}|Write-Host -f Green
''
'folders:'|Write-Host -f Yellow
$folders
$folders.count ? 'folders count {0}' -f $folders.count : 'none'|Write-Host -f Cyan
$folders|foreach{$_.FullName}|Write-Host -f Green
''
# finalize
$time.Stop()
'{0:mm\:ss\.fff} by {1}' -f $time.Elapsed,$MyInvocation.MyCommand.Name

sleep -seconds 33
Note: you need PowerShell 7+ to utilise the ternary operator (<condition> ? <if-true> : <if-false>) as in my script.

To do something with the strings that are the paths that lead inside the archives, it looks like I need to do a bit of research at first :)
For instance, the following command will look inside the archive, but the script doesn't know what to do with that yet (in the current edition, it just throws errors "Cannot find path 'e:\xternal\path\to\some\archive.zip\internal\path\to.something' because it does not exist"). :)
User command (usercmd.ini):

Code: Select all

[em_test_tc_params_files_folders_Z]
cmd=pwsh -c "%commander_path%\Plugins\app\PowerShell\echoParamFiles.ps1"
param=%WL '%P' '%T' %Q %Z
#278521 User License
Total Commander [always the latest version, including beta] x86/x64 on Win10 x64/Windows 11/Android 17
Fla$her
Power Member
Power Member
Posts: 4033
Joined: 2020-01-18, 04:03 UTC

Re: Determine whether the current item is a directory or a file

Post by *Fla$her »

Galizza wrote: 2026-05-24, 15:30 UTC and ideally one that also works within archives (Zip)
If we talk not only about files and folders, but also about pseudo files and pseudo folders (in wfx it may be something else), then on Autorun it is checked like this:

Code: Select all

LoadLibrary Plugins\Autorun_Tweaks.dll
LoadLibrary Plugins\Autorun_Runtime.dll
#——————————————————————————————————————
# Shift + F — show info about the type of the current item
SetHotkeyAction /K:S /H:F CurrentItemCheck

Func CurrentItemCheck

   ItemCount       = RequestInfo(11001)
   CurrentIndex    = RequestInfo(11007)
   GoToParentExist = RequestInfo(11009)
   FirstFileIndex  = RequestInfo(11011)
   If Not ItemCount Then
      MsgBox('There are no items')
   ElseIf CurrentIndex = 0 And GoToParentExist Then
      MsgBox('Cursor is on [..]')
   ElseIf FirstFileIndex = -1 Or CurrentIndex < FirstFileIndex Then
      MsgBox('Cursor is on a folder')
   Else
      MsgBox('Cursor is NOT on a folder')
   EndIf

EndFunc
Overquoting is evil! 👎
User avatar
beb
Power Member
Power Member
Posts: 762
Joined: 2009-09-20, 08:03 UTC
Location: Odesa, Ukraine

Re: Determine whether the current item is a directory or a file

Post by *beb »

beb wrote: 2026-05-24, 21:18 UTC ...but the script doesn't know what to do with that yet
Well, for now, it does know something.

So, meet archive_peeper, which is standalone at the time, will catch cm_CopyFullNamesToClip as its input, and try to do something with that.
Tested with current PowerShell 7.6.
https://github.com/powershell/powershell/releases

Its regex uses zip|7z|rar|tar|gz, however, it natively fully understands only ZIP via the built-in [System.IO.Compression.ZipArchive] .NET class.

archive_peeper_via_IOCompressionZipArchive.ps1

Code: Select all

# Requires -Version 7.6

$time = [Diagnostics.Stopwatch]::StartNew()

# execute cm_CopyFullNamesToClip, then run the script
# the script will catch the clipboard contents and look for something interesting in there

foreach ($string in (Get-Clipboard)) {

# make sure an extra trailing blank space in the clipboard won't break or print false errors
if ([string]::IsNullOrWhiteSpace($string)) { continue }

# Display current input string
Write-Host $string -noNewLine

# Archive path syntax check (regex)
if ($string -match '^(?<ZipPath>.*?\.(zip|7z|rar|tar|gz))\\(?<InternalPath>.*)$') {
    $zipFile      = $Matches['ZipPath']
    $internalPath = $Matches['InternalPath'].Replace('\','/')

    Write-Host ' string looks like a valid archive path!' -f Cyan

    # File existence check
    if (Test-Path -Path $zipFile -PathType Leaf) {
        Write-Host ('File existence: found archive on disk at: "{0}"' -f $zipFile) -f DarkCyan

        # Integrity and internal item check
        $stream = $null
        $archive = $null

        try {
            $stream = [IO.File]::OpenRead($zipFile)
            $archive = [IO.Compression.ZipArchive]::new($stream)
            Write-Host 'Integrity Check: file verified as an authentic archive.' -f Blue

            # Standardize folder targets: archives denote folders with a trailing '/'
            # We check for both the exact string and the string with a trailing slash
            $folderPathPattern = $internalPath.EndsWith('/') ? $internalPath : "$internalPath/"

            # Find matching entry
            $matchedEntry = $archive.Entries | Where-Object {
                $_.FullName -eq $internalPath -or $_.FullName -eq $folderPathPattern } | Select-Object -First 1

            if ($matchedEntry) {
                # Explicit archive directory marker (ends with /) or has a size of 0
                if ($matchedEntry.FullName.EndsWith('/') -or $matchedEntry.Length -eq 0) {
                    'Target item is an existing FOLDER: "{0}"' -f $internalPath|Write-Host -f DarkYellow
                } else {
                    'Target item is an existing FILE: "{0}"' -f $internalPath|Write-Host -f Green
                }
            } else {
                # Implicit folder check: the folder itself might not have an explicit entry,
                # but files might exist inside it (e.g., path is "folder" and "folder/file.txt" exists).
                $hasChildItems = $archive.Entries.FullName | Where-Object { $_ -like "$folderPathPattern*" }

                if ($hasChildItems) {
                    'Target item is an implicit FOLDER (contains nested items): "{0}"' -f $internalPath|Write-Host -f DarkYellow
                } else {
                    'Warning: archive structure is valid, but internal path "{0}" does not exist!' -f $internalPath|Write-Warning
                }
            }
        }
        catch {
             Write-Error 'Integrity check failed: file is either corrupt, unreadable, or a fake archive!'
        }
        finally {
            if ($archive) { $archive.Dispose() }
            if ($stream) { $stream.Dispose() }
        }

    } else {
        'File existence check failed: no file exists at: "{0}"' -f $zipFile|Write-Error
    }
} else {
    Write-Host ' string does not looks like an archive path.' -f Yellow
}
''
}

# Finalize
$time.Stop()
'{0:mm\:ss\.fff} by {1}' -f $time.Elapsed,$MyInvocation.MyCommand.Name

sleep -seconds 33
Tomorrow, we will try to teach it to work with 7-Zip and, therefore, effectively look into other archives.
#278521 User License
Total Commander [always the latest version, including beta] x86/x64 on Win10 x64/Windows 11/Android 17
Fla$her
Power Member
Power Member
Posts: 4033
Joined: 2020-01-18, 04:03 UTC

Re: Determine whether the current item is a directory or a file

Post by *Fla$her »

2beb —>
Galizza wrote: 2026-05-24, 15:30 UTCBut I imagine there must be an easier way
See my post above. There is neither a clipboard, nor archive analysis, nor the use of parameters.
Overquoting is evil! 👎
User avatar
beb
Power Member
Power Member
Posts: 762
Joined: 2009-09-20, 08:03 UTC
Location: Odesa, Ukraine

Re: Determine whether the current item is a directory or a file

Post by *beb »

2Fla$her
I'm going my way here and will go that way until I decide it's enough.
In that regard, I'm not interested in seeing your post, neither above, nor anywhere else, sorry.
I know your approach, and for me your tool is a kind of black box, which I don't wish to rely on.

Galizza, and other users, however, might then decide what is useful for them to know and to use.
#278521 User License
Total Commander [always the latest version, including beta] x86/x64 on Win10 x64/Windows 11/Android 17
Fla$her
Power Member
Power Member
Posts: 4033
Joined: 2020-01-18, 04:03 UTC

Re: Determine whether the current item is a directory or a file

Post by *Fla$her »

2beb
You have some kind of sick reaction. I'm not interested in your way or anyone else's, I'm interested in the underlined nuance.
When you create your topic with the name of your favorite shell in the title, there you can set your own rules.
And what I have shown, with some differences, can be repeated on powershell.
In that regard, I'm not interested in seeing your post, neither above, nor anywhere else, sorry.
If you don't want to see someone's posts, there is a Manage foes option in the User Control Panel on the last tab.
for me your tool is a kind of black box, which I don't wish to rely on.
Without argumentation, this answer tells me nothing. There is a concept of goal setting, which is logical to proceed when choosing a tools. One thing does not replace the other, no matter how much you would like the opposite.
Overquoting is evil! 👎
User avatar
Galizza
Senior Member
Senior Member
Posts: 271
Joined: 2018-09-07, 05:21 UTC
Location: Galiza (Spain)

Re: Determine whether the current item is a directory or a file

Post by *Galizza »

2beb & 2Fla$her
Thanks :!:
 
 Looking through a glass onion
 
User avatar
beb
Power Member
Power Member
Posts: 762
Joined: 2009-09-20, 08:03 UTC
Location: Odesa, Ukraine

Re: Determine whether the current item is a directory or a file

Post by *beb »

2Galizza
My pleasure.
And:
beb wrote: 2026-05-24, 21:18 UTC Tomorrow, we will try to teach it to work with 7-Zip...
  • Optimized [System.IO.Compression.ZipArchive]-based solution:
    It no longer re-reads archives on each input string, but only when necessary.
    archive_peeper_IOCompressionZipArchive_optimized.ps1

    Code: Select all

    $time = [diagnostics.stopwatch]::StartNew()
    
    # operation:
    # 1. execute cm_CopyFullNamesToClip in Total Commander
    # 2. run this script
    
    # initialize storage arrays
    $archives       = @{}
    $validStrings   = [Collections.Generic.List[string]]::new()
    $invalidStrings = [Collections.Generic.List[string]]::new()
    
    # parse clipboard input
    foreach ($string in (Get-Clipboard)) {
    
    # ignore empty lines
    if ([string]::IsNullOrWhiteSpace($string)) { continue }
    
    # archive path syntax check
    if ($string -match '^(?<ZipPath>.*?\.(zip|7z|rar|tar|gz))\\(?<InternalPath>.*)$') {
    
        $validStrings.Add($string)
        $file         = $Matches.ZipPath
        $internalPath = $Matches.InternalPath.Replace('\','/')
    
        # create archive group if needed
        if (-not $archives.ContainsKey($file)) {
            $archives[$file] = [Collections.Generic.List[string]]::new()
        }
    
        # store internal path
        $archives[$file].Add($internalPath)
    }
    else {
        $invalidStrings.Add($string)
        }
    } # end for each string loop
    
    # report strings syntax check
    if ($validStrings.count) {
        Write-Host 'Strings look like valid archive paths:' -f Cyan
        $validStrings | foreach {" $_"}
    }
    
    if ($invalidStrings.count) {
        Write-Host 'Strings do NOT look like archive paths:' -f Yellow
        $invalidStrings | foreach {" $_"}
    }
    
    # process archives
    ''
    Write-Host 'Processing archives...' -f Yellow
    ''
    foreach ($file in $archives.Keys) {
    
    Write-Host $file -f Cyan -noNewLine
    
    # archive existence check
    if (-not (Test-Path -Path $file -PathType Leaf)) {
        Write-Error ('archive does not exist: "{0}"' -f $file)
        continue
    }
    
    Write-Host ' archive exists on disk' -f Green
    
    $stream  = $null
    $archive = $null
    
    Write-Host 'Reading archive structure... ' -noNewLine
    
    try {
        # open archive once
        $stream  = [IO.File]::OpenRead($file)
        $archive = [IO.Compression.ZipArchive]::new($stream)
    
        Write-Host 'archive integrity verified' -f Green
    
        # build fast lookup table
        $entryMap = @{}
    
        foreach ($entry in $archive.Entries) {
            $entryMap[$entry.FullName] = $entry
        }
    
        # process internal paths
        Write-Host "Checking internal paths:"
        foreach ($internalPath in $archives[$file]) {
        Write-Host " $internalPath" -f White -NoNewline
    
        # normalize folder path
        $folderPathPattern =
            if ($internalPath.EndsWith('/')) {
                $internalPath
            } else {
                "$internalPath/"
            }
    
        # exact file/folder match
        $matchedEntry = $null
    
        if ($entryMap.ContainsKey($internalPath)) {
            $matchedEntry = $entryMap[$internalPath]
        }
        elseif ($entryMap.ContainsKey($folderPathPattern)) {
            $matchedEntry = $entryMap[$folderPathPattern]
        }
    
        if ($matchedEntry) {
            # folder detection
            if ($matchedEntry.FullName.EndsWith('/') -or $matchedEntry.Length -eq 0) {
                Write-Host ' target item is an existing FOLDER' -f DarkYellow
            } else {
                Write-Host ' target item is an existing FILE' -f DarkCyan
            }
    
        } else {
    
            # implicit folder detection
            $hasChildItems = $false
            foreach ($entryName in $entryMap.Keys) {
                if ($entryName.StartsWith($folderPathPattern)) {
                    $hasChildItems = $true
                    break
                }
            }
            if ($hasChildItems) {
                Write-Host ' target item is an implicit FOLDER (contains nested items)' -f DarkYellow
            } else {
                Write-Warning 'archive structure is valid, but internal path does not exist!'
            }
        }
    } # end of for each internal path loop
    } # end of try loop
    
    catch {
        Write-Error 'archive integrity check failed, or archive is unreadable!'
    }
    
    finally {
        if ($archive) { $archive.Dispose() }
        if ($stream)  { $stream.Dispose() }
    }
    ''
    } # end of for each file loop
    
    # finalize
    $time.Stop()
    '{0:mm\:ss\.fff} by {1}' -f $time.Elapsed, $MyInvocation.MyCommand.Name
    sleep -Seconds 33
    
  • 7zip-based solution:
    archive_peeper_7zip_list.ps1

    Code: Select all

    $time = [diagnostics.stopwatch]::StartNew()
    
    # operation:
    # 1. execute cm_CopyFullNamesToClip in Total Commander
    # 2. run this script
    
    # 7-zip path
    $env:Path += ";$env:commander_path\Plugins\app\7zip"
    
    # initialize storage arrays
    $archives       = @{}
    $validStrings   = [Collections.Generic.List[string]]::new()
    $invalidStrings = [Collections.Generic.List[string]]::new()
    
    # parse clipboard input
    foreach ($string in (Get-Clipboard)) {
    
    # ignore empty lines
    if ([string]::IsNullOrWhiteSpace($string)) { continue }
    
    # archive path syntax check
    if ($string -match '^(?<ZipPath>.*?\.(zip|7z|rar|tar|gz))\\(?<InternalPath>.*)$') {
    
        $validStrings.Add($string)
        $file         = $Matches.ZipPath
        $internalPath = $Matches.InternalPath.Replace('\','/')
    
        # create archive group if needed
        if (-not $archives.ContainsKey($file)) {
            $archives[$file] = [PSCustomObject]@{InternalPaths = [Collections.Generic.List[string]]::new()
            }
        }
        # store internal path
        $archives[$file].InternalPaths.Add($internalPath)
    }
    else {
        $invalidStrings.Add($string)
        }
    } # end for each string loop
    
    # report strings syntax check
    if ($validStrings.count) {
        Write-Host 'Strings look like valid archive paths:' -f Cyan
        $validStrings | foreach {" $_"}
    }
    
    if ($invalidStrings.count) {
        Write-Host 'Strings do NOT look like archive paths:' -f Yellow
        $invalidStrings | foreach {" $_"}
    }
    
    # process archives
    ''
    Write-Host 'Processing archives...' -f Yellow
    ''
    foreach ($file in $archives.Keys) {
    
    Write-Host $file -f Cyan -noNewLine
    
    # archive existence check
    if (-not (Test-Path -Path $file -PathType Leaf)) {
        Write-Error ('archive does not exist: "{0}"' -f $file)
        continue
    }
    
    Write-Host ' archive exists on disk' -f Green
    
    # read archive via 7z
    Write-Host 'Reading archive structure... ' -noNewLine
    $raw = & 7z l -slt -- $file 2>&1
    
    # integrity check
    if ($LastExitCode -ne 0) {
       Write-Error 'archive integrity check failed or archive is unreadable'
       continue
    }
    
    Write-Host 'archive integrity verified' -f Green
    
    # build lookup tables
    $fileSet = [Collections.Generic.HashSet[string]]::new()
    $dirSet  = [Collections.Generic.HashSet[string]]::new()
    
    # split 7z output into logical blocks
    $blocks = ($raw -join [char]10) -split '(?:\r?\n){2,}'
    
    foreach ($block in $blocks) {
        $path  = $null
        $isDir = $false
    
        if ($block -match '(?m)^Path = (.+)$') {
            $path = $Matches[1]
        }
    
        if ($block -match '(?m)^Folder = (\+|-)$') {
            $isDir = ($Matches[1] -eq '+')
        }
    
        # some archive formats store dirs with Folder = -
        # but with trailing slash in Path
        if ($path -match '[\\/]$') {
        $isDir = $true
    }
    
        if (-not $path) {
            continue
        }
    
        # normalize slashes and trim trailing slash
        $normalized = $path.Replace('\','/').TrimEnd('/')
        if (-not $normalized) {
            continue
        }
    
        # store directory
        if ($isDir) {
            $dirSet.Add($normalized) | Out-Null
        }
        else {
            $fileSet.Add($normalized) | Out-Null
            # build implicit parent directories
            $parts = $normalized.Split('/')
            if ($parts.count -gt 1) {
                $parent = ''
                for ($i = 0; $i -lt ($parts.count - 1); $i++) {
                    if ($parent) {
                        $parent += '/'
                    }
                    $parent += $parts[$i]
                    $dirSet.Add($parent) | Out-Null
                }
            }
        }
    } # end of for each block loop
    
    # process internal paths
    Write-Host "Checking internal paths:"
    foreach ($internalPath in $archives[$file].InternalPaths) {
        Write-Host " $internalPath" -f White -NoNewline
        # normalize input
        $normalized = $internalPath.Replace('\','/')
        $normalized = $normalized.TrimEnd('/')
    
        # check DIRECTORY first; it must win over FILE
        if ($dirSet.Contains($normalized)) {
            Write-Host ' target item is an existing FOLDER' -f DarkYellow
            continue
        }
        # then check FILE
        if ($fileSet.Contains($normalized)) {
            Write-Host ' target item is an existing FILE' -f DarkCyan
            continue
        }
        Write-Warning ' item does not exist inside archive.'
    
    } # end of for each internal path loop
    ''
    } # end of for each file loop
    
    # finalize
    $time.Stop()
    '{0:mm\:ss\.fff} by {1}' -f $time.Elapsed,$MyInvocation.MyCommand.Name
    sleep -seconds 33
    
    
#278521 User License
Total Commander [always the latest version, including beta] x86/x64 on Win10 x64/Windows 11/Android 17
Post Reply