Click here to Skip to main content
Click here to Skip to main content
Go to top

Rewrite DirectoryInfo using IShellFolder

, 25 May 2010
Rate this:
Please Sign up or sign in to vote.
This article describes how to uses IShellFolder to list special / virtual directories using C#.

Introduction

DirectoryInfo is a class to represent a folder in disk, it's suitable to list file system entries, but it cannot be used to represent a special folder (e.g. virtual folder that doesn't exist in the disk). You may have to use IShellFolder to enumerate these directories. DirectoryInfoEx is written to support these folders.

The project is a rewrite based on Steven Roebert's C# File Browser's code and article. His code does even more than my project, his article emphasizes on how to take advantage of the shell once you have a complete implementation (e.g. context menu, shell drag and drop, preview handler, etc.), but lacks documentation about how to create one. I rewrite part of his code (the core part) to learn how it works, and this article explains how to do the basic file operations using IShellFolder interface, in C#.

Because of my lack of knowledge, and the nature of DirectoryInfo / FileInfo, the new class is a lot simpler than CShellItem.

Index

  • Obtaining PIDL
  • IShellFolder interface
  • IStorage interface
  • IStream interface
  • My DirectoryInfoEx implementation
  • Demo

Obtaining PIDL

Folders and Files in shell namespace can be located by PIDL (ITEMIDLIST), just like folder path (or DisplayName), there are Relative PIDL and Full PIDL, just like relative path (abc.txt) and full path (c:\abc.txt).

To obtain PIDL from a string path, you can use Desktop's IShellFolder.ParseDisplayName() (see below):

ShellAPI.SFGAO pdwAttributes = 0;
DesktopShellFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero,
     path, ref pchEaten, out pidlPtr, ref pdwAttributes);
PIDL pidl = new PIDL(pidlPtr, false); 

For special directories (e.g. virtual ones like DRIVES, or special file system directories like PROFILE), CSIDL enum has a list of them, you can use SHGetSpecialFolderLocation() to obtain its PIDL.

int RetVal = ShellAPI.SHGetSpecialFolderLocation(IntPtr.Zero, csidl, out ptrAddr);
if (ptrAddr != IntPtr.Zero)
{
  pidl = new PIDL(ptrAddr, false);
  return pidl;
}

Obtaining IShellFolder Interface

Desktop is the root of all shell namespace folder, you can use SHGetDesktopFolder() to obtain the IShellFolder interface for Desktop.

IntPtr ptrShellFolder = IntPtr.Zero;

if (ShellAPI.SHGetDesktopFolder(out ptrShellFolder) == ShellAPI.S_OK)
  iShellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ptrShellFolder,
     typeof(IShellFolder)); 

As for other directories (including non-file directory, e.g. MyComputer), you can use BindToObject().

if (Parent.ShellFolder.BindToObject(pidl.Ptr, IntPtr.Zero,
   ref ShellAPI.IID_IShellFolder, out ptrShellFolder) == ShellAPI.S_OK)

  iShellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ptrShellFolder,
     typeof(IShellFolder)); 

IShellFolder Interface

