C#

C#でゴミ箱の中身を列挙する(作りかけ)

More than 3 years have passed since last update.

はじめに

主産物です。Windows 7から実装されたSHGetKnownFolderIDList関数を用いてゴミ箱内のファイルの名前を列挙します。インターフェイスのコードはPInvoke.netをとても参考にしています。例外処理などは目をつぶっているので注意して下さい。

ソースコード

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;

namespace RecycleBin2
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] names = RecycleBin.GetItemNames(RecycleBin.ESHGDN.SHGDN_INFOLDER).ToArray();
        }
    }

    public sealed class SafeCoTaskMemHandleZeroIsInvalid : SafeHandle
    {
        private SafeCoTaskMemHandleZeroIsInvalid() : base(IntPtr.Zero, true) { }
        public SafeCoTaskMemHandleZeroIsInvalid(IntPtr handle, bool ownsHandle)
            : base(IntPtr.Zero, ownsHandle) { this.handle = handle; }

        public override bool IsInvalid
        {
            get { return handle == IntPtr.Zero; }
        }

        protected override bool ReleaseHandle()
        {
            Marshal.FreeCoTaskMem(handle);
            return true;
        }
    }

    /// <summary>
    /// システム全体のゴミ箱を操作します。
    /// </summary>
    [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
    public static class RecycleBin
    {
        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethod
        {
            [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
            public static extern void SHGetKnownFolderIDList(
                ref Guid rfid,
                uint dwFlags,
                IntPtr hToken,
                out SafeCoTaskMemHandleZeroIsInvalid ppidl);
            [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
            public static extern void SHGetDesktopFolder(out IShellFolder pshf);

            [DllImport("shlwapi.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = false)]
            public static extern void StrRetToStrW(
                ref STRRET pstr,
                IntPtr pidl,
                out IntPtr ppszName);
        }
        public static Guid KnownFolderID
        {
            get
            {
                return new Guid(
                    0xB7534046, 0x3ECB, 0x4C18, 0xBE, 0x4E, 0x64, 0xCD, 0x4C, 0xB7, 0xD6, 0xAC);
            }
        }
        public static SafeCoTaskMemHandleZeroIsInvalid GetItemIDList()
        {
            SafeCoTaskMemHandleZeroIsInvalid pidl;
            Guid guid = KnownFolderID;
            NativeMethod.SHGetKnownFolderIDList(
                ref guid, 0, IntPtr.Zero, out pidl);
            return pidl;
        }

        private static IShellFolder GetRecycleBin()
        {
            IShellFolder desktop;
            NativeMethod.SHGetDesktopFolder(out desktop);
            try
            {
                Guid shellFolderInterfaceId = typeof(IShellFolder).GUID;
                IntPtr recycleBinShellFolderInterfacePointer;
                desktop.BindToObject(
                    GetItemIDList().DangerousGetHandle(),
                    IntPtr.Zero,
                    ref shellFolderInterfaceId,
                    out recycleBinShellFolderInterfacePointer);
                return (IShellFolder)Marshal.GetObjectForIUnknown(
                    recycleBinShellFolderInterfacePointer);
            }
            finally
            {
                Marshal.FinalReleaseComObject(desktop);
            }
        }

        private static string STRRETToString(ref STRRET str, SafeCoTaskMemHandleZeroIsInvalid pidl)
        {
            IntPtr pointerToName;
            NativeMethod.StrRetToStrW(ref str,
                pidl.DangerousGetHandle(),
                out pointerToName);
            string s = Marshal.PtrToStringUni(pointerToName);
            Marshal.FreeCoTaskMem(pointerToName);
            return s;
        }

        public static IEnumerable<string> GetItemNames(ESHGDN gdn)
        {
            IShellFolder recycleBin = GetRecycleBin();
            try
            {
                IntPtr enumIDListInterfacePointer;
                recycleBin.EnumObjects(
                    IntPtr.Zero,
                    ESHCONTF.SHCONTF_NONFOLDERS | ESHCONTF.SHCONTF_FOLDERS,
                    out enumIDListInterfacePointer);
                using (var enumIDList = new IEnumIDListWrapper(enumIDListInterfacePointer))
                {
                    var names = new List<string>();
                    foreach (var pidl in enumIDList)
                    {
                        STRRET name;
                        recycleBin.GetDisplayNameOf(
                            pidl.DangerousGetHandle(),
                            gdn,
                            out name);
                        names.Add(STRRETToString(ref name, pidl));
                        if (name.uType != (int)STRRETType.Offset)
                            Marshal.FreeCoTaskMem(name.data.pOleStr);
                    }
                    return names;
                }
            }
            finally
            {
                Marshal.FinalReleaseComObject(recycleBin);
            }
        }

        #region IShellFolder
        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("000214E6-0000-0000-C000-000000000046")]
        private interface IShellFolder
        {
            void ParseDisplayName(IntPtr hwnd, IntPtr pbc, String pszDisplayName, UInt32 pchEaten, out IntPtr ppidl, UInt32 pdwAttributes);
            void EnumObjects(IntPtr hwnd, ESHCONTF grfFlags, out IntPtr ppenumIDList);
            void BindToObject(IntPtr pidl, IntPtr pbc, [In]ref Guid riid, out IntPtr ppv);
            void BindToStorage(IntPtr pidl, IntPtr pbc, [In]ref Guid riid, out IntPtr ppv);
            Int32 CompareIDs(Int32 lParam, IntPtr pidl1, IntPtr pidl2);
            void CreateViewObject(IntPtr hwndOwner, [In] ref Guid riid, out IntPtr ppv);
            void GetAttributesOf(UInt32 cidl, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]IntPtr[] apidl, ref ESFGAO rgfInOut);
            void GetUIObjectOf(IntPtr hwndOwner, UInt32 cidl, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]IntPtr[] apidl, [In] ref Guid riid, UInt32 rgfReserved, out IntPtr ppv);
            void GetDisplayNameOf(IntPtr pidl, ESHGDN uFlags, out STRRET pName);
            void SetNameOf(IntPtr hwnd, IntPtr pidl, String pszName, ESHCONTF uFlags, out IntPtr ppidlOut);

        }

        public enum ESFGAO : uint
        {
            SFGAO_CANCOPY = 0x00000001,
            SFGAO_CANMOVE = 0x00000002,
            SFGAO_CANLINK = 0x00000004,
            SFGAO_LINK = 0x00010000,
            SFGAO_SHARE = 0x00020000,
            SFGAO_READONLY = 0x00040000,
            SFGAO_HIDDEN = 0x00080000,
            SFGAO_FOLDER = 0x20000000,
            SFGAO_FILESYSTEM = 0x40000000,
            SFGAO_HASSUBFOLDER = 0x80000000,
        }

        public enum ESHCONTF
        {
            SHCONTF_FOLDERS = 0x0020,
            SHCONTF_NONFOLDERS = 0x0040,
            SHCONTF_INCLUDEHIDDEN = 0x0080,
            SHCONTF_INIT_ON_FIRST_NEXT = 0x0100,
            SHCONTF_NETPRINTERSRCH = 0x0200,
            SHCONTF_SHAREABLE = 0x0400,
            SHCONTF_STORAGE = 0x0800
        }

        public enum ESHGDN
        {
            SHGDN_NORMAL = 0x0000,
            SHGDN_INFOLDER = 0x0001,
            SHGDN_FOREDITING = 0x1000,
            SHGDN_FORADDRESSBAR = 0x4000,
            SHGDN_FORPARSING = 0x8000,
        }

        public enum STRRETType : int
        {
            WideString = 0x0000,
            Offset = 0x0001,
            CString = 0x0002
        }

        [StructLayout(LayoutKind.Explicit, Size = 520)]
        public struct STRRETinternal
        {
            [FieldOffset(0)]
            public IntPtr pOleStr;
            [FieldOffset(0)]
            public IntPtr pStr;
            [FieldOffset(0)]
            public uint uOffset;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct STRRET
        {
            public uint uType;
            public STRRETinternal data;
        }
        #endregion
    }

    public class IEnumIDListWrapper : IDisposable, IEnumerable<SafeCoTaskMemHandleZeroIsInvalid>
    {
        private IEnumIDList enumIDList;
        public IEnumIDListWrapper(IntPtr pointerToIUnknown)
        {
            enumIDList = (IEnumIDList)Marshal.GetObjectForIUnknown(pointerToIUnknown);
        }
        public IEnumIDListWrapper(IEnumIDListWrapper wrapper)
        {
            IntPtr pointerToIUnknown;
            wrapper.enumIDList.Clone(out pointerToIUnknown);
            enumIDList = (IEnumIDList)Marshal.GetObjectForIUnknown(pointerToIUnknown);
        }
        ~IEnumIDListWrapper()
        {
            Dispose();
        }
        public void Dispose()
        {
            Marshal.FinalReleaseComObject(enumIDList);
            GC.SuppressFinalize(this);
        }


        public IEnumerator<SafeCoTaskMemHandleZeroIsInvalid> GetEnumerator()
        {
            const int S_OK = 0;

            enumIDList.Reset();

            uint hr = 0;
            IntPtr pidl;
            uint fetched;
            while ((hr = enumIDList.Next(1, out pidl, out fetched)) == S_OK)
            {
                yield return new SafeCoTaskMemHandleZeroIsInvalid(pidl, true);
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("000214F2-0000-0000-C000-000000000046")]
    public interface IEnumIDList
    {
        [PreserveSig]
        uint Next(uint celt, out IntPtr rgelt, out uint pceltFetched);
        [PreserveSig]
        uint Skip(uint celt);
        [PreserveSig]
        uint Reset();
        [PreserveSig]
        uint Clone(out IntPtr ppenum);
    }
}

各クラスの機能

Program

main関数を含むクラスです。テストの為に存在しています。

SafeCoTaskMemHandleZeroIsInvalid

Dispose時にMarshal.FreeCoTaskMem関数を呼び出すSafeHandleの実装クラスです。LPITEMIDLISTを解放するために使っています。

RecycleBin

ゴミ箱を操作する機能を提供するクラスです。内部に私的に使用するIShellFolderインターフェイスや今後名前を変更するだろう列挙体(PInvoke.netを参考にしました)を含んでいます。メインのクラスです。

IEnumIDListWrapper

IEnumIDListインターフェイスをIDisposableとして扱ったり、GetEnumeratorメソッドを提供するためのクラスです。yield returnするのであれば外側のforeachで毎回FreeCoTaskMemしてもらっても良かったのですが、念の為LPITEMIDLISTを上記SafeCoTaskMemHandleZeroIsInvalidでラッピングするためにも使っています。