My desktop gets cluttered with open explorer windows often. Whenever I need quick access to my drives, I press WIN+E, and since my hair is always on fire, these windows tend to remain open until I finally get frustrated and manually close them all. I was wondering if there wasn't a magic trick to close all open explorer windows with PowerShell, and while trying to figure it out, I learned a lot about COM.
Accessing a COM library
How to you find out which explorer windows are open in the first place? Since Windows (including Vista and Server 2008) is still heavily dependant on COM (Component Object Model, the "old" world before .NET came out), you need to contact the appropriate COM library.
Fortunately, that's easy with PowerShell. All you need is New-Object -COMObject. Add the name of the COM library you want to use, and off you go. Most of the UI is handled by a COM library called Shell.Application, so here is how you can access this library:
PS> $lib = New-Object -comObject Shell.Application
PS>
What's "inside" a COM object?
Once you have access to your COM library through $lib, the next thing you want to find out are the object members: which commands ("methods") and information ("properties") does the library provide? With native PowerShell methods, you could pipe the object to Get-Member or append $lib with a dot "." and press TAB to get code completion.
A much more convenient way is integrated into PowerShellPlus: once you type a dot, you get intellisense-like code completion menus.

As it turns out, the method Windows() returns a list of all open explorer windows. Actually, Windows() returns not just explorer windows but also Internet Explorer- and Outlook-Windows. Essentially, it returns any window that uses a ShellWindows Interface. Since all I want is closing my open explorer windows, I need to examine the returned window objects a bit more closely.
By way of experimenting I found that explorer windows have a property called FullName which contains the full path of the executable, so in order to only close explorer windows, I could sort out all unwanted window objects and focus on explorer windows like this:
- $shapp = New-Object -comObject Shell.Application
-
- foreach ($window in $shapp.Windows()) {
- if ($window.FullName -ne $null) {
- if ($window.FullName.ToLower().EndsWith("\explorer.exe")) {
- $window.Quit()
- }
- }
- }
A foreach loop enumerates all open windows and then checks to see if the returned object has a FullName property at all. As it turns out, Windows() also returns Outlook panes which do not have a FullName property. Next, the script checks to see if Fullname ends with "\explorer.exe", making sure we only close real explorer windows. If you wanted to close browser windows instead, all you needed to do was to replace "\explorer.exe" with "\iexplore.exe".
Programming in "Style"
A lot of folks are migrating from VBScript to PowerShell, and the previous example was really VBScript style. This is not so much a question of political correctness but rather functionality. The previous script had in fact flaws.
When you run it, it will not necessarily close all explorer windows. Some may survive your script, and only when you run it a second time will the remaining explorer windows be wiped away. How come?
The problem is the enumeration returned by Windows(). If you start and close windows in the middle of enumerating the very same windows, the enumerator gets confused. Lets say you had six open windows to begin with, then you closed one. The enumerator now will no longer supply the last (sixth) window to your script because it "thinks" there are only five, not taking into account that the closed window was somewhere at the beginning of the collection and that there were originally six windows when the enumeration started. The bottom line is: never change a collection while enumerating it.
In PowerShell, there is a much easier (and better) way to handle this: use the Pipeline! When you throw the result of Windows() into the PowerShell pipeline, apparently changes to the collection do not affect the enumerator (and your script shrinks a lot in size, too). Actually, I tend to think that it is a speed thing and that the pipeline happily accepts all input from Windows() before actually the first window is closed somewhere at the end of the pipeline.
Here is the same thing as one-liner:
PS> $shapp = New-Object -comObject Shell.Application
PS> $shapp.Windows() | ? { $_.FullName -ne $null} |
? { $_.FullName.toLower().Endswith('\explorer.exe') } |
% { 'Closed "{0}"' -f $_.Locationname; $_.Quit() }
Closed "Tobias"
Closed "Documents"
Closed "Music"
Closed "Games"
PS>
Basically, this line throws all ShellWindows objects into the pipeline and then applies two filters ("?" aka Where-Object): it first checks whether there is a property FullName, and it then makes sure the Fullname property ends with '\explorer.exe'. Any object passing the filters must be an explorer window, so at the end the pipeline processes each explorer window ('%' aka Foreach-Object) and first outputs its name (found in the LocationName property), then quits the explorer window using the Quit() method.
Another way to a clean desktop are the methods MinimizeAll() and UNDOMinimizeAll() which in essence do the WIN+M trick programmatically. They minimize and restore all windows, respectively:
PS> $lib.MinimizeAll()
PS> $lib.UndoMinimizeALL()
There are a ton of additional things you can do with the Shell.Application COM object, and there are tons of additional COM objects. The thing to keep in mind is that PowerShellPlus not only provides advanced intellisense-like code completion to PowerShell Cmdlets and .NET but also to COM. Have a great time playing with COM! For example, let PowerShell speak or read files to you:
PS> # get the COM library and assign it to a variable:
PS> $blabla = New-Object -comObject SAPI.SpVoice
PS>
PS> $blabla.Speak("That's cool, dude!")
1
PS> # to get rid of the return value, cast to VOID:
PS> [void]$blabla.Speak("I can also read files!")
PS> # you can also read file content:
PS> [void]$blabla.Speak("c:\autoexec.bat", 4)
Cheerio
Tobias
Posted
Oct 14 2008, 07:04 AM
by
Tobias Weltner