This contains methods to manage the folder, e.g.:

  • Obtain a list of subitems (sub-folders / sub-files), e.g.
    static ShellAPI.SHCONTF folderflag = ShellAPI.SHCONTF.FOLDERS |
     ShellAPI.SHCONTF.INCLUDEHIDDEN;
     /* Specify to include folders only */
    
    if (iShellFolder.EnumObjects(IntPtr.Zero, folderflag, out ptrEnum)
        == ShellAPI.S_OK) //return the pointer of IEnumIDList
    {
       IEnumIDList IEnum = (IEnumIDList)Marshal.GetTypedObjectForIUnknown(ptrEnum,
         typeof(IEnumIDList));
       IntPtr pidlSubItem;
       int celtFetched;
    
       while (IEnum.Next(1, out pidlSubItem, out celtFetched) == ShellAPI.S_OK
         && celtFetched == 1)
           dirList.Add(new PIDL(pidlSubItem, false));
    /*  Add PIDL of each subdirectory to dirList, noted that in normal case you
     *  should release pidlSubItem but Steven Roebert's PIDL class is a IDisposable
     *  class which will dispose it for you.
     *  Also noted that the pidl is relative instead of full.
     *  Use GetDisplayNameOf() (see below) to get it's name. */
    
       if (IEnum != null) //Release resource
       {
          Marshal.ReleaseComObject(IEnum);
          Marshal.Release(ptrEnum);
        }
      } 
  • Convert PIDL back to readable path using GetDisplayNameOf():
    IntPtr ptrStr = Marshal.AllocCoTaskMem(ShellAPI.MAX_PATH * 2 + 4);
    Marshal.WriteInt32(ptrStr, 0, 0);
    StringBuilder buf = new StringBuilder(ShellAPI.MAX_PATH);
    
    try
    {
      /*  uflags is a SHGNO enum that allow you to get different folder names,
       *  e.g. "My Documents"(SHGNO.NORMAL) folder is named as
       *       "Documents"(SHGNO.FORPARSING) in file system.
       *  StrRetToBuf() convert STRRET structure to
       *  buffer usable by StringBuilder */
    if (iShellFolder.GetDisplayNameOf(pidl, uFlags, ptrStr) == ShellAPI.S_OK)
        ShellAPI.StrRetToBuf(ptrStr, pidl, buf, ShellAPI.MAX_PATH);
    }
    finally
    {
      if (ptrStr != IntPtr.Zero)
        Marshal.FreeCoTaskMem(ptrStr);
      ptrStr = IntPtr.Zero;
    }
    Console.WriteLine(buf.ToString()); 
  • Retrieve IShellFolder / IStorage interface for a subfolder:

    Using SHBindToParent() :

    IntPtr pidlLast = IntPtr.Zero;
    retVal = ShellAPI.SHBindToParent(dir.PIDLRel.Ptr, ShellAPI.IID_IStorage,
      out storagePtr, ref pidlLast); 

    Or BindToStorage():

    retVal = dir.Parent.ShellFolder.BindToStorage(
                        dir.PIDLRel.Ptr, IntPtr.Zero, ref ShellAPI.IID_IStorage,
                        out storagePtr);
    /* Beside IID_IStorage interface, there is IID_IStream and
       IID_IPropertySetStorage as well. */
    
    if ((retVal == ShellAPI.S_OK))
    {
      IStorage storage = (IStorage)Marshal.GetTypedObjectForIUnknown(storagePtr,
      typeof(IStorage));
      /* Your work here, free the pointer and interface when done. */
    } 

IStorage Interface

This contains methods for creation or to manage items / subitems in the folder, e.g.

  • Rename, move or copy files, using PIDL as parameter. e.g.
    SrcStorage.MoveElementTo(SourceFileName, DestStorage,
       DestFilename, ShellAPI.STGMOVE.MOVE) != ShellAPI.S_OK) 
  • Delete files:
    ParentStorage.DestroyElement(name); 
  • Read / Write files contents:
    /* FileStreamEx class is a customized IDisposable Stream class,
     * which uses IStream interface of a file. */
    FileStreamEx stream = new FileStreamEx(path, mode, access);
    StreamReader sr = new StreamReader(stream);
    Console.WriteLine(sr.ReadToEnd()); 

IStream Interface

This allows you to read / write data to stream objects.

  • To obtain the IStream interface, use OpenStream() (Read/Write) or CreateStream() (Create New):
    if (parentStorage.OpenStream(filename, IntPtr.Zero, grfmode, 0,
      out streamPtr) == ShellAPI.S_OK)
        stream = (IStream)Marshal.GetTypedObjectForIUnknown(streamPtr,
          typeof(IStream)); 
  • IStream contains methods like seek(), read(), write().
  • If the stream object is released, it is considered closed.

All interface objects should be released when done.

My DirectoryInfoEx Implementation

