Posted at

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

More than 5 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でラッピングするためにも使っています。