7
votes

Intro

In Windows 7 you have some special folders for documents, pictures and music etc called Libraries.

If you are not aware of them, basically each Library folder can contain Locations (paths) which are basically shortcuts for each Library.

Some examples:

Documents (Library)

  • E:\Personal\Documents (Location)
  • F:\Backups\Documents (Location)

Music (Library)

  • E:\Media\Music\Albums (Location)
  • E:\Media\Music\Singles (Location)

Pictures (Library)

  • E:\Media\Photos (Location)

When you click any of these Library folders from Windows Explorer or Start menu, Windows Explorer will show with the Locations defined inside that Library.

Task

What I need to be able to do is read the Locations for each Library type, and be able to write back (update) the Library with my own Locations. I have found that the Libraries are stored in the user AppData folder like this:

C:\Users\SOMEUSER\AppData\Roaming\Microsoft\Windows\Libraries

These Libraries are this file type: Library (.library-ms) - if you right click on one and select properties, Library tab you can see the Library locations associated with that Library.

I don't see a way of extracting these and putting them for example into a TStringList for editing in Delphi. I wondered if these Library Locations were actually stored in the Windows Registry so with some research from Google I found these paths:

  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders

But again, I don't see the actual list of Library Locations.

So, how can I read the list of Locations inside a Library file in Delphi, add them to a Listbox or TStringList, edit the entries and then write back the changes? Just been able to extract the Library Location paths would be a start.

I just have a feeling this is going to be one of those questions that has a simple answer I cannot seem to find!

2
Read about it on MSDN: msdn.microsoft.com/en-us/windows/ee658250.aspx and thenuse the APIs. Don't even think about hacking the implementation specific stuff.David Heffernan
I've just read somewhere that the .library-ms file could be an XML file, I've lost the page I was reading though!user1175743
You aren't meant to poke in those files. Use the API.David Heffernan
@Blobby today they are - tomorrow MS will remove files or change their format - and your application is screwed. If you don't aim at strategy "make bugs, sell fixes" a la BIND software, then you'd probably not make deliberately future-incompatible programs. Read how programs like that make Windows buggy and less stable: blogs.msdn.com/b/oldnewthing/archive/2003/11/03/55532.aspxArioch 'The
@Arioch'The I just wanted to make the tool for myself, I regularly format and reinstall Windows 7 so manually resetting the library paths can be a pain, if I made a tool to automate this myself that is what I was hoping to do. I don't need to worry about MS changing anything because I know it is always going to be used on Windows 7user1175743

2 Answers

6
votes

Use one of the SHLoadLibraryFrom...() functions, like SHLoadLibraryFromKnownFolder(), to get an IShellLibrary interface, then you can use its methods to enumerate and manipulate the Library, such as IShellLibrary::GetFolders(), IShellLibrary::AddFolder(), IShellLibrary::RemoveFolder(), etc.

Update: For example:

uses
  ..., ActiveX, KnownFolders, ShlObj;

// The SHLoadLibraryFrom...() functions are implemented inline in the Win32 SDK
// shobjidl.h header file, so you have to implement them manually in your 
// code if you you are not using a version of Delphi that already implements
// them in the RTL's ShlObj.pas unit for you...

// SHLoadLibraryFromKnownFolder() is defined wrong in ShlObj.pas!!! See QC #109306
function My_SHLoadLibraryFromKnownFolder(const kfidLibrary: TGUID; grfMode: DWORD; riid: TIID; out ppv): HRESULT;
var
  plib: IShellLibrary;
begin
  Result := CoCreateInstance(CLSID_ShellLibrary, nil, CLSCTX_INPROC_SERVER, IShellLibrary, plib);
  if SUCCEEDED(Result) then
  begin
    Result := plib.LoadLibraryFromKnownFolder(kfidLibrary, grfMode);
    if SUCCEEDED(Result) then
      Result := plib.QueryInterface(riid, ppv);
  end;
end;

function GetLibraryFileSystemFolders(FolderID: TGUID; Folders: TStrings): Boolean;
var
  SL: IShellLibrary;
  Arr: IShellItemArray;
  Enum: IEnumShellItems;
  Item: IShellItem;
  Path: LPWSTR;
begin
  Result := False;

  if FAILED(My_SHLoadLibraryFromKnownFolder(FolderID, STGM_READ, IShellLibrary, SL)) then
    Exit;

  if FAILED(SL.GetFolders(LFF_FORCEFILESYSTEM, IShellItemArray, Arr)) then
    Exit;

  if FAILED(Arr.EnumItems(Enum)) then
    Exit;

  while Enum.Next(1, Item, nil) = S_OK then
  begin
    if FAILED(Item.GetDisplayName(SIGDN_FILESYSPATH, Path)) then
      Exit;
    try
      Folders.Add(Path);
    finally
      CoTaskMemFree(Path); 
    end;
    Item := nil;
  end;

  Result := True;
end;

.

var
  Folders: TStringList;
