How to Display a File's Properties Dialog from C#

How do I display a file's Properties dialog from C#?

Solution is:

using System.Runtime.InteropServices;

[DllImport("shell32.dll", CharSet = CharSet.Auto)]
static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SHELLEXECUTEINFO
{
public int cbSize;
public uint fMask;
public IntPtr hwnd;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpVerb;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpParameters;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpDirectory;
public int nShow;
public IntPtr hInstApp;
public IntPtr lpIDList;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpClass;
public IntPtr hkeyClass;
public uint dwHotKey;
public IntPtr hIcon;
public IntPtr hProcess;
}

private const int SW_SHOW = 5;
private const uint SEE_MASK_INVOKEIDLIST = 12;
public static bool ShowFileProperties(string Filename)
{
SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
info.lpVerb = "properties";
info.lpFile = Filename;
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
return ShellExecuteEx(ref info);
}

// button click
private void button1_Click(object sender, EventArgs e)
{
string path = @"C:\Users\test\Documents\test.text";
ShowFileProperties(path);
}

How to display file properties dialog security tab from c#

Add info.lpParameters = "Security"; to show Security tab.

Or info.lpParameters = "Details"; to show Details tab.

Now ShowFileProperties method is:

    public static bool ShowFileProperties(string Filename)
{
SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
info.lpVerb = "properties";
info.lpFile = Filename;
info.lpParameters = "Security";
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
return ShellExecuteEx(ref info);
}

Reference: To show the properties page of a file and navigate to a tab

show multiple file properties in c#

I found a method similar to SHELLEXECUTEINFO on MSDN, which I find interesting. According to the article...

SHMultiFileProperties function

Displays a merged property sheet for a set of files. Property values common to all the files are shown while those that differ display the string (multiple values).

https://msdn.microsoft.com/en-us/library/windows/desktop/bb762230(v=vs.85).aspx

I am not sure how to convert this into usable C# code, but I will update my answer yet again when I do.

Solution

I found a solution! :D

