Applying a filter to the contents of an Explorer Browser


Today's Little Program hosts an Explorer Browser but filters the contents to remove DLL files. You can, of course, substitute your own filter. (For example, maybe you want to show only files that changed since the last time the user ran your program, or you might want a view of My Computer but filtered to show only removable drives.)

Remember that Little Programs do little to no error checking, and they don't necessarily demonstrate the best programming style. They're just quick demonstrations. Today's smart pointer library is… (rolls dice) … WRL!

Start with our minimal explorer browser program and make these changes.

#include <shlwapi.h> // PathFindExtensionW
#include <wrl\client.h>
#include <wrl\implements.h>

using namespace Microsoft::WRL;

class FolderFilterNoDLLs :
    public RuntimeClass<RuntimeClassFlags<ClassicCom>,
                        IFolderFilter>
{
 // *** IFolderFilter ***
 IFACEMETHODIMP GetEnumFlags(IShellFolder *psf,
    PCIDLIST_ABSOLUTE pidlFolder, HWND *phwnd,
    DWORD *pgrfFlags) { return S_OK; }

 IFACEMETHODIMP ShouldShow(IShellFolder *psf,
    PCIDLIST_ABSOLUTE pidlFolder,
    PCUITEMID_CHILD pidlItem)
 {
  BOOL fShow = TRUE;
  ComPtr<IShellItem> spsi;
  HRESULT hr = SHCreateItemWithParent(pidlFolder, psf, pidlItem,
                                      IID_PPV_ARGS(&spsi));
  if (SUCCEEDED(hr)) {
   SFGAOF sfgaof;
   hr = spsi->GetAttributes(SFGAO_FILESYSTEM | SFGAO_FOLDER,
                            &sfgaof);
   if (SUCCEEDED(hr) && sfgaof == SFGAO_FILESYSTEM) {
    LPWSTR pszName;
    hr = spsi->GetDisplayName(SIGDN_PARENTRELATIVEPARSING,
                                 &pszName);
    if (SUCCEEDED(hr))
    {
     fShow = CompareStringOrdinal(
                 PathFindExtensionW(pszName), -1,
                 L".dll", -1, TRUE) != CSTR_EQUAL;
     CoTaskMemFree(pszName);
    }
   }
  }
  if (SUCCEEDED(hr)) hr = fShow ? S_OK : S_FALSE;
  return hr;
 }
};

The real work happens in the Should­Show method.

  • Create an IShellItem because it's more convenient.
  • Query the SFGAO_FILE­SYSTEM and SFGAO_FOLDER attributes.

  • If the attributes say "Yes, it's a file system object, and no, it's not a folder"...

    • Get the display name.
    • If the display name ends in .dll, then hide the item.

All that's left is to plug this into the Explorer Browser.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
    BOOL fSuccess = FALSE;
    RECT rc;
    PIDLIST_ABSOLUTE pidl = NULL;
    ComPtr<IFolderFilter> spff;
    ComPtr<IFolderFilterSite> spffs;
    if (SUCCEEDED(CoCreateInstance(CLSID_ExplorerBrowser, NULL,
                         CLSCTX_INPROC, IID_PPV_ARGS(&g_peb))) &&
        GetClientRect(hwnd, &rc) &&
        SUCCEEDED(g_peb->Initialize(hwnd, &rc, NULL)) &&
        SUCCEEDED(g_peb->SetOptions(EBO_NAVIGATEONCE)) &&
        SUCCEEDED(MakeAndInitialize<FolderFilterNoDLLs>(&spff)) &&
        SUCCEEDED(g_peb->QueryInterface(IID_PPV_ARGS(&spffs))) &&
        SUCCEEDED(spffs->SetFilter(spff.Get())) &&
        SUCCEEDED(SHParseDisplayName(
                         L"C:\\Program Files\\Internet Explorer",
                                        NULL, &pidl, 0, NULL)) &&
        SUCCEEDED(g_peb->BrowseToIDList(pidl, SBSP_ABSOLUTE))) {
        fSuccess = TRUE;
    }
    ILFree(pidl);
    return fSuccess;
}

We apply the filter to the IExplorerBrowser by querying for IFolder­Filter­Site and using IFolder­Filter­Site::Set­Filter to attach our "no DLLs" filter.

