How to enumerate the real Windows File Explorer windows

I'm trying to enumerates all open File Explorer windows in a PowerShell script.
I have already found on other posts how to enumerate all explorer.exe windows instances, for example using the Shell.Application COM API:

(New-Object -com "Shell.Application").windows()

But this actually returns more than I want:
I want only the "real" File Explorer windows showing actual files on my disk or network, not the "fake" explorer.exe instances that are just containers for various Control Panel windows, etc.
So basically the list of instances shown when hovering the mouse over the File Explorer icon on the Taskbar.
How can this be done reliably, and preferably in a way that works in Windows 7 to 11?

My script already contains C# declarations encapsulating WIN32 API calls, so C# code snippets would also do.


2021-10-10 update:

The best algorithm I've found so far, building on @simon-mourier's answer, can summarized this way:

$self = $window.Document.Folder.Self
$ClassID = $Self.ExtendedProperty("System.NamespaceCLSID")
$BaseClassID = $Self.Path.Substring(2,38) # With proper tests to clear it if it's not a UUID

$FileExplorerIDs = ( # The few known types which are file systems, but don't set $Self.IsFileSystem
  # Windows 10
  "f02c1a0d-be21-4350-88b0-7367fc96ef3c", # Network
  "679f85cb-0220-4080-b29b-5540cc05aab6", # Quick Access
  "20d04fe0-3aea-1069-a2d8-08002b30309d", # This PC
  # Windows 7
  "031e4825-7b94-4dc3-b131-e946b44c8dd5"  # Libraries
)

if ($Self.IsFileSystem) {
  $AppType = "File Explorer"
} elseif ($FileExplorerIDs -contains "$ClassID") {
  $AppType = "File Explorer"
} elseif ($BaseClassID -eq "{26EE0668-A00A-44D7-9371-BEB064C98683}") {
  $AppType = "Control Panel"
} elseif ("{$ClassID}" -eq "{D20EA4E1-3957-11D2-A40B-0C5020524153}") {
  $AppType = "Control Panel"       # Windows 7 Administrative Tools
} elseif ($Self.Name -eq $Self.Path) { # TODO: Improve this test, which is very weak
  $AppType = "Search Results"      # Ex: "Search Results in Indexed Locations"
} else {
  $AppType = "Unknown"
}

The full algorithm, with the proper precautions to eliminate undefined fields, or invalid values, etc, is implemented in this script: https://github.com/JFLarvoire/SysToolsLib/blob/master/PowerShell/ShellApp.ps1

Upvotes: 2

Views: 979

Answers (2)

mklement0
mklement0

Reputation: 437448

It seems that only file-path-based File Explorer windows have a non-$null .LocationUrl property value, so you can filter by that:

  • Caveat: Jean-François reports that this approach doesn't work for Explorer windows that are open to a file-system folder located on a connected smartphone, in which case .LocationUrl is apparently $null too.
$explorerWinsWithFilePaths = 
  (New-Object -com "Shell.Application").Windows() | Where-Object LocationUrl

To extract the file paths that these windows are displaying (the technique also works with non-file locations such as Quick Access, which translate into ::-prefixed GUIDs):

$explorerWinsWithFilePaths.Document.Folder.Self.Path

See Jean-François' comment below for examples of what windows showing folders on a connected smartphone report.

Upvotes: 0

Simon Mourier
Simon Mourier

Reputation: 138841

One solution is to test whether the Shell Folder (IShellFolder) beneath the Shell View that Windows sends back is handled by the Windows file system or by some custom folder.

For that, you can use the System.NamespaceCLSID Windows property. If the folder associated with the view is handled by the file system, this property value will be the ShellFSFolder GUID value which equal to f3364ba0-65b9-11ce-a9ba-00aa004ae837 (from Windows SDK shobjidl_core.h).

You can test it with something like this in PowerShell:

$ShellFSFolder = [System.Guid]::New("f3364ba0-65b9-11ce-a9ba-00aa004ae837")

foreach($win in (New-Object -com "Shell.Application").Windows()) {
    $clsid = $win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID")
    if ($clsid -ne $null) {
        $clsid = [System.Guid]::New($clsid)
        if ($clsid -eq $ShellFSFolder) {
            Write-Host $win.Document.Folder.Self.Path
        }
    }
}

And like this in C#:

var ShellFSFolder = new Guid("f3364ba0-65b9-11ce-a9ba-00aa004ae837");

dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
foreach (var win in shell.Windows)
{
    var clsid = win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID");
    if (clsid != null)
    {
        Guid guid;
        if (clsid is byte[] bytes)
        {
            guid = new Guid(bytes);
        }
        else
        {
            guid = new Guid((string)clsid);
        }
        
        if (guid == ShellFSFolder)
        {
            Console.WriteLine(win.Document.Folder.Title); // for example
        }
    }
}

Upvotes: 3

Related Questions