public class Properties
{
#region Import Methods

[DllImport("shell32.dll", SetLastError = true)]
static extern int SHMultiFileProperties(IDataObject pdtobj, int flags);

[DllImport("shell32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr ILCreateFromPath(string path);

[DllImport("shell32.dll", CharSet = CharSet.None)]
public static extern void ILFree(IntPtr pidl);

[DllImport("shell32.dll", CharSet = CharSet.None)]
public static extern int ILGetSize(IntPtr pidl);

#endregion

#region Static Methods

#region Private

private static MemoryStream CreateShellIDList(StringCollection filenames)
{
// first convert all files into pidls list
int pos = 0;
byte[][] pidls = new byte[filenames.Count][];
foreach (var filename in filenames)
{
// Get pidl based on name
IntPtr pidl = ILCreateFromPath(filename);
int pidlSize = ILGetSize(pidl);
// Copy over to our managed array
pidls[pos] = new byte[pidlSize];
Marshal.Copy(pidl, pidls[pos++], 0, pidlSize);
ILFree(pidl);
}

// Determine where in CIDL we will start pumping PIDLs
int pidlOffset = 4 * (filenames.Count + 2);
// Start the CIDL stream
var memStream = new MemoryStream();
var sw = new BinaryWriter(memStream);
// Initialize CIDL witha count of files
sw.Write(filenames.Count);
// Calcualte and write relative offsets of every pidl starting with root
sw.Write(pidlOffset);
pidlOffset += 4; // root is 4 bytes
foreach (var pidl in pidls)
{
sw.Write(pidlOffset);
pidlOffset += pidl.Length;
}

// Write the root pidl (0) followed by all pidls
sw.Write(0);
foreach (var pidl in pidls) sw.Write(pidl);
// stream now contains the CIDL
return memStream;
}

#endregion

#region Public

public static int Show(IEnumerable<string> Filenames)
{
StringCollection Files = new StringCollection();
foreach (string s in Filenames) Files.Add(s);
var data = new DataObject();
data.SetFileDropList(Files);
data.SetData("Preferred DropEffect", new MemoryStream(new byte[] { 5, 0, 0, 0 }), true);
data.SetData("Shell IDList Array", Properties.CreateShellIDList(Files), true);
return SHMultiFileProperties(data, 0);
}

public static int Show(params string[] Filenames)
{
return Properties.Show(Filenames as IEnumerable<string>);
}

#endregion

#endregion
}

This is tested and works on Windows 10. I actually came up with this based on two other SO articles because neither's code by itself worked.

Sources:

P/Invoke for shell32.dll's SHMultiFileProperties

SHMultiFileProperties doens't work on XP

Display file properties dialog for files in different directories

IShellFolder::GetUIObjectOf() only works with single-level PIDLs that are relative to the IShellFolder being queried. This is clearly stated in the GetUIObjectOf() documentation:

The address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies a file object or subfolder relative to the parent folder. Each item identifier list must contain exactly one SHITEMID structure followed by a terminating zero.

You are converting the 2 absolute PIDLs into relative PIDLs of their respective folders, but then you are using the IShellFolder of the Windows\System32 folder to retrieve the IContextMenu for both files. That will not work for the relative PIDL belonging to the Windows\ folder, since the IShellFolder of the Windows\System32 folder only knows about files in the Windows\System32 folder.

Various online examples show the IContextMenu being queried from the IShellFolder of the desktop when multiple files are involved. That approach only works when the files are in the same parent folder (such as demonstrated in Raymond Chen's example). Things get more complicated when the files are in different folders.

You also have a few memory leaks in your code.

The correct way to handle this situation is to use the SHMultiFileProperties() function:

Displays a merged property sheet for a set of files. Property values common to all the files are shown while those that differ display the string (multiple values).

Use the IShellFolder of the desktop to get an IDataObject for the absolute PIDLs (this is one time where GetUIObjectOf() is allowed to violate the "Each item identifier list must contain exactly one SHITEMID structure" rule) and then pass that to SHMultiFileProperties(). For example:

int main()
{
CoInitialize(NULL);

LPOLESTR pszFile = OLESTR("c:\\Windows\\notepad.exe");
LPOLESTR pszFile2 = OLESTR("c:\\Windows\\System32\\notepad.exe");
LPITEMIDLIST pidl;
LPITEMIDLIST pidl2;
HRESULT hr;
IShellFolder* pDesktop;
IDataObject *pDataObject;

hr = SHGetDesktopFolder(&pDesktop);
if (FAILED(hr))
{
CoUninitialize();
return 0;
}

hr = pDesktop->ParseDisplayName(HWND_DESKTOP, NULL, pszFile, NULL, &pidl, NULL);
if (FAILED(hr)) {
pDesktop->Release();
CoUninitialize();
return 0;
}

hr = pDesktop->ParseDisplayName(HWND_DESKTOP, NULL, pszFile2, NULL, &pidl2, NULL);
if (FAILED(hr)) {
SHFree(pidl);
pDesktop->Release();
CoUninitialize();
return 0;
}

LPCITEMIDLIST list[] = {pidl, pidl2};
hr = pDesktop->GetUIObjectOf(HWND_DESKTOP, 2, (LPCITEMIDLIST *)list, IID_IDataObject, NULL, (void **)&pDataObject);
// alternatively, you can also use SHCreateDataObject() or CIDLData_CreateFromIDArray() to create the IDataObject
pDesktop->Release();
SHFree(pidl);
SHFree(pidl2);

if (SUCCEEDED(hr)) {
hr = SHMultiFileProperties(pDataObject, 0);
pDataObject->Release();

if (SUCCEEDED(hr)) {
MessageBox(0, _T("Dummy message box"), 0, 0);
Sleep(10000); // Give the system time to show the dialog before exiting
}
}

CoUninitialize();
return 0;
}

How to open the Property dialog of selected objects in Management Console?

It's relatively simple.

  • Add a COM Reference to Microsoft Management Console 2.0.
  • Add the using MMC20 directive.
  • Create a new MMC20.Application object
  • Use the Application.Load() method to load a Snap-In (services.msc here)
  • The ActiveView of the Application Document contains the list of items: ListItems Property
  • Select a Node by name or Index and call the DisplaySelectionPropertySheet() method to show its Property pane

For example:

Note: setting mmcApp.UserControl = 1; leaves the Console open, otherwise it would close.

using MMC20; 
// [...]

MMC20.Application mmcApp = new MMC20.Application();
mmcApp.UserControl = 1;
mmcApp.Load("services.msc");

var doc = mmcApp.Document;
var view = doc.ActiveView;
var node = view.ListItems.OfType<Node>().FirstOrDefault(n => n.Name == "Base Filtering Engine");

if (node != null) {
view.Select(node);
view.DisplaySelectionPropertySheet();
}

To enumerate the ListItems, use a standard loop or an extension method as shown above:

var nodes = view.ListItems;

foreach (MMC20.Node node in nodes) {
Console.WriteLine(node.Name);
}

How do I get details from File Properties?

Well, this is not completely impossible. You need to pinvoke SHCreateItemFromParsingName() to obtain an IShellItem2 interface pointer so you can call its GetProperty() method. To get this going in C# is however quite brutal. The shell interfaces are in the domain of native C++, the only language in which you can get the COM declarations and guids you need to bring this to a good end. After 4 major releases of .NET, we're still no closer to make shell programming in a managed language any easier. Good keywords to google for though, somebody somewhere made this work.



Related Topics



Leave a reply



Submit