begin
  Folders := TStringList.Create;
  try
    if GetLibraryFileSystemFolders(FOLDERID_DocumentsLibrary, Folders) then
    begin
      //...
    end;
  finally
    Folders.Free;
  end;
end;

Update: SHLoadLibraryFromKnownFolder() only works for Microsoft-defined Libraries that have KNOWNFOLDERID values defined. If you want to access custom Libraries, you have to use a slightly modified approach to obtaining the IShellLibrary interface, eg:

// SHLoadLibraryFromItem() is defined wrong in ShlObj.pas!!! See QC #109306
function My_SHLoadLibraryFromItem(const psiLibrary: IShellItem; grfMode: DWORD; const riid: TIID; out ppv): HResult;
var
  plib: IShellLibrary;
begin
  Result := CoCreateInstance(CLSID_ShellLibrary, nil, CLSCTX_INPROC_SERVER, IID_IShellLibrary, plib);
  if Succeeded(Result) then
  begin
    Result := plib.LoadLibraryFromItem(psiLibrary, grfMode);
    if Succeeded(Result) then
      Result := plib.QueryInterface(riid, ppv);
  end;
end;

function GetShellLibraryforLibrary(const LibraryName: String; grfMode: DWORD; var ppv: IShellLibrary): Boolean;
var
  SL: IShellLibrary;
  Enum: IEnumShellItems;
  Item: IShellItem;
  DisplayName: LPWSTR;
  hr: HRESULT;
begin
  Result := False;
  ppv := nil;

  if FAILED(SHGetKnownFolderItem(FOLDERID_Libraries, 0, 0, IShellItem, PPointer(@Item)^) then
    Exit;

  hr := Item.BindToHandler(nil, BHID_EnumItems, IEnumShellItems, Enum);
  if FAILED(hr) then
    Exit;

  Item := nil;
  while Enum.Next(1, Item, nil) = S_OK do
  begin
    if FAILED(Item.GetDisplayName(SIGDN_NORMALDISPLAY, DisplayName)) then
      Exit;
    try
      if AnsiSameText(DisplayName, LibraryName) then
      begin
        Result := SUCCEEDED(My_SHLoadLibraryFromItem(Item, grfMode, IShellLibrary, ppv));
        Break;
      end;
    finally
      CoTaskMemFree(DisplayName);
    end;
    Item := nil;
  end;
end;

function GetLibraryFileSystemFolders(const LibraryName: String; Folders: TStrings): Boolean;
var
  SL: IShellLibrary;
  Arr: IShellItemArray;
  Enum: IEnumShellItems;
  Item: IShellItem;
  Path: LPWSTR;
begin
  Result := False;

  if not GetShellLibraryforLibrary(LibraryName, STGM_READ, SL) then
    Exit;

  if FAILED(SL.GetFolders(LFF_FORCEFILESYSTEM, IShellItemArray, Arr)) then
    Exit;

  if FAILED(Arr.EnumItems(Enum)) then
    Exit;

  while Enum.Next(1, Item, nil) = S_OK then
  begin
    if FAILED(Item.GetDisplayName(SIGDN_FILESYSPATH, Path)) then
      Exit;
    try
      Folders.Add(Path);
    finally
      CoTaskMemFree(Path); 
    end;
    Item := nil;
  end;

  Result := True;
end;

.

var
  Folders: TStringList;
begin
  Folders := TStringList.Create;
  try
    if GetLibraryFileSystemFolders('MyLibrary', Folders) then
    begin
      //...
    end;
  finally
    Folders.Free;
  end;
end;
1
votes

I have just found in Marco Cantu's source code repository for his Mastering Delphi books, an example for Delphi 2010 which shows a way of getting the locations inside a library.

The link to the repository is here: http://code.marcocantu.com/p/marcodelphibooks/source/tree/HEAD/

In the delphi2010handbook, chapter 05 (Win7Libraries) is the example source.

The method used in that demo is basically the use of the Windows API that has already been mentioned, the demo confirms that the Library files are indeed of XML format.


Additionally I found the following information which is quite useful:

  • SHAddFolderPathToLibrary - Adds a folder to a library.
  • SHCreateLibrary IShellLibrary - Creates an object.
  • SHLoadLibraryFromItem - Creates and loads IShellLibrary to object from a specified library definition file.
  • SHLoadLibraryFromKnownFolder - Creates and loads of IShellLibrary object for a specified KNOWNFOLDERID.
  • SHLoadLibraryFromParsingName - Creates and loads of IShellLibrary object for a specified path.
  • SHRemoveFolderPathFromLibrary - Removes a folder from a library.
  • SHResolveFolderPathInLibrary - Attempts to resolve the target location of a library folder that has been moved or renamed.
  • SHResolveLibrary - Attempts to find the location of a library.
  • SHSaveLibraryInFolderPath - Saves on IShellLibrary object to disk.
  • SHShowManageLibraryUI - Shows the library management dialog, Which Enables users to manage the library folders and default save location.