ボリュームがマウントされているパスの一覧を取得する

FileSystemWatcherとか、ReadDirectoryChangesWは、ファイルの更新とかを監視するには便利な関数ですが、これらでサブディレクトリを監視するようにしても、サブディレクトリがマウントポイントで、他のボリュームが接ぎ木されているような場合にはそのマウントポイントより下に関しては、監視が出来ません。そのため、隈無く監視の目を光らせるためには、すべてのマウントポイントを確認する必要があります。

ということで、その一覧を列挙する実験。

#include <windows.h>
#include <stdio.h>
#include <locale.h>
#include <tchar.h>

bool enumMountPoints(LPCTSTR volume)
{
  _tprintf(_T("%s\n"), buf);

  DWORD dwSize;
  if(!GetVolumePathNamesForVolumeName(volume, NULL, 0, &dwSize) && GetLastError() != ERROR_MORE_DATA)
    return false;
  
  TCHAR* buf = new TCHAR[dwSize];
  if(!GetVolumePathNamesForVolumeName(volume, buf, dwSize, &dwSize))
  {
    delete[] buf;
    return false;
  }
  
  static const LPCTSTR types[] = {_T("UNKNOWN"), _T("INVALID"), _T("REMOVABLE"), _T("FIXED"), _T("REMOTE"), _T("CDROM"), _T("RAMDISK")};
  
  for(TCHAR* p = buf; *p;)
  {
    size_t len = lstrlen(p);
    if(len)
    {
      DWORD type = GetDriveType(p);
      _tprintf(_T("  %s : %s\n"), (type <= DRIVE_RAMDISK) ? types[type] : _T("?"), p);
    }
    p += len;
  }
  delete[] buf;
  return true;
}

int _tmain(int argc, TCHAR* argv[])
{
  setlocale(LC_ALL, "");
  
  TCHAR buf[MAX_PATH];
  HANDLE hFind = FindFirstVolume(buf, MAX_PATH);
  if(hFind == INVALID_HANDLE_VALUE)
    return 0;
  
  do
  {
    enumMountPoints(buf);
  }
  while(FindNextVolume(hFind, buf, MAX_PATH));
  FindVolumeClose(hFind);
  
  return 0;
}

僕のマシンでの実行結果:

\\?\Volume{2bcfbee6-8376-11de-a1bf-806e6f6e6963}\
\\?\Volume{54a83329-9c55-11de-848d-806e6f6e6963}\
  FIXED : C:\work
\\?\Volume{2bcfbee7-8376-11de-a1bf-806e6f6e6963}\
  FIXED : C:\
\\?\Volume{bd99cfb5-837f-11de-8aca-00125a59038a}\
  FIXED : J:\
\\?\Volume{90f63e07-a90c-11de-bfbe-806e6f6e6963}\
  CDROM : D:\
\\?\Volume{938e7b17-8378-11de-88b6-00125a59038a}\
  CDROM : E:\

マウントポイントもちゃんと列挙されているし、GetDriveTypeでちゃんとマウントポイントの種類まで分かる。ちなみに、workにマウントされているのはSSD。最初のボリュームは、Windows 7のシステムパーティションか。

C#で全ボリューム(ただしFixedなものに限るを監視)

上記を踏まえた上で、全ボリュームを2分間監視するサンプル(新規に作成されたファイルonly)。

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;

public class WatchAllVolumes
{
  public static void Main(string[] args)
  {
    var wfList = new List<FileSystemWatcher>();
    
    foreach (string root in getAllMountPointsForFixedDrives())
    {
      var fsw = new FileSystemWatcher();
      fsw.Path = root;
      fsw.IncludeSubdirectories = true;
      fsw.Filter = "*";
      fsw.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.FileName | NotifyFilters.DirectoryName;
      fsw.Created += new FileSystemEventHandler(onCreated);
      fsw.Changed += new FileSystemEventHandler(onCreated);
      fsw.Renamed += new RenamedEventHandler(onCreated);
      fsw.EnableRaisingEvents = true;
      wfList.Add(fsw);
      Console.WriteLine("Watching: {0}", root);
    }
    
    // sleep for 2 minutes
    System.Threading.Thread.Sleep(1000 * 120);
    
    foreach (FileSystemWatcher fsw in wfList)
      fsw.Dispose();
  }
  
  static void onCreated(object sender, System.IO.FileSystemEventArgs e)
  {
    Console.WriteLine("{0}", e.FullPath);
  }
  
  static string[] getAllMountPointsForFixedDrives()
  {
    var mp = new List<string>();
    var sb = new StringBuilder(MAX_PATH);
    IntPtr hFind = FindFirstVolume(sb, MAX_PATH);
    if (hFind == INVALID_HANDLE_VALUE)
      return mp.ToArray();
    try
    {
      do
      {
        string volume = sb.ToString();
        if (GetDriveType(volume) == DriveType.Fixed)
        {
          string path = getFirstMountPointsForVolume(volume);
          if (!string.IsNullOrEmpty(path))
            mp.Add(path);
        }
        sb = new StringBuilder(MAX_PATH);
      }
      while(FindNextVolume(hFind, sb, MAX_PATH));
      return mp.ToArray();
    }
    finally
    {
      FindVolumeClose(hFind);
    }
  }
  
  private static string getFirstMountPointsForVolume(string volume)
  {
    int size;
    GetVolumePathNamesForVolumeName(volume, null, 0, out size);
    char[] buf = new char[size];
    GetVolumePathNamesForVolumeName(volume, buf, size, out size);
    
    foreach (string s in new string(buf).Split('\0'))
      if (!string.IsNullOrEmpty(s))
        return s; // return the first mount point
    return string.Empty;
  }
  
  static readonly int MAX_PATH = 260;
  static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
  
  enum DriveType
  {
    Unknown = 0,
    NoRootDir = 1,
    Removable = 2,
    Fixed = 3,
    Remote = 4,
    CdRom = 5,
    RamDisk = 6
  }
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  static extern DriveType GetDriveType(string lpRootPathName);
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  static extern IntPtr FindFirstVolume(System.Text.StringBuilder longPath, int bufSize);
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  static extern bool FindNextVolume(IntPtr hFind, System.Text.StringBuilder longPath, int bufSize);
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  static extern bool FindVolumeClose(IntPtr hFind);
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  static extern bool GetVolumePathNamesForVolumeName(string lpszVolumeName, char[] lpszVolumePathNames, int cchBuferLength, out int lpcchReturnLength);

}