SHChangeNotify with SHCNE_RMDIR behavior inconsistent between Windows 7 and Windows 10

3

Overview

I am using SHChangeNotify with SHCNE_RMDIR to notify the shell of a folder that has been removed from within my shell namespace extension. My expectation is that this will cause any explorer (or other shell) windows which have their folder view navigated into the removed folder (or any of its sub folders) to be navigated to the parent folder of the removed folder. This expected behavior is occurring on Windows 10. However, on Windows 7 those windows are being navigated to the removed folder.

Question

Is this behavior on Windows 7 a bug, and/or is there something I can do (without having special code for Windows 7) to get the same behavior for both OSes?

Detailed steps to reproduce the issue

Here is a walk through of how to create and observe the issue from scratch. This involves using a "built in" Microsoft namespace extension called a Shell Instance Object (rather than my real namespace extension). I used this for simplicity as well as to show that it is not related to my specific namespace extension. All this sample namespace extension does is creates an icon under "My Computer" that will browse into your %TEMP% directory.

  1. Install the namespace extension and register it under "My Computer". To do this, enter the following into the registry:

    HKEY_CURRENT_USER\Software\Classes\CLSID
    
      {0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}=REG_SZ_EXPAND:"My Namespace Extension"
        DescriptionID=REG_DWORD:0x00000008
        System.IsPinnedToNameSpaceTree=REG_DWORD:0x00000001
        DefaultIcon=REG_EXPAND_SZ:"%SystemRoot%\system32\main.cpl,9"
        InProcServer32=REG_EXPAND_SZ:"%SystemRoot%\system32\shdocvw.dll"
          ThreadingModel=REG_SZ:"Apartment"
        ShellFolder
          Attributes=REG_DWORD:0x60000000
        Instance
          CLSID=REG_SZ:"{0AFACED1-E828-11D1-9187-B532F1E9575D}"
          InitPropertyBag
            Attributes=REG_DWORD:0x00000011
            Target=REG_SZ_EXPAND:"%TEMP%"
    

    Here is a .reg file that will automate that for you:

    Windows Registry Editor Version 5.00
    
    [HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}]
    @="My Namespace Extension"
    "System.IsPinnedToNameSpaceTree"=dword:00000001
    "DescriptionID"=dword:00000008
    
    [HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}\DefaultIcon]
    @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
      00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,6d,00,61,00,\
      69,00,6e,00,2e,00,63,00,70,00,6c,00,2c,00,39,00,00,00
    
    [HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}\InProcServer32]
    @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
      00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,73,00,68,00,\
      64,00,6f,00,63,00,76,00,77,00,2e,00,64,00,6c,00,6c,00,00,00
    "ThreadingModel"="Apartment"
    
    [HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}\Instance]
    "CLSID"="{0AFACED1-E828-11D1-9187-B532F1E9575D}"
    
    [HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}\Instance\InitPropertyBag]
    "Attributes"=dword:00000011
    "Target"=hex(2):25,00,54,00,45,00,4d,00,50,00,25,00,00,00
    
    [HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}\ShellFolder]
    "Attributes"=dword:60000000
    
    [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\Namespace\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}]
    @="My Namespace Extension"
    

    The following .reg file will allow you to easily remove the above registry entries:

    Windows Registry Editor Version 5.00
    
    [-HKEY_CURRENT_USER\Software\Classes\CLSID\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}]
    
    [-HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\Namespace\{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}]
    
  2. At this point, when you open an explorer Window and navigate through "My Computer", you should see the "My Namespace Extension". Browsing into it should reveal your %TEMP% directory folders/files.

  3. Create a directory in your %TEMP% folder called FolderToRemove. Within FolderToRemove create a sub folder called subFolder.

  4. Open 3 explorer windows and browse to the following locations:

    • My Computer\My Namespace Extension
    • My Computer\My Namespace Extension\FolderToRemove
    • My Computer\My Namespace Extension\FolderToRemove\subFolder

    Image showing 3 explorer windows before executing SHChangeNotify

  5. On Windows 7, Execute the following C++ code:

    // This path represents My Computer\My Namespace Extension\FolderToRemove
    const wchar_t * pPath = L"::{20d04fe0-3aea-1069-a2d8-08002b30309d}\\::{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}\\FolderToRemove";
    SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, pPath, NULL);
    
  6. After having executed the above code, you'll notice that the only explorer window that changed was the one that was originally navigated into My Computer\My Namespace Extension\FolderToRemove\subFolder. Notice that it is now pointing to My Computer\My Namespace Extension\FolderToRemove:

    Image showing Windows 7 after having executed SHChangeNotify

  7. The expectation is for two of the explorer windows to have changed, such that they are all navigated to My Computer\My Namespace Extension (the parent folder of the the removed folder). Here is an image of the expected behavior (which is what happens on Windows 10): Image showing the expected result after executing SHChangeNotify

