explorer.exe windows w duplicate names

Started by fhammer, May 03, 2016, 11:40:16 AM

Previous topic - Next topic

fhammer

I want to create a set of @NORMAL desktop explorer.exe windows, then size/arrange them in a specific pattern. The problem is that, although each window displays the contents of a particular path\directory, the window-name does not include the path (only the the directory name). Therefore I can end up with multiple explorer.exe windows with the same window-name, although each, in reality, represents a different directory.

How can I unambiguously reference the windows in my script?

Note: Although I am creating the explorer.exe windows within my script, there is a possibility that a duplicatly-named window may already exist, prior to running the script.

Thank you!

td

you didn't indicate your Windows version but generally you can get the full path from an Explore window.  On approach is to do something like the following rough example:
Code (winbatch) Select
objShell = ObjectCreate("Shell.Application")
objWindows = objShell.Windows
cnt = objWindows.Count -1

; Loop once for each shell window
For item = 0 To cnt
   
   ; Get the Window Object
   objWin = objWindows.Item(item);
   if StrIndexNc(objWin.FullName,'explorer.exe', 1, @FWDSCAN)
      Message('Explorer Path', objWin.locationURL)
   endif
next

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

Two comments:

1) When I run the above script, I get OLE Exception on line 2.

2) Although I can't be sure, it looks like your program retrieves the full path of the EXPLORER.EXE executable file.  I think the OP is asking for the directory that is being displayed by the explorer instance.  I.e., if I do:

explorer C:\tmp

(to display/work with the contents of C:\tmp)

then the sought after piece of information is C:\tmp.

Of course, I'm just guessing about both of these assumptions...

It seems likely that it is possible, probably by digging around in the Win32Process class, to retrieve the current working directory of the explorer process.  That will probably (although with no guarantee) be the directory that Explorer is displaying.

fhammer

Yes, I'm looking for the directory being displayed by a particular explorer window. For example, there could be two open windows, both named "SOURCE". However one window is displaying contents of C:\SOURCE and the other is displaying the contents of C:\xxx\SOURCE.

Thanks.

snowsnowsnow

Quote from: fhammer on May 03, 2016, 05:06:54 PM
Yes, I'm looking for the directory being displayed by a particular explorer window. For example, there could be two open windows, both named "SOURCE". However one window is displaying contents of C:\SOURCE and the other is displaying the contents of C:\xxx\SOURCE.

Thanks.

In order to really solve this, I'd need to know more about your real problem.  Why do you need this?
How often do you need it?  How efficient does it have to be?  How much work are you willing to invest in it?  (I.e., all the usual questions).

It seems clear that there is no native WinBatch solution - no magic bullets in WinBatch itself to solve this.  One way or another, you're going to have to invoke some external code.  My first thought was using WMI and the Win32ProcessClass; unfortunately, Win32Process doesn't seem to have "current working directory" (too bad, that).  After a bit of Googling, I found this:

https://download.sysinternals.com/files/Handle.zip

which contains a program called Handle.EXE, which seems to be a stripped down/command line version of Process Explorer (a really nice, big, complex, GUI program, that I highly recommend, BTW).  Anyway, Handle.EXE seems like it will solve your problem.  I did a little testing, and it looks like if you run:

handle -p {processId_Of_an_ExplorerWindow}

and then parse the output, looking for lines containing the string "RWD", you should be able to figure out which directory EXPLORER.EXE is displaying.  In my tests, it was always the first one (i.e., the first one with RWD), but you may not want to rely on that.

I'm sure there are other utilities and such out there, but this one looks pretty good to me.

td

Quote from: snowsnowsnow on May 03, 2016, 03:28:15 PM
Two comments:

1) When I run the above script, I get OLE Exception on line 2.

Works fine on Windows 7 as long as you are using the WinBatch exe with the correct security level and uiaccess.

Quote
2) Although I can't be sure, it looks like your program retrieves the full path of the EXPLORER.EXE executable file.  I think the OP is asking for the directory that is being displayed by the explorer instance.  I.e., if I do:

