c++winapiexplorerwindows-shellmtp

Programtically ask explorer to refresh Portable device folder contents


I want to programmatically ask explorer to refresh MTP folder contents (Android device) and display the existing files (simulating F5 on the folder). if it was a standard path with drive letter, I can refresh using:

SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, FolderPath, NULL);

However, MTP folder doesn't have a drive letter path that I can use. how can I ask explorer to refresh such folder, or alternatively ask explorer to refresh all open folder windows?


Solution

  • Here is some sample code that scans all opened Windows Explorer views and refresh all views that currently display a portable device Shell Item (using WPD properties), you can adapt it to filter the exact view(s) you want to target:

    #include <windows.h>
    #include <stdio.h>
    #include <atlbase.h>
    #include <shobjidl_core.h>
    #include <shlobj_core.h>
    #include <ShlGuid.h>
    #include <PortableDevice.h>
    #include <propvarutil.h>
    
    int main()
    {
      CoInitialize(nullptr);
      {
        // get Shell windows' list
        CComPtr<IShellWindows> windows;
        ATLASSERT(SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)));
    
        // browse all opened views, pick the path or display name that you need
        long count = 0;
        windows->get_Count(&count);
        for (auto i = 0; i < count; i++)
        {
          CComPtr<IDispatch> disp;
          ATLASSERT(SUCCEEDED(windows->Item(CComVariant(i), &disp)));
    
          // get the PIDL from the underlying IShellView
          CComPtr<IPersistIDList> idlist;
          ATLASSERT(SUCCEEDED(IUnknown_QueryService(disp, SID_SFolderView, IID_PPV_ARGS(&idlist))));
    
          CComHeapPtr<ITEMIDLIST> pidl;
          ATLASSERT(SUCCEEDED(idlist->GetIDList(&pidl)));
    
          // get this as an IShellItem, easier to work with
          CComPtr<IShellItem2> item;
          ATLASSERT(SUCCEEDED(SHCreateItemFromIDList(pidl, IID_PPV_ARGS(&item))));
    
          CComHeapPtr<wchar_t> displayName;
          ATLASSERT(SUCCEEDED(item->GetDisplayName(SIGDN::SIGDN_DESKTOPABSOLUTEEDITING, &displayName)));
          wprintf(L"display name:%s\n", displayName.m_pData);
    
          CComHeapPtr<wchar_t> path;
          ATLASSERT(SUCCEEDED(item->GetDisplayName(SIGDN::SIGDN_DESKTOPABSOLUTEPARSING, &path)));
          wprintf(L"path:%s\n", path.m_pData);
    
          // if you want to make sure it's a WPD object, you can get a WPD property
          // for example WPD_OBJECT_NAME (should be the same as SIGDN_NORMALDISPLAY)
          PROPVARIANT pv;
          PropVariantInit(&pv);
          ATLASSERT(SUCCEEDED(item->GetProperty(WPD_OBJECT_NAME, &pv))); // PortableDevice.h
    
          // here we refresh all WPD views
          auto isWpd = pv.vt != VT_EMPTY;
    
          CComHeapPtr<wchar_t> name;
          ATLASSERT(SUCCEEDED(PropVariantToStringAlloc(pv, &name))); // propvarutil.h
          wprintf(L"WPD name:%s\n", name.m_pData);
          PropVariantClear(&pv);
          if (isWpd)
          {
            // ask for refresh
            SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, pidl, NULL);
          }
        }
      }
      CoUninitialize();
    }