Additional notes

  • I found that I can get the behavior I want for Windows 7 by specifying the parent folder of the removed folder in the SHChangeNotify. E.g.:

    // This path represents My Computer\My Namespace Extension
    const wchar_t * pPath = L"::{20d04fe0-3aea-1069-a2d8-08002b30309d}\\::{0672A6D1-A6E0-40FE-AB16-F25BADC6D9E4}";
    SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, pPath, NULL);
    

    But when I execute this code on Windows 10 it, of course, causes the windows to be navigated back to My Computer which is undesired. If I went with this workaround I would need different code for different OSes.

winapi
shell-extensions
shell32
shell-namespace-extension
asked on Stack Overflow May 3, 2017 by Matt Smith • edited May 5, 2017 by Matt Smith

1 Answer

4

As part of a Microsoft Support case, I was given the following information, which describes how Explorer reacts in this workflow. It, however, doesn't give any reasoning as to why Windows 10 behaves differently:


For any browser window navigated to the folder specified in the SHCNE_RMDIR notification or one of its descendants, Explorer will navigate the browser window to a valid (ancestor) folder. The process for determining the new folder to open in an Explorer browser window, starting with the absolute ITEMIDLIST of folder specified in the SCHNE_RMDIR notification, is:

  1. Obtain an IShellFolder for the parent of the specified absolute ITEMIDLIST.
  2. Validate the child folder by calling IShellFolder::GetAttributesOf with the SFGAO_VALIDATE flag.
  3. If GetAttributesOf reports that the item is invalid (returns an error), obtain an absolute ITEMIDLIST of the parent folder and return to step 1.
  4. If GetAttributesOf reports that the item is valid, call the browser window’s IShellBrowser::BrowseObject to navigate to the valid folder.

In the repro steps you posted to StackOverflow, we have Explorer browser windows opened to the following folders:

::{CLSID_MyComputer}::{CLSID_My Namespace Extension}

::{CLSID_MyComputer}::{CLSID_My Namespace Extension}\FolderToRemove

::{CLSID_MyComputer}::{CLSID_My Namespace Extension}\FolderToRemove\subFolder

Here is how the SHCNE_RMDIR notification for the

::{CLSID_MyComputer}\::{CLSID_My Namespace Extension}\FolderToRemove

folder is processed by the browser windows:

Browser window navigated to ::{CLSID_MyComputer}\::{CLSID_My Namespace Extension}:

  1. Do nothing since this folder is not the folder being removed or one of its descendants.

Browser window navigated to ::{CLSID_MyComputer}\::{CLSID_My Namespace Extension}\FolderToRemove

  1. Obtain an IShellFolder for the ::{CLSID_MyComputer}::{CLSID_My Namespace Extension} folder and call IShellFolder::GetAttributesOf(SFGAO_VALIDATE) on FolderToRemove.
  2. GetAttributesOf returns S_OK since FolderToRemove was not actually deleted.
  3. GetAttributesOf reports that FolderToRemove is valid this is a valid folder, call IShellBrowser::BrowseObject on ::{CLSID_MyComputer}::{CLSID_My Namespace Extension}\FolderToRemove. This is essentially a no-op since the browser window is already navigated to the folder.

Browser window navigated to ::{CLSID_MyComputer}\::{CLSID_My Namespace Extension}\FolderToRemove\subFolder

  1. We start with the ITEMIDLIST of the ::{CLSID_MyComputer}::{CLSID_My Namespace Extension}\FolderToRemove folder, since this is the folder that was specified in the SHCNE_RMDIR notification and the browser window is navigated to a descendant folder.
  2. Obtain an IShellFolder for the ::{CLSID_MyComputer}::{CLSID_My Namespace Extension} folder and call IShellFolder::GetAttributesOf(SFGAO_VALIDATE) on FolderToRemove.
  3. GetAttributesOf returns S_OK since FolderToRemove was not actually deleted.
  4. GetAttributesOf reports that FolderToRemove is valid this is a valid folder, call IShellBrowser::BrowseObject on ::{CLSID_MyComputer}::{CLSID_My Namespace Extension}\FolderToRemove.
  5. The browser window navigates to the FolderToRemoveFolder.
answered on Stack Overflow May 18, 2017 by Matt Smith • edited Jun 20, 2020 by Community

User contributions licensed under CC BY-SA 3.0