The implementation is simpler than CShellItem, however, as FileSystemInfoEx's PIDL is exposed (via PIDLRel and PIDL property), you can implement custom operation (e.g. Extract Icon, Context Menu) externally. IShellFolder and IStorage (generate on demand) are also exposed in DirectoryInfoEx, they are automatically destroyed when disposed, do not free them yourself.

Both DirectoryInfoEx and FileInfoEx are inherited from FileSystemInfoEx, they are used as enumeration (listing) subitems, DirectoryInfoEx contains GetFiles() and GetDirectories() method for this purpose. To modify a file, use FileEx class for managing files (DirectoryEx is not implemented yet, use System.IO.Directory at this time), and FileStreamEx class for read/write files.

DirectoryInfoEx (and FileInfoEx)'s constructor accept a Path or PIDL. A number of special directories are defined in DirectoryInfoEx, including DesktopDirectory, MyComputerDirectory, CurrentUserDirectory, SharedDirectory and NetworkDirectory. For other special directories, you can obtain its PIDL by calling DirectoryInfoEx.CSIDLtoPIDL().

And a Demo

This demo is a simple WPF application that lists the subdirectories below desktop. The Icons are obtained using SHGetFileInfo(), which takes a full PIDL parameter.

Issues

  • DirectoryEx static class is not implemented.
  • Haven't figured out how to get IShellFolder directly, current implementation looks up from Desktop directory (e.g. C:\ = Desktop, Computer, C:\ = 3 times), which is slower.

References