No, locationURL contains the full path of the directory contents being displayed by the Explorer window.

Quote
It seems likely that it is possible, probably by digging around in the Win32Process class, to retrieve the current working directory of the explorer process.  That will probably (although with no guarantee) be the directory that Explorer is displaying.

Problem is that Explorer has multiple threads.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

Quote from: fhammer on May 03, 2016, 05:06:54 PM
Yes, I'm looking for the directory being displayed by a particular explorer window. For example, there could be two open windows, both named "SOURCE". However one window is displaying contents of C:\SOURCE and the other is displaying the contents of C:\xxx\SOURCE.

Thanks.

The example posted above does exactly that on Windows 7 (haven't tried it on other versions of Windows but it likely works the same) in URL form, i.e.,  full path preceded by 'file:///'.  The exception is shell special folders like 'library' where it will simply show the special folder name.  For example, if the shell window is currently displaying the c:\logs directory, locationURL will contain 'file:///c:/logs. Have you actual tried it?
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

The example above also works on Windows 10 and XP.  On Windows 10 when the Explorer locationURL is an empty string because Explorer is displaying a shell special folder like 'This PC' use the 'LocationName' property to get the name of the special folder.

Code (winbatch) Select

strFolder = objWin.LocationURL
if strFolder == "" then strFolder = objWin.LocationName
Message('Explorer Path', strFolder)
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

fhammer

That will help. Thanks. You people are great (and tolerant of old newbies)!

As I mentioned in the original post, I'm trying to create a bunch of explorer windows, then size and arrange them on the desktop (for convenience). I can easily create them via the Run command, e.g. Run("explorer.exe","/n /select,C:\xxx\Source"). However, when I then attempt to size and move the window to the upper left part of the desktop, e.g. WinPlaceSet(@NORMAL,"Source","10 10 350 700") ...  other windows named "Source" may sometimes exist. I'm currently trying to create and arrange multiple explorer windows for which many of the paths end the same. What it comes down to (I think) is that I need a way to ambiguously reference (in the WinPlaceSet call) a window that I just created.

Maybe there is an easier way to accomplish that. Could I assume that WinPlaceSet will always reference the most-recently-created window named "Source"? I'll experiment with that. If so, I would still use your other suggestions to determine if there are any prexisting explorer windows with names I intend to create, and which are displaying the directories of interest.



td

Speaking of easier ways. I am sure this isn't one of them but maybe it will get you started in the right direction.

Code (winbatch) Select
AddExtender("wwctl44i.dll")

strDir = 'C:\projects\test' ; Your path here.
strParms = '/n /select,"'
strParms := strDir:'"'
strDirUrl = StrReplace(strDir,'\','/')  ; Make slashes URL compliant.

ShellExecute("explorer.exe",strParms,'',@normal,'')
TimeDelay(.5) ; Wait for window to instantiate.
objShell = ObjectCreate("Shell.Application")
objWindows = objShell.Windows
cnt = objWindows.Count -1

; Loop once for each shell window
For item = 0 To cnt
   
   ; Get the Window Object
   objWin = objWindows.Item(item);
   if StrIndexNc(objWin.FullName,'explorer.exe', 1, @FWDSCAN)
      strFolder = objWin.LocationURL
      if strFolder == "" then strFolder = objWin.LocationName
      if StrIndexNc(strFolder,strDirUrl,1, @FWDSCAN) 
         WinPlaceSet(@NORMAL,cWinIdConvert(objWin.hwnd),"10 10 350 700")
      endif
   endif
next



"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

fhammer

Solved my problem with the WinIdGet function, which appears to be designed for duplicate-window-name situations. Apologies to "td" and "snowsnowsnow" for not doing more research and experimenting, prior to the original post. Thank you.

td

WinIdGet uses exactly the same window title search as WinPlaceSet does when you specify a window name so how did WinIdGet solve your problem?
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

td

Here is a rough example of another approach that may work as long as some assumptions hold true.

Code (winbatch) Select
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Borrows heavily from PopMenu's menu file.

; Positions an explorer Window based on
; the directory it is displaying.
; (assumes directory for each Explorer Window is
; unique
#DefineFunction ExplorerPlace( strDir, strCoors )
   strName = '~':ItemExtract(-1, strDir, '\')
   if !WinWaitExist(strName, 5) then return 0
   WinPlaceSet( @NORMAL, strName, strCoors  )
   ;WinActivate( strName ) ; Uncomment to give window focus
   return 1
#EndFunction

; Set the Explorer window class.
if WinVersion(1) >= 6 then nClassCode  = 1
else nClassCode  = 0

; Kill existing Explorer windows.
lWinIds = IntControl( 31, nClassCode, 0, 0, 0 )  ;Returns a list of ids of explorer windows
nCnt = ItemCount( lWinIds, @TAB )
for i=1 to nCnt
   WinClose(ItemExtract(i, lWinIds, @Tab))
next

; Display new Explorer windows.
strDir = 'C:\Projects'
ShellExecute("explorer.exe",'/n /select,"%strDir%"','',@normal,'')
ExplorerPlace(strDir, '0 0 500 450')
strDir = 'C:\Logs'
ShellExecute("explorer.exe",'/n /select,"%strDir%"','',@normal,'')
ExplorerPlace(strDir, '500 0 1000 450')
strDir = 'C:\Users'
ShellExecute("explorer.exe",'/n /select,"%strDir%"','',@normal,'')
ExplorerPlace(strDir, '0 450 500 900')
strDir = 'C:\Temp'
ShellExecute("explorer.exe",'/n /select,"%strDir%"','',@normal,'')
ExplorerPlace(strDir, '500 450 1000 900')

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

fhammer

The WinIdGet function returns an ID for the most-recently-accessed window, of the specified window name. Since I am creating the windows in my script, I can always get a unique ID, as long as I call WinIdGet immediately after creating the window. The example in the Help addresses this specific case.

td

As previously mentioned WinIdGet works exactly the same way using exactly the same functionality that WinPlaceSet uses to identify a window.   
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

snowsnowsnow

Quote from: td on May 13, 2016, 10:43:23 PM
As previously mentioned WinIdGet works exactly the same way using exactly the same functionality that WinPlaceSet uses to identify a window.

I think you sort of have to read between the lines here.

It seems clear to me that OP's model is not:

1) Create a window
2) Resize that window
3) Repeat until done

But rather it is:

1) Create a bunch of windows
2) Go back and manipulate them all (as a group).

