Search code examples
windowsdelphiwindows-shell

Use Delphi to Find Special Drives


I am trying to write a small program in Delphi 2007 to access files off of a portable USB drive whenever it is plugged in to a Windows 7 machine. This drive does not show up as a standard drive letter though. It appears under Portable Devices in Windows Explorer. I have written the following code to enumerate all the items under 'Computer':

Procedure TfrmMain.ComputerChanged(Var Msg: TMessage);
Var
  Enum: IEnumIDList;
  Fetched: Longword;
  Item: PItemIDList;
  Path: String;
  Computer: IShellFolder;
  StrRet: TSTRRET;
Begin
  Status('Computer changed...  Checking folders.');
  fDesktop.BindToObject(fCompPidl, Nil, IID_IShellFolder, Computer);
  If Assigned(Computer) And
     (Computer.EnumObjects(Self.Handle, SHCONTF_FOLDERS, Enum) = NOERROR) Then
  Begin
    While (Enum.Next(1, Item, Fetched) = NOERROR) Do
    Begin
      FillChar(StrRet, SizeOf(StrRet), #0);
      Computer.GetDisplayNameOf(Item, SHGDN_FORADDRESSBAR or SHGDN_NORMAL, StrRet);
      Path := StrRetToStr(StrRet, Item);
      Status(Path);
    End;
  End;
End;

(note: the Status procedure just outputs a message to a TMemo.)

This is called whenever my application is notified of a change by the Windows shell subsystem. It enumerates all of the local drives and network drives but nothing else (iCloud Photos drive is missing as well).

Does anyone know how I can access the files on these virtual drives?


Solution

  • You more than likely aren't initializing COM correctly. Your code will work as-is if you don't call CoInitializeEx or if you call it with a bad value, but the Portable Device drivers require apartment threading to work.

    Based on your code, here's a sample app that works correctly and shows portable devices. If you comment out the CoInitializeEx/CoUninitialize calls or pass in COINIT_MULTITHREADED instead it will still work, but it only shows the drives.

    program ListMyComputer;
    
    {$APPTYPE CONSOLE}
    
    uses
      ComObj, ShlObj, ShellApi, ShLwApi, ActiveX, Windows, SysUtils;
    
    var
      Enum: IEnumIDList;
      Fetched: Longword;
      CompPidl, Item: PItemIDList;
      Path: PWideChar;
      Desktop, Computer: IShellFolder;
      StrRet: TSTRRET;
    begin
      CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
      try
        WriteLn('Computer changed...  Checking folders.');
        SHGetDesktopFolder(Desktop);
        SHGetFolderLocation(0, CSIDL_DRIVES, 0, 0, CompPidl);
        Desktop.BindToObject(CompPidl, Nil, IID_IShellFolder, Computer);
        CoTaskMemFree(CompPidl);
        If Assigned(Computer) And
           (Computer.EnumObjects(0, SHCONTF_FOLDERS, Enum) = NOERROR) Then
        Begin
          While (Enum.Next(1, Item, Fetched) = NOERROR) Do
          Begin
            FillChar(StrRet, SizeOf(StrRet), #0);
            Computer.GetDisplayNameOf(Item, SHGDN_FORADDRESSBAR or SHGDN_NORMAL, StrRet);
            StrRetToStr(@StrRet, Item, Path);
            WriteLn(Path);
            CoTaskMemFree(Path);
          End;
        End;
        WriteLn('Enumeration complete');
        ReadLn;
      finally
        CoUninitialize
      end;
    end.