3

Is there any way to programatically lookup the list of directories currently setup on Windows' Media Libraries?

E.g.: Supposing I have the following libraries (I apologize for the portuguese, but you'll get the idea):

enter image description here

How can I get programatically these three directories paths listed on the Video library?

D:\Filmes
D:\Series
D:\Videos

This question almost got me there, but its not exactly what I want. So far, my alternative is trying to look that up directly from the Windows Registry.

Community
  • 1
  • 1
everton
  • 7,579
  • 2
  • 29
  • 42

2 Answers2

5

Here it is, finally!

 using System.Runtime.InteropServices;
 using System.Diagnostics;
 using System.IO;
 using System.Xml;


 [DllImport("shell32.dll")]
 private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, ref IntPtr ppszPath);

 public void GetVideoLibraryFolders()
 {
     var pathPtr = default(IntPtr);
     var videoLibGuid = new Guid("491E922F-5643-4AF4-A7EB-4E7A138D8174");
     SHGetKnownFolderPath(videoLibGuid, 0, IntPtr.Zero, ref pathPtr);

     string path = Marshal.PtrToStringUni(pathPtr);
     Marshal.FreeCoTaskMem(pathPtr);
     List<string> foldersInLibrary = new List<string>();

     using (XmlReader reader = XmlReader.Create(path))
     {
         while (reader.ReadToFollowing("simpleLocation"))
         {
             reader.ReadToFollowing("url");
             foldersInLibrary.Add(reader.ReadElementContentAsString());
         }
     }

     for (int i = 0; i < foldersInLibrary.Count; i++)
     {
         if (foldersInLibrary[i].Contains("knownfolder"))
         {
             foldersInLibrary[i] = foldersInLibrary[i].Replace("knownfolder:{", "");
             foldersInLibrary[i] = foldersInLibrary[i].Replace("}", "");

             SHGetKnownFolderPath(new Guid(foldersInLibrary[i]), 0, IntPtr.Zero, ref pathPtr);
             foldersInLibrary[i] = Marshal.PtrToStringUni(pathPtr);
             Marshal.FreeCoTaskMem(pathPtr);
         }
     }

    // foldersInLibrary now contains the path to all folders in the Videos Library

 }

So, how did I do it?

First off, there's this function SHGetKnownFolderPath in the shell32.dll library, which returns the path of a folder provided its GUID (documentation). And there's also a list of GUIDs for each Known Folder on Windows.

"491E922F-5643-4AF4-A7EB-4E7A138D8174" is the ID for the Videos_Library folder.

But there's one problem! That function will return this path: %appdata%\Microsoft\Windows\Libraries\Videos.library-ms

If you try to access that folder with methods like Directory.GetDirectories you will get a DirectoryNotFoundException. What's wrong? Well, the problem is: Videos.library-ms isn't a folder! It's a XML file. If you open it with some text editor, you'll see.

After discovering that it was a XML file, then all I had to do was to read it and we would have the path to the directories. If you open the xml, you'll see that all the folders in a Library are under <simpleLocation> elements. So you simply have to read all the <simpleLocation> XML Elements and then their child element <url>, which content contains the path for the folder itself.

Although that could be the end, I luckily noticed that not every folder path is described as an usual path in the .library-ms file; some of them are described with GUID's (yes, those same I linked previously), and those have the knownfolder attribute in them. Thus, in the last for, I search for elements in the List of directories which have the knownfolder attribute in them. For each one found, I then replace their value with the correct one, by searching all over again to which path that GUID points using SHGetKnownFolderPath.

So, that's it!

andre_ss6
  • 1,195
  • 1
  • 13
  • 34
  • Almost there... That returns me the "My Videos" folder (and its subdirectories) under my User Folder. These media directories are not located there... – everton Dec 14 '14 at 01:19
  • So, as I understand, it's not returning you the "Filmes" and "Series" folders, but only the folders inside "Vídeos". Is that right? – andre_ss6 Dec 14 '14 at 01:25
  • Actually not. I've removed the "My Videos" folder from the "Videos" library at all. – everton Dec 14 '14 at 01:27
  • Well, I managed to get the directory path for the libraries, but unfortunately they're different from normal directory paths, so functions like Directory.EnumerateDirectories or Directory.GetDirectories aren't able to access them (they throw a DirectoryNotFoundException).If you want the code anyway I can post it. Also, I've discovered that Windows Store apps have a easy way for accessing those and that is through the KnownFolders class, so if you're working with a Store app, then there is no need for all this complication. – andre_ss6 Dec 14 '14 at 02:51
  • I think I came with the same solution as you, although I'll wait for your Edit so I can accept ;) – everton Dec 14 '14 at 03:56
2

For the future researchers' sake, here's the code I came up as well:

public class MediaLibraries
{
    private static readonly string LibrariesFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Microsoft\\Windows\\Libraries";
    private static readonly string VideosLibraryFileName = "Videos.library-ms";

    private static IEnumerable<DirectoryInfo> _videosDirectories;

    public static IEnumerable<DirectoryInfo> VideosDirectories
    {
        get
        {
            if (_videosDirectories != null)
                return _videosDirectories;

            _videosDirectories = new HashSet<DirectoryInfo>();

            var videosLibraryXmlFilePath = Path.Combine(LibrariesFolderPath, VideosLibraryFileName);

            if (!File.Exists(videosLibraryXmlFilePath))
                return _videosDirectories;

            XDocument videosLibraryXml = XDocument.Load(File.OpenRead(videosLibraryXmlFilePath));
            XNamespace ns = videosLibraryXml.Root.Name.Namespace;

            string[] videoFoldersPaths = videosLibraryXml.Root
                                                         .Element(ns + "searchConnectorDescriptionList")
                                                         .Elements(ns + "searchConnectorDescription")
                                                         .Select(scd => scd.Element(ns + "simpleLocation").Element(ns + "url").Value)
                                                         .ToArray();

            _videosDirectories = videoFoldersPaths.Select(v => new DirectoryInfo(v)).AsEnumerable();

            return _videosDirectories;
        }
    }
}

The idea is the same as @AndreSilva's answer, despite the fact that I hadn't have to go through interop.

Basically, I've composed the path for the Libraries folder, and then appended the file name for the Video Libraries XML. After that, it's just Linq2XML magic.

everton
  • 7,579
  • 2
  • 29
  • 42
  • Nice! Remember to check for `knownfolder`'s though. In Windows 8, the Documents library contains SkyDrive's Documents folder by default, which is a known folder and thus will be described by its GUID - and not its address - in the Documents.library-ms. – andre_ss6 Dec 14 '14 at 04:15
  • Good to know, although only the Videos library matters to me right now. Btw, it's for this: https://subsync.codeplex.com – everton Dec 14 '14 at 16:48
  • 1
    I got it now what you meant. Even for the Video libraries, windows sets up the default video folders using the GUIDs. I'll need to use your code to check them as well :) – everton Dec 15 '14 at 15:23