History

  • 08-23-09 version 0.2
    • Demo updated.
  • 11-01-09 version 0.3
    • Demo no longer load Network contents, edit the converter to disable this change.
    • DirectoryEx (static class) added.
    • PIDL class is now IDisposable and free automatically now. Also added new - internal classes ShellFolder and Storage which do the same.
    • Performance improved, no longer construct from desktop directory. (see above)
    • DirectoryInfoEx and FileInfoEx is now serializable.
  • 11-01-09 Version 0.4
    • Fixed Cache not working.
  • 11-04-09 Version 0.5
    • DirectoryInfoEx/FileInfoEx works even if the path specified is not exists (Exists == false, you have to call Create() or Refresh() before using it).
    • Refresh(), Create(), MoveTo(), Delete(), CreateSubdirectory() Open() and related instance method added.
    • Constructor support Environment path (e.g. %temp%)
    • Test project.
  • 11-07-09 Version 0.6
    • Context menu support (ContextMenuWrapper)
    • Demo updated (Context menu)
    • FileSystemWatcherEx class added.
    • Fixed FileInfoEx created by EnumFiles()(which used by GetFiles() and GetFileSystemInfos()) return incorrect Parent directory.
  • 11-08-09 Version 0.7
    • Fixed Root of all FileInfoEx equals to c:\Users\{User}\Desktop instead of a GUID.
    • Demo updated (Context menu multiselected)
  • 11-16-09 Version 0.8
    • Fixed unable Rename item in same directory.
    • Fixed ContextMenuWrapper dont return OnHover message on popup.
    • Added QueryMenuItemsEventArgs.Command, return properly for user query items.
    • Demo updated (added statusbar)
  • 12-06-09 Version 0.9
    • Fixed minor typo in DirectoryInfoEx.EmuFiles (if (iShellFolder != null))
    • Fixed DirectoryEx.Copy does not Copy directory recursively. (it currently copies an empty folder)
    • Fixed DIrectoryEx.Move (and perhaps FileEx as well) does not work correctly.
    • Fixed Wrong Operator (new) in DirectoryInfoEx.Delete(), should be override.
  • 01-04-10 Version 0.10
    • Fixed FileSystemInfoEx.getParentIShellFolder() method generate ArgumentException when pidl of items directly in Desktop directory, caused by _desktopShellFolder.BindToObject({Desktop's PIDL},...);
    • Fixed FileSystemInfoEx.Delete() return NotImplementException when get called.
    • Fixed DirectoryEx/FileEx.Exists does not check if it's directory / file.
    • Fixed FileSystemInfoEx.refresh() method does not update attribute.
    • Implemented IClonable interface in FileSystemInfoEx, DirectoryInfoEx and FileInfoEx classes.
    • Added BeforeInvoke event to ContextMenuWrapper class.
    • Added Run behavior when double click in filelist.
    • Added FileSystemWatcherEx.Filter.
  • 01-20-10 Version 0.11
    • Added: DirectoryTree in the demo; now properly refreshes when changed. (Implemented an ObservableCollection in the GetDirectoriesConverterEx class using the FileSystemWatcherEx class.)
    • Added: ContextMenuWrapper.OnQueryMenuItems.QueryContextMenu2 / QueryContextMenu3 property.
    • Added: ContextMenuWrapper.OnBeforePopup event.
    • Added: ContextMenuWrapper.OnQueryMenuItems event; now supports multilevel menu (e.g.: @"Tools\Add").
    • Added: ContextMenuWrapper.OnQueryMenuItems event; now supports GrayedItems / HiddenItems.
  • 02-15-10 Version 0.12
    • Fixed: Fullname of User/Shared directory under desktop is now its GUID instead of its file path.
    • Fixed: PIDL, PIDLRel, ShellFolder, Storage properties generated on demand to avoid x-thread issues.
    • Added: PathEx class to deal with PIDL related paths.
  • 03-14-10 Version 0.13
    • Fixed: FileSystemWaterEx ignoring remove directory event.
    • Fixed: Removed IDisposable in PIDL as it is causing an AccessViolationException, user has to free by calling the Free() method.
  • 03-16-10 Version 0.14
    • Fixed: FileSystemInfoEx now stores a copy of PIDL/Rel, will return copy of it when properties are called (to avoid AccessViolation).
    • Fixed: FileSystemInfoEx records the PIDL when constructed, as some paths are not parseable (e.g., EntireNetwork).
    • Added: Allows folder list so Non-FileAncestor directories (e.g., recycle-bin) are listed.
  • 03-18-10 Version 0.15
    • Fixed: ShellFolder/PIDL not freed in a couple locations.
    • (Please note that PIDL/ShellFolder/Store are no longer stored in the FileSystemInfoEx => must be freed by the user.)
  • 03-19-10 Version 0.16
    • Added: IShellFolder2 interface and ShellFolder2 class.
    • Added: ExtraPropertiesProvider which can list the extra file properties / list columns available (e.g., ExtraPropertiesProvider.GetProperty(file, ref ImageSummaryInformation.BitDepth);).
    • Fixed: getRelPIDL() cannot return the correct value in the File/DirInfoEx construct with string. (Attempt to return a freed up pointer.)
    • Fixed: ShellFolder not freed in two spots.
  • 04-26-2010
    • Updated download files.
  • 05-25-2010
    • Updated download files.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author


Comments and Discussions

 
QuestionThrowExceptionForHR PinmemberMember 930529318-Sep-12 2:02 
GeneralCrashes on win7 PinmemberXmen W.K.18-Jul-12 18:08 
GeneralRe: Crashes on win7 PinmemberLeung Yat Chun19-Jul-12 19:24 
GeneralRe: Crashes on win7 PinmemberXmen W.K.19-Jul-12 23:03 
GeneralRe: Crashes on win7 PinmemberXmen W.K.20-Jul-12 13:44 
GeneralRe: Crashes on win7 PinmemberLeung Yat Chun22-Jul-12 19:55 
GeneralVersion 18 : ArgumentException Fix PinmemberLeung Yat Chun9-Jun-10 7:45 
GeneralVersion 17 Notes Pinmembercwharmon29-Apr-10 2:25 
GeneralRe: Version 17 Notes PinmemberLeung Yat Chun29-Apr-10 4:30 
GeneralRe: Version 17 Notes Pinmembercwharmon29-Apr-10 6:34 
GeneralRe: Version 17 Notes PinmemberLeung Yat Chun29-Apr-10 7:45 
GeneralRe: Version 17 Notes Pinmembercwharmon29-Apr-10 8:08 
GeneralRe: Version 17 Notes - Performance Issue Pinmembercwharmon21-May-10 10:23 
GeneralRe: Version 17 Notes - Performance Issue PinmemberLeung Yat Chun21-May-10 18:21 
GeneralRe: Version 17 Notes - Performance Issue Pinmembercwharmon25-May-10 3:18 
NewsVersion 0.17 update log PinmemberLeung Yat Chun26-Apr-10 6:03 
GeneralRe: Version 0.18 update log PinmemberLeung Yat Chun24-May-10 7:08 
GeneralContext Menu: Default Action Pinmembercwharmon26-Mar-10 3:23 
GeneralRe: Context Menu: Default Action PinmemberLeung Yat Chun26-Mar-10 4:45 
GeneralRe: Context Menu: Default Action Pinmembercwharmon29-Mar-10 3:52 
GeneralRe: Context Menu: Default Action [modified] PinmemberLeung Yat Chun29-Mar-10 4:36 
GeneralRe: Context Menu: Disappears Pinmembercwharmon18-May-10 12:01 
GeneralRe: Context Menu: Disappears PinmemberLeung Yat Chun19-May-10 2:16 
GeneralMy vote of 1 Pinmembertommyjackson17-Mar-10 6:17 
GeneralRe: My vote of 1 PinmemberLeung Yat Chun17-Mar-10 20:26 
GeneralRe: My vote of 1 PinmemberIzzet Kerem Kusmezer27-Apr-10 8:57 
GeneralAcess Violation Exception Pinmembercwharmon12-Mar-10 10:51 
GeneralRe: Acess Violation Exception PinmemberLeung Yat Chun12-Mar-10 19:30 
GeneralRe: Acess Violation Exception PinmemberLeung Yat Chun13-Mar-10 6:50 
GeneralRe: Acess Violation Exception Pinmembercwharmon14-Mar-10 5:30 
GeneralRe: Acess Violation Exception [modified] PinmemberLeung Yat Chun14-Mar-10 7:58 
GeneralRe: Acess Violation Exception [modified] PinmemberLeung Yat Chun14-Mar-10 21:40 
GeneralRe: Acess Violation Exception Pinmembercwharmon15-Mar-10 5:06 
GeneralRe: Acess Violation Exception PinmemberLeung Yat Chun15-Mar-10 7:37 
GeneralRe: Acess Violation Exception [modified] PinmemberLeung Yat Chun15-Mar-10 20:23 
GeneralRe: Acess Violation Exception Pinmembercwharmon16-Mar-10 5:26 
GeneralRe: Acess Violation Exception Pinmembercwharmon16-Mar-10 7:50 
GeneralRe: Acess Violation Exception [modified] PinmemberLeung Yat Chun16-Mar-10 18:12 
GeneralRe: Acess Violation Exception [modified] PinmemberLeung Yat Chun17-Mar-10 4:24 
GeneralRe: Acess Violation Exception Pinmembercwharmon17-Mar-10 14:36 
GeneralRe: Acess Violation Exception PinmemberLeung Yat Chun17-Mar-10 20:15 
GeneralRe: Acess Violation Exception Pinmembercwharmon23-Mar-10 6:10 
GeneralRe: Acess Violation Exception PinmemberLeung Yat Chun23-Mar-10 7:41 
NewsNext part of this article (DirectoryInfoExA) PinmemberLeung Yat Chun6-Jan-10 3:57 
NewsUpdated Version 0.14 [modified] PinmemberLeung Yat Chun1-Nov-09 2:20 
GeneralRe: Updated Version 4 PinmemberPaul Selormey3-Nov-09 12:57 
GeneralRe: Updated Version 4 PinmemberLeung Yat Chun4-Nov-09 0:08 
GeneralGreat Efforts Pinmembergulzarhasan14-Sep-09 4:50 
GeneralRe: Great Efforts PinmemberLeung Yat Chun19-Sep-09 0:26 
GeneralRe: Great Efforts PinmemberLeung Yat Chun14-Feb-10 8:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 25 May 2010
Article Copyright 2009 by Leung Yat Chun
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid