特定のプロセスが開いているファイルの一覧を取得する

そんなん、下の奴を見れば終わり:

っていう訳にもいきませぬ。
上の奴からたどれる範囲のコードでは、

32bit PID値に未対応

SYSTEM_HANDLE ではなく、SYSTEM_EXTENDED_HANDLE を使う。

ネットワーク共有上のファイルに未対応

\Device\Mup\ から始まるファイル名を適切に処理する。

っていう修正が必要です。C#だとポインタとかで割と死ねます。

ソース

次の様な使い方を想定:

foreach (var fn in ProcessUtils.EnumFilesOpened(hwnd))
{
  Console.WriteLine(fn);
}

全ソース。あんまりコメントなし:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace Handles
{
  public static class ProcessUtils
  {
    // HOWTO: Enumerate handles
    // http://forum.sysinternals.com/howto-enumerate-handles_topic18892.html
    public static IEnumerable<string> EnumFilesOpened(IntPtr hwnd)
    {
      uint pid;
      GetWindowThreadProcessId(hwnd, out pid);
      using (var proc = Process.GetProcessById((int)pid))
      {
        IntPtr hProcess = proc.Handle;
        foreach (var shi in EnumHandles((int)pid))
        {
          IntPtr hObj = IntPtr.Zero;
          try
          {
            if (!NT_SUCCESS(NtDuplicateObject(hProcess,
                             shi.HandleValue,
                             Process.GetCurrentProcess().Handle,
                             out hObj, 0, 0, 0)))
            {
              continue;
            }

            using (var nto1 = new NtObject(hObj, ObjectInformationClass.ObjectTypeInformation, typeof(OBJECT_TYPE_INFORMATION)))
            {
              var oti = ObjectTypeInformationFromBuffer(nto1.Buffer);
              if (oti.Name.ToString() != "File")
                continue;
            }

            //  this can lock NtQueryInformationFile (blocking IO named pipes), and 
            //  sometimes also NtQueryObject as well.
            //  It is recommended to skip this granted access, 
            //  although a correct explanation is still to be found.
            //
            if (shi.GrantedAccess == 0x0012019f   // seems mandatory (named pipes)
              || shi.GrantedAccess == 0x001a019f   // blocking named pipe (not a file anyways)

              // Ignore certain flags combinations that are 
              // frequently associated with system files and
              // folders. This gives us a large performance
              // advantage at the cost of some open file handles
              // potentially being missed. Since we don't have
              // control over what happens shortly after, we
              // cannot be perfect anyway.

              || shi.GrantedAccess == 0x00100000   // SYNCHRONIZE only
              || shi.GrantedAccess == 0x00160001   // used on directories
              || shi.GrantedAccess == 0x00100001   // used on directories
              || shi.GrantedAccess == 0x00100020)  // used on SxS files
            {
              continue;
            }

            using (var nto2 = new NtObject(hObj, ObjectInformationClass.ObjectNameInformation, typeof(OBJECT_NAME_INFORMATION)))
            {
              var oni = ObjectNameInformationFromBuffer(nto2.Buffer);
              yield return GetRegularFileNameFromDevice(oni.Name.ToString());
            }
          }
          finally
          {
            CloseHandle(hObj);
          }

        }
      }
    }

    /// <summary>
    /// Works much like as <c>(OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_TYPE_INFORMATION))</c>
    /// </summary>
    /// <param name="buffer">Pointer to byte buffer of OBJECT_TYPE_INFORMATION.</param>
    /// <returns>C# interpretation of OBJECT_TYPE_INFORMATION; it contains references to the buffer
    /// and the buffer should not be released until finishing access to the structure.</returns>
    static OBJECT_TYPE_INFORMATION ObjectTypeInformationFromBuffer(IntPtr buffer)
    {
#if USE_SAFE_CODE_ONLY
      return (OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_TYPE_INFORMATION));
#else
      unsafe { return *(OBJECT_TYPE_INFORMATION*)buffer.ToPointer(); }
#endif
    }

    /// <summary>
    /// Works much like as <c>(OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_NAME_INFORMATION))</c>
    /// </summary>
    /// <param name="buffer">Pointer to byte buffer of OBJECT_NAME_INFORMATION.</param>
    /// <returns>C# interpretation of OBJECT_NAME_INFORMATION; it contains references to the buffer
    /// and the buffer should not be released until finishing access to the structure.</returns>
    static OBJECT_NAME_INFORMATION ObjectNameInformationFromBuffer(IntPtr buffer)
    {
#if USE_SAFE_CODE_ONLY
      return (OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_NAME_INFORMATION));
#else
      unsafe { return *(OBJECT_NAME_INFORMATION*)buffer.ToPointer(); }
#endif
    }

    class NtObject : IDisposable
    {
      public NtObject(IntPtr hObj, ObjectInformationClass infoClass, Type type)
      {
        Init(hObj, infoClass, Marshal.SizeOf(type));
      }

      public NtObject(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize)
      {
        Init(hObj, infoClass, estimatedSize);
      }

      public void Init(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize)
      {
        Close();

        // NOTE:
        // Buffer may be referenced by certain fields in m_obj and should not be
        // released before releasing m_obj.
        Buffer = Query(hObj, infoClass, estimatedSize);
      }

      public void Close()
      {
        if (Buffer != IntPtr.Zero)
        {
          Marshal.FreeCoTaskMem(Buffer);
          Buffer = IntPtr.Zero;
        }
      }

      public void Dispose()
      {
        Close();
      }

      /// <summary>
      /// Return the buffer. You can use <see cref="Marshal.PtrToStructure"/> to get the actual
      /// structure from the buffer.
      /// Basically I want to provide NTObject&lt;T&gt; but C# generics does not allow me to
      /// use T* pointer if T is a generic parameter.
      /// </summary>
      public IntPtr Buffer { get; private set; }

      public static IntPtr Query(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize)
      {
        int size = estimatedSize;
        IntPtr buf = Marshal.AllocCoTaskMem(size);
        int retsize = 0;
        while (true)
        {
          var ret = NtQueryObject(hObj, infoClass, buf, size, out retsize);
          if (NT_SUCCESS(ret))
            return buf;
          if (ret == NT_STATUS.INFO_LENGTH_MISMATCH || ret == NT_STATUS.BUFFER_OVERFLOW)
          {
            buf = Marshal.ReAllocCoTaskMem(buf, retsize);
            size = retsize;
          }
          else
          {
            Marshal.FreeCoTaskMem(buf);
            return IntPtr.Zero;
          }
        }
      }
    }

    static readonly string NETWORK_PREFIX = @"\Device\Mup\";

    static string GetRegularFileNameFromDevice(string strRawName)
    {
      if (strRawName.StartsWith(NETWORK_PREFIX))
        return @"\\" + strRawName.Substring(NETWORK_PREFIX.Length);

      string strFileName = strRawName;
      foreach (var drvPath in Environment.GetLogicalDrives())
      {
        var drv = drvPath.Substring(0, 2);
        var sb = new StringBuilder(MAX_PATH);
        if (QueryDosDevice(drv, sb, MAX_PATH) == 0)
          return strRawName;

        string drvRoot = sb.ToString();
        if (strFileName.StartsWith(drvRoot))
        {
          strFileName = drv + strFileName.Substring(drvRoot.Length);
          break;
        }
      }
      return strFileName;
    }

    static SYSTEM_EXTENDED_HANDLE SystemExtendedHandleFromPtr(IntPtr ptr, int offset)
    {
#if USE_SAFE_CODE_ONLY
      return (SYSTEM_EXTENDED_HANDLE)Marshal.PtrToStructure(buffer.Offset(offset), typeof(SYSTEM_EXTENDED_HANDLE));
#else
      unsafe
      {
        var p = (byte*)ptr.ToPointer() + offset;
        return *(SYSTEM_EXTENDED_HANDLE*)p;
      }
#endif
    }

    static int lastSizeUsed = 0x10000;

    static IEnumerable<SYSTEM_EXTENDED_HANDLE> EnumHandles(int processId)
    {
      int size = lastSizeUsed;
      IntPtr buffer = Marshal.AllocCoTaskMem(size);
      try
      {
        int required;
        while (NtQuerySystemInformation(SystemExtendedHandleInformation, buffer, size, out required) == NT_STATUS.INFO_LENGTH_MISMATCH)
        {
          size = required;
          buffer = Marshal.ReAllocCoTaskMem(buffer, size);
        }

        // FIXME: it should be race condition safe...
        if (lastSizeUsed < size)
          lastSizeUsed = size;

        // sizeof(SYSTEM_HANDLE) is 16 on 32-bit and 42 on 64-bit due to padding issues
        int entrySize = Marshal.SizeOf(typeof(SYSTEM_EXTENDED_HANDLE));
        int offset = Marshal.SizeOf(typeof(IntPtr)) * 2;
        int handleCount = Marshal.ReadInt32(buffer);

        for (int i = 0; i < handleCount; i++)
        {
          var shi = SystemExtendedHandleFromPtr(buffer, offset + entrySize * i);
          if (shi.UniqueProcessId != new IntPtr(processId))
            continue;

          yield return shi;
        }
      }
      finally
      {
        if (buffer != IntPtr.Zero)
          Marshal.FreeCoTaskMem(buffer);
      }
    }

    static string GetProcessCommandLine(uint processId)
    {
      IntPtr hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
        false, processId);
      if (hProcess == IntPtr.Zero)
        return string.Empty;

      try
      {
        return GetProcessCommandLine(hProcess);
      }
      catch
      {
        return string.Empty;
      }
      finally
      {
        CloseHandle(hProcess);
      }
    }

    static string GetProcessCommandLine(IntPtr hProcess)
    {
      int size;
      PROCESS_BASIC_INFORMATION pbi;
      int status = NtQueryInformationProcess(
        hProcess, PROCESSINFOCLASS.ProcessBasicInformation,
        out pbi, Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)),
        out size);
      int err = RtlNtStatusToDosError(status);
      if (err != 0)
        throw new Win32Exception(err);

      // PEB
      var peb = ObjectFromProcessMemory<PEB>(hProcess, pbi.PebBaseAddress);

      var upp = ObjectFromProcessMemory<RTL_USER_PROCESS_PARAMETERS>(hProcess, peb.ProcessParameters);
      if (upp.CommandLine.Length == 0)
        return string.Empty;
      return Encoding.Unicode.GetString(ReadProcessMemory(hProcess, upp.CommandLine.Buffer, upp.CommandLine.Length));
    }

    static Type ObjectFromProcessMemory<Type>(IntPtr hProcess, IntPtr lpBaseAddress) where Type : struct
    {
      return (Type)ObjectFromProcessMemory(hProcess, lpBaseAddress, typeof(Type));
    }

    static object ObjectFromProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, Type type)
    {
      int size = Marshal.SizeOf(type);
      return BufferToStructure(ReadProcessMemory(hProcess, lpBaseAddress, size), type);
    }

    static byte[] ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, int size)
    {
      byte[] buffer = new byte[size];
      IntPtr bytesRead;
      if (ReadProcessMemory(hProcess,
        lpBaseAddress,
        buffer,
        new IntPtr(size),
        out bytesRead))
      {
        if (bytesRead.ToInt32() < size)
          throw new ApplicationException(
            string.Format("Read only {0} of {1} bytes", bytesRead, size));
        return buffer;
      }
      throw new Win32Exception();
    }

    static object BufferToStructure(byte[] buffer, Type type)
    {
      unsafe
      {
        fixed (byte* p = buffer)
        {
          return Marshal.PtrToStructure(new IntPtr(p), type);
        }
      }
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_BASIC_INFORMATION
    {
      IntPtr Reserved1;
      public IntPtr PebBaseAddress; // PEB*
      IntPtr reserved2_0;
      IntPtr reserved2_1;
      public IntPtr UniqueProcessId; // ULONG_PTR
      IntPtr Reserved3;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PEB
    {
      IntPtr Reserved1;
      IntPtr Reserved2;
      IntPtr Reserved3;
      public IntPtr Ldr; // struct PEB_LDR_DATA*
      public IntPtr ProcessParameters; // RTL_USER_PROCESS_PARAMETERS*
      // ...
    }

    [StructLayout(LayoutKind.Sequential)]
    struct RTL_USER_PROCESS_PARAMETERS
    {
      byte b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15;
      IntPtr ip0, ip1, ip2, ip3, ip4, ip5, ip6, ip7, ip8, ip9;
      public UNICODE_STRING ImagePathName;
      public UNICODE_STRING CommandLine;
    }

    enum PROCESSINFOCLASS
    {
      ProcessBasicInformation = 0,
      ProcessDebugPort = 7,
      ProcessWow64Information = 26,
      ProcessImageFileName = 27,
    }

    [DllImport("ntdll.dll")]
    static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic,
    out PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize);

    [DllImport("ntdll.dll")]
    static extern int RtlNtStatusToDosError(int Status);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead);



    [DllImport("user32.dll")]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenProcess(
      uint dwDesiredAccess,
      bool bInheritHandle,
      uint dwProcessId);

    const uint PROCESS_DUP_HANDLE = 0x0040;
    const uint PROCESS_QUERY_INFORMATION = 0x0400U;
    const uint PROCESS_VM_READ = 0x0010U;

    [DllImport("kernel32.dll")]
    static extern int CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);

    const int MAX_PATH = 260;

    [DllImport("ntdll.dll")]
    static extern NT_STATUS NtQuerySystemInformation(
      int SystemInformationClass,
      IntPtr SystemInformation,
      int SystemInformationLength,
      out int ReturnLength);

    const int SystemHandleInformation = 16;

    struct SYSTEM_EXTENDED_HANDLE
    {
      public IntPtr Object;
      public IntPtr UniqueProcessId;
      public IntPtr HandleValue;
      public uint GrantedAccess;
      public ushort CreatorBackTraceIndex;
      public ushort ObjectTypeIndex;
      public uint HandleAttributes;
      public uint Reserved;
    }

    const int SystemExtendedHandleInformation = 64;

    enum NT_STATUS : uint
    {
      SUCCESS = 0x00000000,
      BUFFER_OVERFLOW = 0x80000005,
      INFO_LENGTH_MISMATCH = 0xC0000004
    }

    static bool NT_SUCCESS(NT_STATUS status)
    {
      return ((uint)status & 0x80000000) == 0;
    }

    [DllImport("ntdll.dll")]
    static extern NT_STATUS NtDuplicateObject(
      IntPtr SourceProcessHandle,
      IntPtr SourceHandle,
      IntPtr TargetProcessHandle,
      out IntPtr TargetHandle,
      uint DesiredAccess, uint Attributes, uint Options);

    [DllImport("ntdll.dll")]
    static extern NT_STATUS NtQueryObject(
      IntPtr ObjectHandle,
      ObjectInformationClass ObjectInformationClass,
      IntPtr ObjectInformation,
      int ObjectInformationLength,
      out int returnLength);

    enum ObjectInformationClass : int {
      ObjectBasicInformation = 0,
      ObjectNameInformation = 1,
      ObjectTypeInformation = 2,
      ObjectAllTypesInformation = 3,
      ObjectHandleInformation = 4
    }

    [StructLayout(LayoutKind.Sequential)]
    struct OBJECT_NAME_INFORMATION
    { // Information Class 1
      public UNICODE_STRING Name;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct UNICODE_STRING
    {
      // NOTE:
      // UNICODE_STRING should be 8 byte on 32-bit and 16 byte on 64-bit
      // and reserved will condition it correctly.
      private IntPtr reserved;
      public IntPtr Buffer;

      public ushort Length
      {
        get { return (ushort)(reserved.ToInt64() & 0xffff); }
      }
      public ushort MaximumLength
      {
        get { return (ushort)(reserved.ToInt64() >> 16); }
      }

      public override string ToString()
      {
        if (Buffer == IntPtr.Zero)
          return "";
        return Marshal.PtrToStringUni(Buffer, Wcslen());
      }

      /// <summary>
      /// Calculate string length in C's wcslen compatible way.
      /// </summary>
      /// <returns>Length of the string.</returns>
      public int Wcslen()
      {
        unsafe
        {
          ushort* p = (ushort*)Buffer.ToPointer();
          for (ushort i = 0; i < Length; i++)
          {
            if (p[i] == 0)
              return i;
          }
          return Length;
        }
      }

    }

    [StructLayout(LayoutKind.Sequential)]
    struct GENERIC_MAPPING
    {
      public int GenericRead;
      public int GenericWrite;
      public int GenericExecute;
      public int GenericAll;
    }
  
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct OBJECT_TYPE_INFORMATION
    { // Information Class 2
      public UNICODE_STRING Name;
      public uint TotalNumberOfObjects;
      public uint TotalNumberOfHandles;
      public uint TotalPagedPoolUsage;
      public uint TotalNonPagedPoolUsage;
      public uint TotalNamePoolUsage;
      public uint TotalHandleTableUsage;
      public uint HighWaterNumberOfObjects;
      public uint HighWaterNumberOfHandles;
      public uint HighWaterPagedPoolUsage;
      public uint HighWaterNonPagedPoolUsage;
      public uint HighWaterNamePoolUsage;
      public uint HighWaterHandleTableUsage;
      public uint InvalidAttributes;
      public GENERIC_MAPPING GenericMapping;
      public uint ValidAccess;
      public byte SecurityRequired;
      public byte MaintainHandleCount;
      public ushort MaintainTypeList;
      public int PoolType;
      public int PagedPoolUsage;
      public int NonPagedPoolUsage;
    }
  }

  /// <summary>
  /// Extension methods for <see cref="IntPtr"/>.
  /// </summary>
  static class Helper
  {
    /// <summary>
    /// Offset the specified pointer.
    /// </summary>
    /// <param name="ptr">Base pointer.</param>
    /// <param name="offset">Offset in bytes.</param>
    /// <returns>New pointer.</returns>
    public static IntPtr Offset(this IntPtr ptr, int offset)
    {
      return new IntPtr(ptr.ToInt64() + offset);
    }

    /// <summary>
    /// Size of <see cref="IntPtr"/> value.
    /// </summary>
    public static readonly int PTR_SIZE = Marshal.SizeOf(typeof(IntPtr));
  }
}