windowswinapiwindows-shellshell32

Opening Folder in New Tab of Existing Shell Window


Note: This question only applies to Windows 11.

I want to open a folder in a new tab of an existing shell window. It seems that there is no official API.

I have tried two method:

The first, most obvious one, was the ShellExecute function with the verb opennewtab (This verb comes from inside of the Shell key of the Folder ProgID in the registry -[HKEY_CLASSES_ROOT\Folder\shell\opennewtab]). But, it didn't work. The function just opens a new window for the folder.

For second chance, I have examined the opennewtab verb in the registry. It is implemented as an IExecuteCommand (through DelegateExecute). So, my second try was to retrieve the interfaces from its COM server, set their required parameters via the methods they provide (the SetXXX family of functions for IExecuteCommand, the Initialize method of IInitializeCommand etc.), and finally call the Execute method.

To learn what Windows Shell passes to these methods and in what order it calls them, I implemented my custom verb implementation and its COM server, and replaced the old COM server registered with opennewtab with my server.

Here you can see the output displayed by DebugView (my verb implementation outputs using OutputDebugString).

enter image description here

As you can see, it doesn't call all the methods of the interfaces. So, I thought that it should have been pretty easy by setting the same parameters. But, yes, there are some things I didn't take into account. This approach generated a Null dereferencing exception with no success.

Note: I am aware of that the second one is "hacky" one.

Note: The CLSID that implements the default verb handler is CLSID_ExecuteFolder.

How can we open a folder in a new tab?


Solution

  • The first solution is to use the official UI Automation API.

    Microsoft UI Automation is an accessibility framework that enables Windows applications to provide and consume programmatic information about user interfaces (UIs). It provides programmatic access to most UI elements on the desktop. It enables assistive technology products, such as screen readers, to provide information about the UI to end users and to manipulate the UI by means other than standard input. UI Automation also allows automated test scripts to interact with the UI.

    This code opens a c:\temp folder (or select one already opened) and adds a new tab to the Explorer window:

    int main()
    {
      CoInitialize(nullptr);
    
      // get a pidl for a given folder
      LPITEMIDLIST pidl;
      if (SUCCEEDED(SHParseDisplayName(L"c:\\temp", nullptr, &pidl, 0, nullptr)))
      {
        // open the folder (or activate an already matching opened one)
        SHOpenFolderAndSelectItems(pidl, 1, (LPCITEMIDLIST*)&pidl, 0);
    
        // find window that displays this pidl
        IShellWindows* windows;
        if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&windows))))
        {
          // build buffer from pidl
          VARIANT url;
          VariantInit(&url);
          if (SUCCEEDED(InitVariantFromBuffer(pidl, ILGetSize(pidl), &url))) // propvarutil.h
          {
            long hwnd = 0;
            IDispatch* disp;
            VARIANT empty;
            VariantInit(&empty);
            windows->FindWindowSW(&url, &empty, SWC_BROWSER, &hwnd, 0, &disp);
            VariantClear(&url);
            VariantClear(&empty); // useless but we always pair calls
    
            if (disp) // should be null but we never know...
            {
              disp->Release();
            }
    
            if (hwnd)
            {
              //needs UIAutomationCore.h & UIAutomationClient.h
              IUIAutomation* automation;
              if (SUCCEEDED(CoCreateInstance(CLSID_CUIAutomation8, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&automation)))) // or CLSID_CUIAutomation
              {
                // get UIA element from handle
                IUIAutomationElement* window;
                automation->ElementFromHandle((UIA_HWND)(INT_PTR)hwnd, &window);
                if (window)
                {
                  // window's class should be 'ShellTabWindowClass'
                  // create inconditional "true" condition
                  IUIAutomationCondition* trueCondition;
                  automation->CreateTrueCondition(&trueCondition);
                  if (trueCondition)
                  {
                    // create a tree walker to determine window's parent
                    IUIAutomationTreeWalker* walker;
                    automation->CreateTreeWalker(trueCondition, &walker);
                    if (walker)
                    {
                      IUIAutomationElement* parent;
                      walker->GetParentElement(window, &parent);
                      if (parent)
                      {
                        // create a condition to find the first button with AutomationId property set to AddButton
                        VARIANT v;
                        V_VT(&v) = VT_BSTR;
                        V_BSTR(&v) = SysAllocString(L"AddButton");
                        IUIAutomationCondition* condition;
                        automation->CreatePropertyCondition(UIA_AutomationIdPropertyId, v, &condition);
                        VariantClear(&v);
                        if (condition)
                        {
                          IUIAutomationElement* button;
                          // search button
                          parent->FindFirst(TreeScope_Subtree, condition, &button);
                          if (button)
                          {
                            // a button implements the "Invoke" pattern
                            IUnknown* unk;
                            button->GetCurrentPattern(UIA_InvokePatternId, &unk);
                            if (unk)
                            {
                              IUIAutomationInvokePattern* pattern;
                              unk->QueryInterface(&pattern);
                              if (pattern)
                              {
                                // press the button
                                pattern->Invoke();
                                pattern->Release();
                              }
                              unk->Release();
                            }
                            button->Release();
                          }
                          condition->Release();
                        }
                        parent->Release();
                      }
                      walker->Release();
                    }
                    trueCondition->Release();
                  }
                }
                automation->Release();
              }
              CoUninitialize();
            }
          }
          windows->Release();
        }
        CoTaskMemFree(pidl);
      }
      CoUninitialize();
      return 0;
    }
    

    To be able to program UIA you generally first analyse the window's structure for example using the Inspect tool from Windows SDK or the newer Accessibility Insights to understand what actions you should execute. Here's an inspect screenshot on hightlighted Explorer's "Add new tab" button that tells us this button has an Automation Id property set to "AddButton":

    enter image description here

    PS: Sometimes, it's less obvious as Automation Id is not always set.

    Another solution is to use the undocumented Explorer's WM_COMMAND 0xA21B (41499) which directly asks Explorer to open a new tab. So, with the previous example code, you can simply replace all UIA code by this, after you got ahold on the ShellTabWindowClass window:

    ...
    if (hwnd)
    {
      // window's class should be 'ShellTabWindowClass'
      // ask to open a new tab
      SendMessage((HWND)(INT_PTR)hwnd, WM_COMMAND, 0xA21B, 0);
    }
    ...
    

    Once you have opened a new tab, it's seen as a new window (view), so you can (re)start using CLSID_ShellWindows again and navigate this new view to any folder using something like what I have done here https://stackoverflow.com/a/78272475/403671 with Navigate2 call.