In fact, it is quite possible that resizing the windows isn't really the point at all - just a simplification for posting to the board purposes.  In any case, it makes sense to me that the following is a sensible model:

1) Create a window
2) Use WinIdGet() to store away a WindowID for the window just created into an array.
3) Repeat until done
4) Manipulate the windows (as a group) using the array of IDs.


td

It doesn't really matter if the windows are all created before they are placed.  Either  WinIdGet or WinPlaceSet 'returns an ID for the most-recently-accessed window, of the specified window name.'  It is not just the most recently created window but the most recently created window with a given name. 

An advantage of using WinIdGet is that you only have to search for the Window once when you are using more than a couple of the Win* functions on the window identified.  This is because the Win* functions do not have to perform a search for the the window when they already have an id.  Basically, it is a way to enhance a script's performance a little bit.  If the OP is creating several Explorer windows with the same starting directory and not immediately using any Win* functions on them then it would make some sense to use WinIdGet.  In fact, this last point was the response I was expecting but did not get from the OP when I asked the question originally.

In any of these scenarios there is  some risk of things not working out as expected when relying on the most resent created window feature of these functions because you are working in an interactive environment.  On Windows 10 it is even less reliable because Windows Runtime windows all get enumerated before desktop windows. 

All the preceding verbiage doesn't  really matter,  if the OP is satisfied with his current results.  I was just curious about what the OP was up to.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade