I'm trying to copy files from a network stream to the local disk (with C#). I want to use the IFileOperation interface to get the modern copy UI, however I'm not sure how to get an IShellItem for a file that does not yet exist.
Currently I'm using the older IOperationsProgressDialog, which does work, however I'm wanting the Shell to deal with asking the user to overwrite files, permission issues, etc.
How do I create an IShellItem from an IStream to use in IFileOperation::Copy()?
IShellItem is a high level abstraction that's implemented by the Shell to avoid dealing with old interfaces like IShellFolder (which are not easy to work with). The problem is if you want to create a "virtual" one, you do need an IShellFolder somehow.
That's what the code below does, creates a simple IShellFolder implementation just to ensure the Shell can create a "virtual" IShellItem over it.
Exemple of console app (note the STAThread mandatory for IFileOperation to work)
[STAThread]
static void Main()
{
// prepare an item with a name
var size = 50 * 200000;
var item = new VirtualItem("test.txt", size, new DateTime(1966, 3, 24));
item.Read += (s, e) =>
{
if (size == 0) // finished?
return;
// in this sample, we dont put anything in the buffer
// we just wait to force the Shell progress dialog to show
var written = Math.Min(e.Bytes.Length, size);
e.Written = written;
Thread.Sleep(100);
size -= written;
};
// get source item as an IShellItem
ShellNative.SHCreateItemWithParent(IntPtr.Zero, item.VirtualFolder, item.Pidl, typeof(ShellNative.IShellItem).GUID, out var source);
// get some target folder as an IShellItem
ShellNative.SHCreateItemFromParsingName(#"c:\temp", null, typeof(ShellNative.IShellItem).GUID, out var target);
// create IFileOperation and ask to copy
var fo = (ShellNative.IFileOperation)new ShellNative.FileOperation();
fo.CopyItem((ShellNative.IShellItem)Marshal.GetObjectForIUnknown(source), (ShellNative.IShellItem)Marshal.GetObjectForIUnknown(target), null, IntPtr.Zero);
// if you dont use of of these and the target file already exists
// the whole process will fail for some reason
const uint FOFX_DONTDISPLAYSOURCEPATH = 0x04000000;
const uint FOFX_DONTDISPLAYLOCATIONS = 0x80000000;
fo.SetOperationFlags(FOFX_DONTDISPLAYLOCATIONS);
// do it
fo.PerformOperations();
}
Note that if the target file already exists, you must set at least FOFX_DONTDISPLAYSOURCEPATH or FOFX_DONTDISPLAYLOCATIONS option otherwise the system will fail. The first one will show a dialog like this (where clicking on the compare choice does nothing):
And the second one will show a dialog like this (and you can customize what's shown as "My Path of 'text.txt'":
And the classes with all Shell interop:
public sealed class VirtualItem : ShellNative.IPropertyStore, IStream, IDisposable
{
private readonly ComMemory _pidl;
public event EventHandler<ReadEventArgs> Read;
public VirtualItem(string name, long? size = null, DateTime? dateModified = null)
{
ArgumentNullException.ThrowIfNull(name);
Name = name;
VirtualFolder = new VirtualShellFolder(this);
_pidl = new ComMemory(2); // 4 bytes starting with 2 is the most simple non-terminator PIDL
Size = size; // if size is unspecified, the dialog will work but info will be wrong
DateModified = dateModified;
}
public string Name { get; }
public long? Size { get; }
public DateTime? DateModified { get; }
public ShellNative.IShellFolder VirtualFolder { get; }
public IntPtr Pidl => _pidl.Pointer;
public void Dispose() => _pidl.Dispose();
public int GetValue(ref ShellNative.PROPERTYKEY key, IntPtr pv)
{
if (key.Equals(ShellNative.PROPERTYKEY.PKEY_FileName))
{
ShellNative.WritePropVariant(Name, pv);
return 0;
}
if (key.Equals(ShellNative.PROPERTYKEY.PKEY_ItemPathDisplay))
{
// this will be used when FOFX_DONTDISPLAYLOCATIONS is set in SetOperationFlags
ShellNative.WritePropVariant(#"The Path of '" + Name + "'", pv);
return 0;
}
// this is mostly used for the modified date
if (key.Equals(ShellNative.PROPERTYKEY.PKEY_FindData))
{
var findData = new ShellNative.WIN32_FIND_DATAW();
if (DateModified.HasValue)
{
findData.ftLastWriteTime = ShellNative.ToFILETIME(DateModified.Value);
}
if (Size.HasValue)
{
findData.nFileSizeLow = (uint)(Size.Value & uint.MaxValue);
findData.nFileSizeHigh = (uint)(Size.Value >> 32);
}
findData.cFileName = Name;
using var mem = new ComMemory(findData);
ShellNative.WritePropVariant(mem, pv);
return 0;
}
// shell scans this to determine a root folder
if (key.Equals(ShellNative.PROPERTYKEY.PKEY_Volume_IsRoot))
{
ShellNative.WritePropVariant(true, pv);
return 0;
}
if (key.Equals(ShellNative.PROPERTYKEY.PKEY_Size) && Size.HasValue)
{
ShellNative.WritePropVariant(Size.Value, pv);
return 0;
}
if (key.Equals(ShellNative.PROPERTYKEY.PKEY_DateModified) && DateModified.HasValue)
{
ShellNative.WritePropVariant(DateModified.Value, pv);
return 0;
}
const int VT_EMPTY = 0;
Marshal.WriteInt16(pv, VT_EMPTY);
return 0;
}
void IStream.Read(byte[] pv, int cb, IntPtr pcbRead)
{
var evt = new ReadEventArgs(pv);
Read?.Invoke(this, evt);
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt32(pcbRead, evt.Written);
}
}
void IStream.Stat(out STATSTG pstatstg, int grfStatFlag)
{
pstatstg = new()
{
cbSize = Size ?? -1
};
}
int ShellNative.IPropertyStore.GetCount(out int cProps) => throw new NotImplementedException();
int ShellNative.IPropertyStore.GetAt(int iProp, out ShellNative.PROPERTYKEY pkey) => throw new NotImplementedException();
int ShellNative.IPropertyStore.SetValue(ref ShellNative.PROPERTYKEY key, IntPtr propvar) => throw new NotImplementedException();
int ShellNative.IPropertyStore.Commit() => throw new NotImplementedException();
void IStream.Clone(out IStream ppstm) => throw new NotImplementedException();
void IStream.Commit(int grfCommitFlags) => throw new NotImplementedException();
void IStream.CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) => throw new NotImplementedException();
void IStream.LockRegion(long libOffset, long cb, int dwLockType) => throw new NotImplementedException();
void IStream.Revert() => throw new NotImplementedException();
void IStream.Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) => throw new NotImplementedException();
void IStream.SetSize(long libNewSize) => throw new NotImplementedException();
void IStream.UnlockRegion(long libOffset, long cb, int dwLockType) => throw new NotImplementedException();
void IStream.Write(byte[] pv, int cb, IntPtr pcbWritten) => throw new NotImplementedException();
private sealed class VirtualShellFolder :
ShellNative.IShellFolder2,
ShellNative.IPersistIDList,
ShellNative.IPersistFolder,
ShellNative.IParentAndItem,
ShellNative.IPropertyStoreFactory
{
private readonly VirtualItem _item;
public VirtualShellFolder(VirtualItem item)
{
_item = item;
}
public int BindToObject(IntPtr pidl, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv)
{
if (riid == typeof(IStream).GUID)
return ShellNative.QueryInterface(_item, riid, out ppv);
return ShellNative.QueryInterface(this, riid, out ppv);
}
public int GetAttributesOf(int cidl, IntPtr apidl, ref int rgfInOut)
{
// our item is only a stream
rgfInOut = ShellNative.SFGAO_STREAM;
return 0;
}
public int GetDisplayNameOf(IntPtr pidl, ShellNative.SHGDNF uFlags, IntPtr pName)
{
// we dont honor full parsing name because ... we can't and that's probably why it fails in case of target file already exists
ShellNative.WriteSTRRETString(pName, _item.Name);
return 0;
}
public int GetIDList(out IntPtr ppidl)
{
ppidl = ShellNative.ILClone(_item.Pidl);
return 0;
}
public int GetParentAndItem(IntPtr ppidlParent, IntPtr ppsf, IntPtr ppidlChild)
{
if (ppsf != IntPtr.Zero)
{
ShellNative.QueryInterface(this, typeof(ShellNative.IShellFolder).GUID, out var ppv);
Marshal.WriteIntPtr(ppsf, ppv);
}
if (ppidlChild != IntPtr.Zero)
{
var ppidl = ShellNative.ILClone(_item.Pidl);
Marshal.WriteIntPtr(ppidlChild, ppidl);
}
return 0;
}
public int GetDetailsEx(IntPtr pidl, ref ShellNative.PROPERTYKEY pscid, IntPtr pv) => _item.GetValue(ref pscid, pv);
public int GetPropertyStore(int flags, IntPtr pUnkFactory, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv) => ShellNative.QueryInterface(_item, riid, out ppv);
public int GetPropertyStoreForKeys(IntPtr rgKeys, int cKeys, int flags, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv) => ShellNative.QueryInterface(_item, riid, out ppv);
public int Initialize(IntPtr pidl) => throw new NotImplementedException();
public int ParseDisplayName(IntPtr hwnd, IBindCtx pbc, [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, out int pchEaten, out IntPtr ppidl, IntPtr pdwAttributes) => throw new NotImplementedException();
public int EnumObjects(IntPtr hwnd, int grfFlags, out IntPtr ppenumIDList) => throw new NotImplementedException();
public int CompareIDs(long lParam, IntPtr pidl1, IntPtr pidl2) => throw new NotImplementedException();
public int SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, ShellNative.SHGDNF uFlags, out IntPtr ppidlOut) => throw new NotImplementedException();
public int GetDefaultSearchGUID(out Guid pguid) => throw new NotImplementedException();
public int EnumSearches(out IntPtr ppenum) => throw new NotImplementedException();
public int GetDefaultColumn(int dwRes, out int pSort, out int pDisplay) => throw new NotImplementedException();
public int GetDefaultColumnState(int iColumn, out int pcsFlags) => throw new NotImplementedException();
public int SetParentAndItem(IntPtr pidlParent, ShellNative.IShellFolder psf, IntPtr pidlChild) => throw new NotImplementedException();
public int SetIDList(IntPtr pidl) => throw new NotImplementedException();
public int BindToStorage(IntPtr pidl, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv) => throw new NotImplementedException();
public int CreateViewObject(IntPtr hwndOwner, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv) => throw new NotImplementedException();
public int GetDetailsOf(IntPtr pidl, int iColumn, IntPtr psd) => throw new NotImplementedException();
public int MapColumnToSCID(int iColumn, ref ShellNative.PROPERTYKEY pscid) => throw new NotImplementedException();
public int GetClassID(out Guid pClassID) => throw new NotImplementedException();
public int GetUIObjectOf(IntPtr hwndOwner, int cidl, IntPtr apidl, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, IntPtr rgfReserved, out IntPtr ppv) => throw new NotImplementedException();
}
}
public sealed class ReadEventArgs : EventArgs
{
public ReadEventArgs(byte[] bytes)
{
Bytes = bytes;
}
public byte[] Bytes { get; }
public int Written { get; set; }
}
public sealed class ComMemory : IDisposable
{
private IntPtr _pointer;
public ComMemory(object structure) { Size = Marshal.SizeOf(structure); _pointer = Marshal.AllocCoTaskMem(Size); Marshal.StructureToPtr(structure, _pointer, false); }
public IntPtr Pointer => _pointer;
public int Size { get; }
public void Dispose() { var handle = Interlocked.Exchange(ref _pointer, IntPtr.Zero); if (handle != IntPtr.Zero) Marshal.FreeCoTaskMem(handle); }
}
public static class ShellNative
{
public static void WriteSTRRETString(IntPtr ptr, string name)
{
if (ptr == IntPtr.Zero)
return;
// implicit STRRET_TYPE is 0 => STRRET_WSTR
ZeroMemory(ptr, IntPtr.Size == 8 ? Marshal.SizeOf<STRRET_64>() : Marshal.SizeOf<STRRET_32>());
if (name == null)
return;
var strPtr = Marshal.StringToCoTaskMemUni(name);
// skip uType wich size + padding = 4 or 8
Marshal.WriteIntPtr(ptr + IntPtr.Size, strPtr);
}
public static FILETIME ToFILETIME(DateTime dt) { var ft = dt.ToFileTime(); return new FILETIME { dwLowDateTime = (int)(ft & uint.MaxValue), dwHighDateTime = (int)(ft >> 32) }; }
public static int QueryInterface(object obj, Guid iid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
if (obj == null)
return E_NOINTERFACE;
var unk = Marshal.GetIUnknownForObject(obj);
try
{
return Marshal.QueryInterface(unk, ref iid, out ppv);
}
finally
{
Marshal.Release(unk);
}
}
public static void WritePropVariant(object obj, IntPtr pv)
{
if (obj is string s)
{
var ptr = Marshal.StringToCoTaskMemUni(s);
Marshal.WriteIntPtr(pv + 8, ptr);
const int VT_LPWSTR = 31;
Marshal.WriteInt16(pv, VT_LPWSTR);
return;
}
if (obj is bool b)
{
Marshal.WriteInt16(pv + 8, (short)(b ? -1 : 0));
const int VT_BOOL = 11;
Marshal.WriteInt16(pv, VT_BOOL);
return;
}
if (obj is long l)
{
Marshal.WriteInt64(pv + 8, l);
const int VT_UI8 = 21;
Marshal.WriteInt16(pv, VT_UI8);
return;
}
if (obj is DateTime dt)
{
InitPropVariantFromFileTime(dt.ToFileTime(), pv);
return;
}
if (obj is ComMemory mem)
{
var hr = InitPropVariantFromBuffer(mem.Pointer, mem.Size, pv);
return;
}
throw new NotSupportedException();
}
[DllImport("kernel32", EntryPoint = "RtlZeroMemory")]
public static extern void ZeroMemory(IntPtr address, IntPtr length);
public static void ZeroMemory(IntPtr address, int length) => ZeroMemory(address, (IntPtr)length);
[DllImport("shell32")]
public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[DllImport("shell32")]
public static extern int SHCreateItemWithParent(IntPtr pidlParent, IShellFolder psfParent, IntPtr pidl, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[DllImport("propsys")]
public static extern int InitPropVariantFromBuffer(IntPtr pv, int cb, IntPtr ppropvar);
[DllImport("propsys")]
public static extern int InitPropVariantFromFileTime([MarshalAs(UnmanagedType.LPStruct)] long pftIn, IntPtr ppropvar);
[DllImport("shell32")]
public static extern IntPtr ILClone(IntPtr pidl);
[ComImport, Guid("000214e6-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellFolder
{
[PreserveSig] int ParseDisplayName(IntPtr hwnd, IBindCtx pbc, [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, out int pchEaten, out IntPtr ppidl, IntPtr pdwAttributes);
[PreserveSig] int EnumObjects(IntPtr hwnd, int grfFlags, out IntPtr ppenumIDList);
[PreserveSig] int BindToObject(IntPtr pidl, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] int BindToStorage(IntPtr pidl, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] int CompareIDs(long lParam, IntPtr pidl1, IntPtr pidl2);
[PreserveSig] int CreateViewObject(IntPtr hwndOwner, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] int GetAttributesOf(int cidl, IntPtr apidl, ref int rgfInOut);
[PreserveSig] int GetUIObjectOf(IntPtr hwndOwner, int cidl, IntPtr apidl, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, IntPtr rgfReserved, out IntPtr ppv);
[PreserveSig] int GetDisplayNameOf(IntPtr pidl, SHGDNF uFlags, IntPtr pName);
[PreserveSig] int SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, SHGDNF uFlags, out IntPtr ppidlOut);
}
[ComImport, Guid("93f2f68c-1d1b-11d3-a30e-00c04f79abd1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellFolder2 : IShellFolder
{
[PreserveSig] new int ParseDisplayName(IntPtr hwnd, IBindCtx pbc, [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, out int pchEaten, out IntPtr ppidl, IntPtr pdwAttributes);
[PreserveSig] new int EnumObjects(IntPtr hwnd, int grfFlags, out IntPtr ppenumIDList);
[PreserveSig] new int BindToObject(IntPtr pidl, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] new int BindToStorage(IntPtr pidl, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] new int CompareIDs(long lParam, IntPtr pidl1, IntPtr pidl2);
[PreserveSig] new int CreateViewObject(IntPtr hwndOwner, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] new int GetAttributesOf(int cidl, IntPtr apidl, ref int rgfInOut);
[PreserveSig] new int GetUIObjectOf(IntPtr hwndOwner, int cidl, IntPtr apidl, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, IntPtr rgfReserved, out IntPtr ppv);
[PreserveSig] new int GetDisplayNameOf(IntPtr pidl, SHGDNF uFlags, IntPtr pName);
[PreserveSig] new int SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, SHGDNF uFlags, out IntPtr ppidlOut);
[PreserveSig] int GetDefaultSearchGUID(out Guid pguid);
[PreserveSig] int EnumSearches(out IntPtr ppenum);
[PreserveSig] int GetDefaultColumn(int dwRes, out int pSort, out int pDisplay);
[PreserveSig] int GetDefaultColumnState(int iColumn, out int pcsFlags);
[PreserveSig] int GetDetailsEx(IntPtr pidl, ref ShellNative.PROPERTYKEY pscid, IntPtr pv);
[PreserveSig] int GetDetailsOf(IntPtr pidl, int iColumn, IntPtr psd);
[PreserveSig] int MapColumnToSCID(int iColumn, ref ShellNative.PROPERTYKEY pscid);
}
[ComImport, Guid("0000010c-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPersist
{
[PreserveSig] int GetClassID(out Guid pClassID);
}
[ComImport, Guid("1079acfc-29bd-11d3-8e0d-00c04f6837d5"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPersistIDList : IPersist
{
[PreserveSig] new int GetClassID(out Guid pClassID);
[PreserveSig] int SetIDList(IntPtr pidl);
[PreserveSig] int GetIDList(out IntPtr ppidl);
}
[ComImport, Guid("000214ea-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPersistFolder : IPersist
{
[PreserveSig] new int GetClassID(out Guid pClassID);
[PreserveSig] int Initialize(IntPtr pidl);
}
[ComImport, Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyStore
{
[PreserveSig] int GetCount(out int cProps);
[PreserveSig] int GetAt(int iProp, out PROPERTYKEY pkey);
[PreserveSig] int GetValue(ref PROPERTYKEY key, IntPtr pv);
[PreserveSig] int SetValue(ref PROPERTYKEY key, IntPtr propvar);
[PreserveSig] int Commit();
}
[ComImport, Guid("bc110b6d-57e8-4148-a9c6-91015ab2f3a5"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyStoreFactory
{
[PreserveSig] int GetPropertyStore(int flags, IntPtr pUnkFactory, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
[PreserveSig] int GetPropertyStoreForKeys(IntPtr rgKeys, int cKeys, int flags, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv);
}
[ComImport, Guid("b3a4b685-b685-4805-99d9-5dead2873236"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IParentAndItem
{
[PreserveSig] int SetParentAndItem(IntPtr pidlParent, IShellFolder psf, IntPtr pidlChild);
[PreserveSig] int GetParentAndItem(IntPtr ppidlParent, IntPtr ppsf, IntPtr ppidlChild);
}
[ComImport, Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItem
{
// undefined as we dont need methods
}
[Guid("947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFileOperation
{
[PreserveSig] int Advise(IntPtr pfops, out int pdwCookie);
[PreserveSig] int Unadvise(int dwCookie);
[PreserveSig] int SetOperationFlags(uint dwOperationFlags);
[PreserveSig] int SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string pszMessage);
[PreserveSig] int SetProgressDialog(IntPtr popd);
[PreserveSig] int SetProperties(IntPtr pproparray);
[PreserveSig] int SetOwnerWindow(IntPtr hwndOwner);
[PreserveSig] int ApplyPropertiesToItem(IShellItem psiItem);
[PreserveSig] int ApplyPropertiesToItems([MarshalAs(UnmanagedType.IUnknown)] object punkItems);
[PreserveSig] int RenameItem(IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, IntPtr pfopsItem);
[PreserveSig] int RenameItems([MarshalAs(UnmanagedType.IUnknown)] object pUnkItems, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
[PreserveSig] int MoveItem(IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, IntPtr pfopsItem);
[PreserveSig] int MoveItems([MarshalAs(UnmanagedType.IUnknown)] object punkItems, IShellItem psiDestinationFolder);
[PreserveSig] int CopyItem(IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszCopyName, IntPtr pfopsItem);
[PreserveSig] int CopyItems([MarshalAs(UnmanagedType.IUnknown)] object punkItems, IShellItem psiDestinationFolder);
[PreserveSig] int DeleteItem(IShellItem psiItem, IntPtr pfopsItem);
[PreserveSig] int DeleteItems([MarshalAs(UnmanagedType.IUnknown)] object punkItems);
[PreserveSig] int NewItem(IShellItem psiDestinationFolder, uint dwFileAttributes, [MarshalAs(UnmanagedType.LPWStr)] string pszName, [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName, IntPtr pfopsItem);
[PreserveSig] int PerformOperations();
[PreserveSig] int GetAnyOperationsAborted(out bool pfAnyOperationsAborted);
}
[ComImport, Guid("3ad05575-8857-4850-9277-11b85bdb8e09")] // CLSID_FileOperation
public class FileOperation { }
[StructLayout(LayoutKind.Sequential)]
public struct PROPERTYKEY : IEquatable<PROPERTYKEY>
{
public Guid fmtid { get; }
public int pid { get; }
public PROPERTYKEY(Guid fmtid, int pid) { this.fmtid = fmtid; this.pid = pid; }
public override string ToString() => fmtid.ToString("B") + " " + pid;
public override int GetHashCode() => fmtid.GetHashCode() ^ pid;
public bool Equals(PROPERTYKEY other) => other.fmtid == fmtid && other.pid == pid;
public override bool Equals(object obj) => obj is PROPERTYKEY other && Equals(other);
public static PROPERTYKEY PKEY_DateModified { get; } = new PROPERTYKEY(new Guid("b725f130-47ef-101a-a5f1-02608c9eebac"), 14);
public static PROPERTYKEY PKEY_FileName { get; } = new PROPERTYKEY(new Guid("41cf5ae0-f75a-4806-bd87-59c7d9248eb9"), 100);
public static PROPERTYKEY PKEY_FindData { get; } = new PROPERTYKEY(new Guid("28636aa6-953d-11d2-b5d6-00c04fd918d0"), 0);
public static PROPERTYKEY PKEY_ItemPathDisplay { get; } = new PROPERTYKEY(new Guid("e3e0584c-b788-4a5a-bb20-7f5a44c9acdd"), 7);
public static PROPERTYKEY PKEY_Size { get; } = new PROPERTYKEY(new Guid("b725f130-47ef-101a-a5f1-02608c9eebac"), 12);
public static PROPERTYKEY PKEY_Volume_IsRoot { get; } = new PROPERTYKEY(new Guid("9b174b35-40ff-11d2-a27e-00c04fc30871"), 10);
}
[StructLayout(LayoutKind.Explicit, Size = 264)]
public struct STRRET_32
{
[FieldOffset(0)]
public STRRET_TYPE uType;
[FieldOffset(4)]
public IntPtr pOleStr;
[FieldOffset(4)]
public uint uOffset;
[FieldOffset(4)]
public IntPtr cStr;
}
[StructLayout(LayoutKind.Explicit, Size = 272)]
public struct STRRET_64
{
[FieldOffset(0)]
public STRRET_TYPE uType;
[FieldOffset(8)]
public IntPtr pOleStr;
[FieldOffset(8)]
public uint uOffset;
[FieldOffset(8)]
public IntPtr cStr;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WIN32_FIND_DATAW
{
public int dwFileAttributes;
public FILETIME ftCreationTime;
public FILETIME ftLastAccessTime;
public FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
public enum STRRET_TYPE
{
STRRET_WSTR = 0,
STRRET_OFFSET = 1,
STRRET_CSTR = 2,
}
[Flags]
public enum SHGDNF
{
SHGDN_NORMAL = 0x0,
SHGDN_INFOLDER = 0x1,
SHGDN_FOREDITING = 0x1000,
SHGDN_FORADDRESSBAR = 0x4000,
SHGDN_FORPARSING = 0x8000,
}
public const int SFGAO_STREAM = 0x00400000;
public const int E_NOTIMPL = unchecked((int)0x80004001);
public const int E_NOINTERFACE = unchecked((int)0x80004002);
}
You have to use the IBindCtx parameter to pass additional data to the parser, in this case your own file metadata so SHParseDisplayName() will not try to access a real file to get the metadata. This is described in the IShellFolder::ParseDisplayName() and SHCreateItemFromParsingName() documentations:
A pointer to a bind context used to pass parameters as inputs and outputs to the parsing function. These passed parameters are often specific to the data source and are documented by the data source owners. For example, the file system data source accepts the name being parsed (as a WIN32_FIND_DATA structure), using the STR_FILE_SYS_BIND_DATA bind context parameter. STR_PARSE_PREFER_FOLDER_BROWSING can be passed to indicate that URLs are parsed using the file system data source when possible. Construct a bind context object using CreateBindCtx and populate the values using IBindCtx::RegisterObjectParam. See Bind Context String Keys for a complete list of these.
And outlined in detail on the MSDN "Old New Thing" blog:
Creating a simple pidl: For the times you care enough to send the very fakeđź•—
I need to turn off my monitor in code. I googled and found some code that should do it, but when I run this, nothing at all happens, and I just get a 0 WinAPI result. What am I doing wrong?
class Program
{
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
const int SC_MONITORPOWER = 0xF170;
const int WM_SYSCOMMAND = 0x0112;
const int MONITOR_OFF = 2;
static void Main(string[] args)
{
Console.WriteLine(
SendMessage(
GetDesktopWindow(),
WM_SYSCOMMAND,
(IntPtr) SC_MONITORPOWER,
(IntPtr) MONITOR_OFF));
Console.WriteLine("Hello World!");
}
}
From Fumbling around in the dark and stumbling across the wrong solution:
the desktop window is a very special window and as a rule should be avoided, since it won't behave like windows created by applications. In particular, the author tried to post a message to the desktop window. This used to work in the historically open world of the window manager, but security and robustness concerns have come to take priority over compatibility.
The real solution is to create your own window and send it the message, anything else is a hack.
If you don't mind hacks, at least try to find a suitable window:
[DllImport("user32.dll", SetLastError = false)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
const int SC_MONITORPOWER = 0xF170;
const int WM_SYSCOMMAND = 0x0112;
const int MONITOR_OFF = 2;
static void Main(string[] args)
{
IntPtr w;
for (; IntPtr.Zero == (w = GetForegroundWindow());) System.Threading.Thread.Sleep(1000);
PostMessage(w, WM_SYSCOMMAND, (IntPtr) SC_MONITORPOWER, (IntPtr) MONITOR_OFF);
}
And because it is a hack there are where times it might not work. The window you are borrowing might be destroyed before you post the message. You might not have the right to send messages to it (UAC). Or it might decide to not pass this message to DefWindowProc.
Another slightly better hack is to create a temporary window:
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
static extern int DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError=true)]
public static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, IntPtr cap, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
const int SC_MONITORPOWER = 0xF170;
const int WM_SYSCOMMAND = 0x0112;
const int MONITOR_OFF = 2;
static void Main(string[] args)
{
IntPtr w = CreateWindowEx(0, "Button", IntPtr.Zero, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (w != IntPtr.Zero)
{
SendMessage(w, WM_SYSCOMMAND, (IntPtr) SC_MONITORPOWER, (IntPtr) MONITOR_OFF);
DestroyWindow(w);
}
}
This is still somewhat of a hack because it never actually pumps messages.
I'm trying to write an application which will creates a new desktop using the Win32 API.
The below code works in Windows 7 but but in Windows 8 the desktop is created with out the desktop icons and with an empty taskbar.
public class Desktop
{
private const int NORMAL_PRIORITY_CLASS = 0x00000020;
private const long DF_ALLOWOTHERACCOUNTHOOK = 0x0001L;
private const uint DELETE = 0x00010000;
private const uint READ_CONTROL = 0x00020000;
private const uint WRITE_DAC = 0x00040000;
private const uint WRITE_OWNER = 0x00080000;
private const uint STANDARD_RIGHTS_REQUIRED = DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER;
private const long DESKTOP_CREATEWINDOW = 0x0002L;
private const long DESKTOP_ENUMERATE = 0x0040L;
private const long DESKTOP_WRITEOBJECTS = 0x0080L;
private const long DESKTOP_SWITCHDESKTOP = 0x0100L;
private const long DESKTOP_CREATEMENU = 0x0004L;
private const long DESKTOP_HOOKCONTROL = 0x0008L;
private const long DESKTOP_READOBJECTS = 0x0001L;
private const long DESKTOP_JOURNALRECORD = 0x0010L;
private const long DESKTOP_JOURNALPLAYBACK = 0x0020L;
private const uint AccessRights = (uint)DESKTOP_JOURNALRECORD |
(uint)DESKTOP_JOURNALPLAYBACK |
(uint)DESKTOP_CREATEWINDOW |
(uint)DESKTOP_ENUMERATE |
(uint)DESKTOP_WRITEOBJECTS |
(uint)DESKTOP_SWITCHDESKTOP |
(uint)DESKTOP_CREATEMENU |
(uint)DESKTOP_HOOKCONTROL |
(uint)DESKTOP_READOBJECTS |
STANDARD_RIGHTS_REQUIRED;
private delegate bool EnumDesktopProc(string lpszDesktop, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll")]
private static extern bool EnumDesktops(IntPtr hwinsta, EnumDesktopProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr CreateDesktop(string desktopName, string device, string deviceMode, int flags, uint accessMask, [In] ref SECURITY_ATTRIBUTES attributes);
[DllImport("user32.dll")]
private static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll")]
private static extern bool SwitchDesktop(IntPtr hDesktop);
[DllImport("kernel32.dll")]
private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation);
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
private static List<string> _desktops = new List<string>();
public static IntPtr Create(string name)
{
var securityAttributes = new SECURITY_ATTRIBUTES();
securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
return CreateDesktop(name, null, null, (int) DF_ALLOWOTHERACCOUNTHOOK, AccessRights, ref securityAttributes);
}
public static IntPtr Open(string name)
{
return OpenDesktop(name, 0, false, AccessRights);
}
public static bool Show(IntPtr desktopPrt)
{
if (desktopPrt == IntPtr.Zero) return false;
return SwitchDesktop(desktopPrt);
}
public static void Prepare(string desktopName)
{
CreateProcess("C:\\Windows\\explorer.exe", desktopName);
}
public static bool Exists(string name)
{
return GetDesktops().Any(s => s == name);
}
private static void CreateProcess(string path, string desktopName)
{
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = desktopName;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
var lpCurrentDirectory = Path.GetDirectoryName(path);
CreateProcess(path, null, IntPtr.Zero, IntPtr.Zero, true, NORMAL_PRIORITY_CLASS, IntPtr.Zero, lpCurrentDirectory, ref si, ref pi);
}
private static string[] GetDesktops()
{
IntPtr windowStation = GetProcessWindowStation();
if (windowStation == IntPtr.Zero) return new string[0];
_desktops.Clear();
bool result = EnumDesktops(windowStation, DesktopEnumProc, IntPtr.Zero);
if (!result) return new string[0];
return _desktops.ToArray();
}
private static bool DesktopEnumProc(string lpszDesktop, IntPtr lParam)
{
_desktops.Add(lpszDesktop);
return true;
}
}
Usage:
var desktopName = "TestDesktop";
var desktopPrt = Desktop.Create(desktopName);
Desktop.Show(desktopPrt);
Desktop.Prepare(desktopName);
The Sysinternals Desktops app (http://technet.microsoft.com/en-us/sysinternals/cc817881.aspx) uses the same API and it works in Windows 8 so there must be something that I am missing.
Does anyone have any pointers for me? Or does anyone know of any where I can find examples of how to use this API? MSDN for the API calls gives a lot of information but no many examples.
Thanks ;D
EDIT: Sry, I realised that you run it in your code snippet, please ignore my comment.
How about explicitly running explorer.exe for each desktop you create? That one handles icons, taskbar, etc ...
When you run SysInternals Desktops app you can see that it actually does that.
now i have code that detect session Id of users that connect to Windows with remote desktop (mstsc.exe) to windows XP but i want to know their Usernames.
[DllImport("wtsapi32.dll")]
static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);
[DllImport("wtsapi32.dll")]
static extern void WTSCloseServer(IntPtr hServer);
[DllImport("wtsapi32.dll")]
static extern Int32 WTSEnumerateSessions(
IntPtr hServer,
[MarshalAs(UnmanagedType.U4)] Int32 Reserved,
[MarshalAs(UnmanagedType.U4)] Int32 Version,
ref IntPtr ppSessionInfo,
[MarshalAs(UnmanagedType.U4)] ref Int32 pCount);
[DllImport("wtsapi32.dll")]
static extern void WTSFreeMemory(IntPtr pMemory);
[DllImport("Wtsapi32.dll")]
static extern bool WTSQuerySessionInformation(
System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public Int32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public String pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}
public enum WTS_INFO_CLASS
{
WTSInitialProgram,
WTSApplicationName,
WTSWorkingDirectory,
WTSOEMId,
WTSSessionId,
WTSUserName,
WTSWinStationName,
WTSDomainName,
WTSConnectState,
WTSClientBuildNumber,
WTSClientName,
WTSClientDirectory,
WTSClientProductId,
WTSClientHardwareId,
WTSClientAddress,
WTSClientDisplay,
WTSClientProtocolType
}
public enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
public static IntPtr OpenServer(String Name)
{
IntPtr server = WTSOpenServer(Name);
return server;
}
public static void CloseServer(IntPtr ServerHandle)
{
WTSCloseServer(ServerHandle);
}
public static DataTable ListUsers(String ServerName)
{
DataTable DtResult = new DataTable();
DtResult.Columns.Add("Domain");
DtResult.Columns.Add("User");
IntPtr serverHandle = IntPtr.Zero;
List<String> resultList = new List<string>();
serverHandle = OpenServer(ServerName);
try
{
IntPtr SessionInfoPtr = IntPtr.Zero;
IntPtr userPtr = IntPtr.Zero;
IntPtr domainPtr = IntPtr.Zero;
Int32 sessionCount = 0;
Int32 retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref SessionInfoPtr, ref sessionCount);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int32 currentSession = (int)SessionInfoPtr;
uint bytes = 0;
if (retVal != 0)
{
for (int i = 0; i < sessionCount; i++)
{
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)currentSession, typeof(WTS_SESSION_INFO));
currentSession += dataSize;
WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSUserName, out userPtr, out bytes);
WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSDomainName, out domainPtr, out bytes);
DtResult.Rows.Add(Marshal.PtrToStringAnsi(domainPtr) ,Marshal.PtrToStringAnsi(userPtr));
WTSFreeMemory(userPtr);
WTSFreeMemory(domainPtr);
}
WTSFreeMemory(SessionInfoPtr);
}
}
finally
{
CloseServer(serverHandle);
}
return DtResult;
}
You can also use the Cassia library if you'd like to dealing with the P/Invokes. See this answer for a code sample.
I'm trying to find out the 'correct' windows API for finding out the localized name of 'special' folders, specifically the Recycle Bin. I want to be able to prompt the user with a suitably localized dialog box asking them if they want to send files to the recycle bin or delete them directly.
I've found lots on the internet (and on Stackoverflow) about how to do the actual deletion, and it seems simple enough, I just really want to be able to have the text localized.
Read this article for code samples and usage:
http://www.codeproject.com/KB/winsdk/SpecialFolders.aspx
Also there is an article on MSDN that helps you Identify the Location of Special Folders with API Calls
I actually didn't find the CodeProject article terribly helpful, so I thought I'd answer this question with the actual code that I used to retrieve the localized name of the recycle bin.
This sample also tries to behave correctly with regard to freeing resources. Any comments are welcome, especially if you spot an error with my resource management!
public static string GetLocalizedRecycleBinName()
{
IntPtr relative_pidl, parent_ptr, absolute_pidl;
PInvoke.SHGetFolderLocation(IntPtr.Zero, PInvoke.CSIDL.BitBucket,
IntPtr.Zero, 0, out absolute_pidl);
try
{
PInvoke.SHBindToParent(absolute_pidl,
ref PInvoke.Guids.IID_IShellFolder,
out parent_ptr, out relative_pidl);
PInvoke.IShellFolder shell_folder =
Marshal.GetObjectForIUnknown(parent_ptr)
as PInvoke.IShellFolder;
// Release() for this object is called at finalization
if (shell_folder == null)
return Strings.RecycleBin;
PInvoke.STRRET strret = new PInvoke.STRRET();
StringBuilder sb = new StringBuilder(260);
shell_folder.GetDisplayNameOf(relative_pidl, PInvoke.SHGNO.Normal,
out strret);
PInvoke.StrRetToBuf(ref strret, relative_pidl, sb, 260);
string name = sb.ToString();
return String.IsNullOrEmpty(name) ? Strings.RecycleBin : name;
}
finally { PInvoke.ILFree(absolute_pidl); }
}
static class PInvoke
{
[DllImport("shell32.dll")]
public static extern int SHGetFolderLocation(IntPtr hwndOwner,
CSIDL nFolder, IntPtr hToken, uint dwReserved, out IntPtr ppidl);
[DllImport("shell32.dll")]
public static extern int SHBindToParent(IntPtr lpifq, [In] ref Guid riid,
out IntPtr ppv, out IntPtr pidlLast);
[DllImport("shlwapi.dll")]
public static extern Int32 StrRetToBuf(ref STRRET pstr, IntPtr pidl,
StringBuilder pszBuf, uint cchBuf);
[DllImport("shell32.dll")]
public static extern void ILFree([In] IntPtr pidl);
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E6-0000-0000-C000-000000000046")]
public interface IShellFolder
{
[PreserveSig]
Int32 CompareIDs(Int32 lParam, IntPtr pidl1, IntPtr pidl2);
void ParseDisplayName(IntPtr hwnd, IntPtr pbc, String pszDisplayName,
UInt32 pchEaten, out IntPtr ppidl, UInt32 pdwAttributes);
void EnumObjects(IntPtr hwnd, int 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);
void CreateViewObject(IntPtr hwndOwner, [In] ref Guid riid,
out IntPtr ppv);
void GetAttributesOf(UInt32 cidl,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
IntPtr[] apidl, ref uint 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, SHGNO uFlags, out STRRET pName);
void SetNameOf(IntPtr hwnd, IntPtr pidl, string pszName,
int uFlags, out IntPtr ppidlOut);
}
public enum CSIDL
{
BitBucket = 0x000a,
}
public enum SHGNO
{
Normal = 0x0000, ForParsing = 0x8000,
}
[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;
}
public class Guids
{
public static Guid IID_IShellFolder =
new Guid("{000214E6-0000-0000-C000-000000000046}");
}
}