Bonus reading: Filtering the folders that appear in the Browse for Folder dialog.

Comments (42)
  1. skSdnW says:

    I know this is just a little program but SHParseDisplayName is able to parse L"shell:ProgramFiles\Internet Explorer"...

  2. Ben Voigt says:

    "If the display name ends in .dll, then hide the item." made me think that Raymond, like almost all developers, has "Hide known file extensions" disabled.  And that the Little Program might also have bugs related to super-hidden extensions, hiding non-DLL files named .dll.lnk and .dll.shs.

    But SIGDN_PARENTRELATIVEPARSING doesn't really give a display name.  The docs describe it as a "parsing name".  Whatever that is.

    [You folks extrapolate way too much. -Raymond]
  3. Joshua says:

    [You folks extrapolate way too much. -Raymond]

    Yeah I suppose so. However, we ended up with a curious lemma, "He who doesn't change his developer workstation away from 'Hide extensions of known file types' is a bad developer." I wouldn't be surprised if an exception were found, but the rule works very well.

    [There's a column called "Type". Use it. The problem with showing extensions is that you become susceptible to .jpg.exe attacks. -Raymond]
  4. Abnormality Collision says:

    [There's a column called "Type". Use it. The problem with showing extensions is that you become susceptible to .jpg.exe attacks. -Raymond]

    But the "Type" column doesn't show the file extension, it shows some 'user-friendly' string, and I really don't want to have to learn the mapping between the two.

    Also, if I remember correctly, the "Type" for an extension will change when new applications are installed that handle that file extension.

  5. skSdnW says:

    @Ben Voigt: The parsing name is the real name basically. The other names are for UI usage and might change depending on a users shell settings (Show/hide extensions etc). In most cases the parsing name will give you back the same pidl when passed to SHParseDisplayName/IShellFolder::ParseDisplayName but some special items like control panel (Mouse IIRC) will not round-trip.

  6. Malcolm says:

    Obligatory follow up for nitpickers: Yes, I realise Windows now recognises .jpeg as a JPEG image too...

  7. Nico says:

    All this chitchat is great, but the real questions are:

    Do you really roll a dice? and

    How many sides does it have?

    :)

  8. alegr1 says:

    [The problem with showing extensions is that you become susceptible to .jpg.exe attacks. -Raymond]

    I'm not sure how you came to that conclusion, but a friend of mine can thank "hide extensions" for a lot of pictures lost to a email virus. Some .mp3.vb kind.

  9. Wear says:

    It might be something along the lines of If you are hiding extensions and yet something has an extension you will be very skeptical of that thing.

  10. Joshua says:

    So how does Raymond change a file extension when hide extensions is set? Until I found the switch to show them, I used the command line. It bugged me, and I use the command line all the time. Funny how it comes up more on end-user tasks than programming tasks. (Most recent case: pipe delimited file came in as .CSV)

    [Who are you people who spend all day changing file extensions? Do you run around changing creator and type codes on MacOS too? -Raymond]
  11. InVT says:

    [then you know that anything that shows an extension is trying to fool you]

    That... is really interesting.  I never thought about it that way.  I sincerely hope you write the essay.

  12. Joshua says:

    Notepad. Save as doit.bat got doit.bat.txt

    Save as makefile got makefile.txt (this one had command window open anyway so didn't matter)

    HL7 message in .text file (almost everybody handling HL7 does this) doesn't open too well in notepad due to bare CR being the line terminator.

    Except for the rare case of opening a .docx with a zip reader (to extract images) every case is a case of a text file with the wrong extension (pro tip: Excel munges CSV even when it is comma).

    Mac style type codes don't really get out of trouble. Full MIME would (BeOS actually implememted it) except for Excel's munging of CSV. Maybe convincing mail clients to change the extension on save as to match the MIME type would help.

    [Save as "doit.bat" with quotation marks to override the default extension. -Raymond]
  13. 640k says:

    [Who are you people who spend all day changing file extensions? Do you run around changing creator and type codes on MacOS too? -Raymond]

    Setting the execute bit on files in unix/linux/bsd/osx is a common practice when you want to execute a file. The corresponding task in windows is to rename/add the exe file extension.

    [That still makes no sense. Who takes a .TXT file and changes the extension to .EXE? That's not going to run. -Raymond]
  14. Nitpicker (Corner?) says:

    @Joshua:

    Pop double-quotes around it in the 'File name' box in the Save dialog, e.g. "doit.bat". Notepad now happily saves the file with exactly the file name you specify.

  15. Mark VY says:

    Since Raymond has not written the essay yet, (and I would love to read such an essay), I will attempt to expand the cryptic comment at least a little bit.  There are several cases.

    1. Show extensions: the trick is obvious (ends in .exe)

    2. Hide extensions: the trick is pretty obvious (ends in .jpg, must be a trick.)

    3. Hide extensions but friend's computer shows them: the trick is obvious (see two extensions when you're used to seeing zero)

    4. Show extensions, but friend's computer is hides them: trick is very subtle (see one extension, just like you expected, and possibly even the icon is correct)

    Is this the basic idea, or is there even more to it?

  16. cheong00 says:

    [That still makes no sense. Who takes a .TXT file and changes the extension to .EXE? That's not going to run. -Raymond]

    Actually you "chmod +x" to a text file to make the script file runnable (you may also use "source <filename>" to run it, but not many people bother to do that) So the correct analogy is to change the extension to .BAT/.CMD/.VBS/.PS1 .

  17. foo says:

    I always show both extensions and the type column by habit. Monitors these days have lot's of horizontal pixels. Pro tip: Always be extra careful when using someone else's computer - input devices house all sorts of bacteria!

  18. Malcolm says:

    [There's a column called "Type". Use it. The problem with showing extensions is that you become susceptible to .jpg.exe attacks. -Raymond]

    I'm not quite sure I understand that chain of thought. Most people turn on the display of extensions precisely so they can tell if they are looking at Hotpicture.jpg or Hotpicture.jpg.exe.

    It's quite simple to see if the filename ends in .exe, .bat, .pif, .vbs or other executable extensions. Or, inverting that, to see that it *does not* end in .jpg, like you expected. Why is hiding that better?

    [Showing extensions always means that you will be faked out when you borrow a machine that doesn't have extensions always. Whereas if you don't show extensions, then you know that anything that shows an extension is trying to fool you. I have an essay in my head about this subject, which is too long to fit in a comment. -Raymond]
  19. h.v.dijk says:

    Trusting the file name to correctly show the extension is unsafe even on your own machine. Consider the file name not.an.‮gpj.exe‬. Would you realise that this is actually not.an.(UNICODE RTL OVERRIDE)gpj.exe? The Type column shows "Application", though, so if you rely on the Type column, you'll know not to trust that file.

  20. Anon says:

    I'm bemused. I'm a 8+ years developer and never once have I considered whether to show or hide file extensions. What reason could I have for caring about this seemingly trivial detail? I'm genuinely asking.

  21. Liquorice says:

    [Who are you people who spend all day changing file extensions? Do you run around changing creator and type codes on MacOS too? -Raymond]

    Some file transferring softwares do not accept certain file extensions (blocking .exe is reasonable, but I wonder why some softwares block .rar)

  22. Count Zero says:

    Asking - non-MS developers out of pure curiosity:

    All developers I know (including my own, humble self) with only one exception use/prefer 3rd party file managers instead of Windows Explorer. Most of us prefer the two-paneled one. (Having regard on Raymond I will not mention the name, I think we all know what software I'm talking about.) That file manager and its clones provide a more development-oriented access to the file system, could easily be handled with keyboard, and provides a lot of services like its own file viewer, FTP client etc. My question would be this:

    Do you developers really use Windows Explorer?

    p.s.: This comment did not meant to be a criticism of the Explorer. I think it is perfectly fits the needs of a regular user and even some powerusers. Only I think the alternative fits better for developers.

  23. @Anon says:

    If you have a number of files with the same "name" but different "extensions" in the same folder, you want to differentiate the files by their real, full name:

    a.groupproj

    a.groupproj.local

    a.dsk

    a.~dsk

    a.exe

    a.ini

    a.dpr

    a.dproj

    a.dproj.local

    a.identcache

    Some of them even show the same value in the "Type" column (in my example, .dpr and .dproj are both shown as "Delphi Project File").

  24. @Count Zero says:

    Just one aspect: I have not used FTP for more then 10 years (since no longer working on Unix), and I do not expect to need it in the coming years. So this feature is of no use under Windows. For what scenario do you need it?

    As for the keyboard thing: The days of floppy disks/ZIP drives are over, and due to corporate network, I have no much use at work for other changeable media like USB sticks either. I do not move/copy groups of files that often. So, to be able to do this by one keystroke is also not so important for me.

  25. Count Zero says:

    @[Since you did not provide a nick, I can't address you, but I hope you are reading this.]

    I might have written FTP but it is also capable of scp and a variety of other protocols. Also you might not copy/move group of files that often, but I do. We have a few network shares where common files are stored. (Definitely not sources. Sources are on the version tracker server. But there are utilities, documents, stuff like that.)

    Most of the other developers (known by me) move huge amount of data (files) too. And we search file contents of file trees (sometimes based on a regex query), and archive stuff with one keystroke. And view log files with a text viewer, and side-by-side compare files of different origin. And compare/synchronise folders i.e. on test environments for side-by-side testing on two virtual PCs. And batch rename files. Do I have to continue, or you got the basic idea?

  26. Nitpicker (Corner?) says:

    ... and Raymond beats me to the punch.

    *whingewhinge, something about comments/edits not appearing in real-time, even though I'm well aware it would be a pain in the bum with JavaScript (or whatever else) to run it, sucked-up bandwidth, client/server time dedicated to it, crycry*

  27. cheong00 says:

    [Pop double-quotes around it in the 'File name' box in the Save dialog, e.g. "doit.bat". Notepad now happily saves the file with exactly the file name you specify.]

    Last time I checked, for Save dialogs, you just need to change the filter dropdown to "All files (*.*)" and it'll not append extension for you.

  28. Eddie says:

    Raymond, the only time I really need to change the extension of a file is when I create a TXT file and then change it to H or CPP.  I know, I should really be using Visual Studio to create a source file, and I do.  Sometimes, for whatever reason, I find it somehow better to create a TXT file, rename to CPP, and then add existing file in Visual Studio.  I think it has to do with working with some solutions that have crazy subdirectories, in which case I create the source file in Explorer rather than Visual Studio.  Again, I don't claim that it makes full rational sense, but it is how I work.

    Having said that, the reasons you give for preferring to hide extensions is really interesting.  I'll have to give this a try for a couple of weeks and see how it goes.

    [There's no law that says you can't open a command prompt. You probably have one open already. -Raymond]
  29. Joshua says:

    Hmmm let's see how this would go (looks in home directory)

    AppData

    .bash_history

    Downloads

    .gpg

    .local

    My Documents

    My Pictures

    .NET

    .NET Reflector

    .profile

    .putty.rnd

    .ssh

    .vimrc

    To be fair, they finally did fix the leading . fiasco in Win 8.1 but I'm still on Win 7. That quoted filename trick will cone in handy.

  30. @Count Zero says:

    You asked, I answered. I'm just curious about this "moving huge amount of data(files)" on a regular basis: As a software developer, I'm working in my IDE, with the database & DB tools, with MS Word, other tools as well. Most of the time, the files I work with already exists and under version controlled: Why copying large amounts of files around, often? Only now and then, and for this occasions, the Explorer is enough.

    Besides, I even have to google "scp"....

  31. Count Zero says:

    @[The anonimvs guy/girl who keeps addressing me without introducing him/herself.]

    "Why copying large amounts of files around, often?"

    For example when I share the documentation of a database layer (which is - of course - in plain text) with more than two other developers and don't wish to re-send it on a regular basis, I compress my text files (we are talking about a few dozen files in case of a full db layer documentation) by pressing [ALT] + [F5] directly on a network share then press [TAB] to jump to the file path, [ALT] + [INS] to copy the file path, then [ALT] + [TAB] to Outlook and press [CTRL] + [V] to share the link to the docs. In 5 sec.

    "As a software developer, I'm working in my IDE, with the database & DB tools, with MS Word, other tools as well."

    I'm working with multiple IDEs too, but lots of times there are reasons to write code in outer editors. I also use SQL studio, but I don't trust any database that is not exported as a generator script (which I usually strip from unnecessary contents manually). I almost never use Word (or other word processors) since I don't believe in the overformatting of documentation.

    "Most of the time, the files I work with already exists"

    And how exactly does it counteract with batch moving/renaming files?

    "and under version controlled"

    So you are version controlling log files produced during a unit test? Or communication logs? Debug screens? Test data? I don't think those are meant to be stored in a version control system.

    "Besides, I even have to google "scp""

    Well, seemingly I transfer files more often than you.

  32. Eddie says:

    @Count Zero:

    I am a long time developer for the Windows platform.  I currently develop C++ and C# applications.  I use Explorer all the time.  In fact, I always have one instance permanently running.

    With each new version of Windows I find myself liking Explorer more and more.  Yes, there is an initial shock of change (Win9x/NT4 to XP, and then XP to Vista) and sometimes there is something I don't like.  Overall, I like Explorer.

    I said that I have one instance permanently running.  Seldom do I open a second or third instance (usually to drag-n-drop something) but then I close that second instance once I'm done.  Why?  Well, once you practice using Explorer navigation, breadcrumbs, and shortcuts you'll find that you can get a lot of mileage out of one Explorer window instance.  And I do bounce around a lot of different directories, including network shares.  That is my experience and work habits.  To each his own.

  33. DWalker says:

    @Raymond: [Showing extensions always means that you will be faked out when you borrow a machine that doesn't have extensions always.]

    Yep, which is why, if we had the time machine, we would go back and NOT implement the "hide extensions" option.  Then we wouldn't be in this mess.

    As others have pointed out, often more than one extension maps to the same "Type" and it's often handy to distinguish between them.

    Also, the extension is visually CLOSE to the file name in the Explorer window, and the Type column is much further away.  Since the Type takes lots of horizontal space, I can turn off the Type column in my four-pane Explorer replacement (which I use sometimes, along with Windows Explorer) and have more room for other, useful columns.

  34. Malcolm says:

    It's interesting to see the differing points of view on this issue.

    Developers and sysadmins (I fall mostly into the latter camp, my development skills are not that great... although I can create some pretty funky scripts) tend to turn off the hiding of file extensions because it obfuscates what is really going on. Users, who unless they have been using computers since the Dark Ages of DOS/Windows 3.1, don't even realise file extensions exist, don't even know they can turn it on. But there's a knowledge gap there ... because, not knowing about the relevance of file extensions or their existence, means your average user gets caught out - because they're unlikely to check the 'Type' column and Windows tends not to warn you when the type is something unexpected.

  35. Nico says:

    I think @Abnormality Collision already gave the main reason I enable showing file extensions.  The Type column may replace some of the need for file extensions, but it has several deficiencies:

    * The value can change as programs come and go and are upgraded.

    * The value can be ambiguous or duplicated across multiple types.

    * The value is an arbitrary string that can be misleading or just wrong.

    * The column is only visible in Details view, which most "normal" users do not use, and isn't visible on the Desktop.

    If I'm in a situation where I suddenly DO care about the type of a file, then I usually want *exact* information, and these issues are all unacceptable.  Personally, I find the Type column only really useful for sorting (grouping) by file type, since that's not really affected by those issues.

  36. Count Zero says:

    @Eddie - Finally an answer instead of heckling. Thank you. (Based upon what you have "said" I picture your Explorer usage like the one notable exception I've mentioned.) But I don't really know if we "doublepanellers" are a minority amongst developers (and you "explorers" are the majority), or is it vica-versa. As I mentioned almost all devs I personally know are "doublepanellers", but I might not have a representative sample (not sure about the word - English is nor my first, neither my second language).

  37. Eddie says:

    @Count Zero

    I've used double panel file managers, they are nice, but it never grew on me.  In college I used all sorts of UIs, from various window managers of Linux, NextSTEP, Sun Solaris (CDE, and it sucked), and Silicon Graphics (remember them?) IRIX.  One of them (I forget) had a file manager that was multi-panel (more than two) where each panel was basically a breadcrumb of the current path.  It was cool and all, but it didn't become something I had to have.

    For the most part, I spent a lot of time in xterm windows and so I began practicing and getting used to pushd/popd shell commands to better navigate around the file system (if you are a shell guy then you really want those commands to be second nature).  Today, being a Windows user, the Explorer window having back/forward buttons gives me that pushd/popd behavior.  That is an example of how Explorer is sufficient to my developer needs.

  38. Count Zero says:

    @Eddie

    Wow, multiple (more than two) panels does not seem to be a good feature. I mean you lose the benefit of having exactly two panels and can transfer/compare/copy/swap files between them with a single keystroke. I mean the double panel tools can have "tabs" on each panel, but there is only one active panel at a time.

  39. Jon says:

    "Showing extensions always means that you will be faked out when you borrow a machine that doesn't have extensions always."

    Not a problem because I always turn on extensions whenever borrowing a machine. I'm surprised that you don't. Why hide information?

    I want to know if a file is a JPG, not that its registered handler is a "Contoso Image File". And extensions are better that "Type" for screen real estate.

  40. Eddie says:

    "Showing extensions always means that you will be faked out when you <emphasis>borrow a machine</emphasis> that doesn't have extensions always"

    That is why I stopped customizing software and learned the defaults.  I make my desktop completely customized and when I ask a coworker for assistance he or she finds my computer unusable.  Likewise, if somebody asks me for assistance and I go to his computer I find myself like a fish out of water because my customizations are no longer present.  A similar phenomenon exists with installed software.  I used to have like 50 utility apps installed, but nobody else has them installed, so I'm useless on other computers.  Now I have only 3 apps installed I can't live without but everything else is done with the features that comes out of the box with Windows and Visual Studio.  It is also why I became obsessed with trying to design best-fit defaults.  I'm very glad that you and your colleagues on the shell team have debates, and perhaps heated arguments, about default settings for the GUI.

    To everyone else who suggests to simply change the hide-extensions setting when on other computers, let me tell you something...  Nothing pisses me off more when I ask for assistance and the person who helps starts changing 20 settings for reasons unknown to me.  I find that very rude.  Dude, I asked you why I'm getting this strange compiler syntax error.  Do you really need to toggle "Show protected operating system files" in my Explorer window?!  People do this a lot, apparently they feel the need to pretend to be kernel debuggers when taking care of simple user-land IT issues.  When I use other people's computers I use their desktop as is, even if it does slow me down.

  41. Marc K says:

    @Raymond "Showing extensions always means that you will be faked out when you borrow a machine that doesn't have extensions always.": Not at all.  It will be glaringly obvious the first time a folder is opened and there are files listed without extensions.  Yes, there is the edge case that the very first folder I open only has 1 file in it and it is the picture.jpg.exe file.  But, let's not nitpick.  :)

    @Eddie "I used to have like 50 utility apps installed": I put my 50 utilities on a network share.  Now they are available all of the time.  For family/friends support, I have them on a USB drive.

  42. Eddie says:

    @Marc K

    Putting them on a network share doesn't solve any problem, it only creates new problems.  First, are the utilities there so that other people can install them?  Well, what if they don't want to?  I don't own the company I work for, nor am I the CTO/CEO.  Thus, I can't pass an edict that forces all developers to install my favorite utilities.  Second, am I supposed to install them myself when I'm on someone else's computer?  Well, perhaps I can if I log in as me, but the scenario is that I'm helping somebody else so we are logged in with that person's profile.  In that case it would be rather rude of me (see my earlier comment of how I feel when people start changing things on my desktop under my profile).  Third, there wouldn't be 50 utilities on the network share.  There would be 300 utilities on the network share.  Why?  Because it turns out I like Notepad++, but Alice likes Notepad2, Bob likes EditPad Pro, and Charlie likes Vim for Windows.  OK, now I went from being a developer to now politicking and campaigning for why my favorite utility is better than everyone else's favorite utility.  And you know the attack ads will come out (I like RegEx Coach, what do you use, yeah, well you are an idiot for using that, the cool and smart kids use RegEx Coach).  I'd rather write code.  Fourth, even if other people were willing to use my preferred utilities there is an issue of money.  Some of the utilities are not free.  That's right, sometimes I pay for software if I find that it really helps my day-to-day activities better than the free alternatives.  I have no problem shelling out $24.99 for a utility app.  Other people do have a problem.  Instant deal breaker.  (Puzzles me, truthfully, because we are all white collar employees that are paid very well, you *really* can't afford $24.99?  But they always tell me to have kids so that I'll know what it is like to pinch pennies)

    As for family and friends, moot point.  Today they use iPhone/iPad.  If they can't get something done then they resort to their MacBook Air.  I'm the only one with a PC running Windows.  Far cry from the early days when all my family and friends had Dells or Gateways (remember them?) running Windows.

Comments are closed.

Skip to main content