Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one invoke the Windows Permissions dialog programmatically?

I'm trying to write a certificate manager, and I want to manage the permissions over the certificate file. I'd prefer not reinventing the wheel of the Windows permissions dialog, so ideally there would be some kind of shell command that I could pass the path of the item whose permissions are being managed. Then, I could just invoke it and let the shell take care of updating the permissions.

I've seen some mention here and there of a shell function, SHObjectProperties, but nothing clear on how to use it. Any help would be appreciated.

like image 921
Chris B. Behrens Avatar asked Jan 19 '15 23:01

Chris B. Behrens


2 Answers

You can display the Windows file permissions dialog using ShellExecuteEx (using the "properties" verb and the "Security" parameter).

This will display a dialog like the following within your process, and the file permission viewing and editing will be fully functional just as if you had got this dialog through the Windows explorer shell:

enter image description here

Here is an example with Windows Forms where a file is selected and the Security properties of that file then displayed. I have used the P/Invoke code for ShellExecuteEx from this Stackoverflow answer.

using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FileSecurityProperties
{
    public partial class FileSelectorForm : Form
    {
        private static bool ShowFileSecurityProperties(string Filename, IntPtr parentHandle)
        {
            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;
            info.hwnd = parentHandle;
            info.lpParameters = "Security"; // Opens the file properties on the Security tab
            return ShellExecuteEx(ref info);
        }

        private void fileSelectButton_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                ShowFileSecurityProperties(
                    openFileDialog.FileName,
                    this.Handle); // Pass parent window handle for properties dialog
            }
        }

        #region P/Invoke code for ShellExecuteEx from https://stackoverflow.com/a/1936957/4486839
        [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;
        #endregion

        #region Irrelevant Windows forms code
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
            this.fileSelectButton = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // openFileDialog1
            // 
            this.openFileDialog.FileName = "";
            // 
            // fileSelectButton
            // 
            this.fileSelectButton.Location = new System.Drawing.Point(52, 49);
            this.fileSelectButton.Name = "fileSelectButton";
            this.fileSelectButton.Size = new System.Drawing.Size(131, 37);
            this.fileSelectButton.TabIndex = 0;
            this.fileSelectButton.Text = "Select file ...";
            this.fileSelectButton.UseVisualStyleBackColor = true;
            this.fileSelectButton.Click += new System.EventHandler(this.fileSelectButton_Click);
            // 
            // FileSelectorForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(248, 162);
            this.Controls.Add(this.fileSelectButton);
            this.Name = "FileSelectorForm";
            this.Text = "File Selector";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.OpenFileDialog openFileDialog;
        private System.Windows.Forms.Button fileSelectButton;

        public FileSelectorForm()
        {
            InitializeComponent();
        }

        #endregion
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new FileSelectorForm());
        }
    }
}

If you were hoping to get the file permissions dialog on its own, rather than as a tab in the general file properties dialog, that is possible using aclui.dll, e.g. using the EditSecurity function, but this will NOT then give you your other requirement of having the file permissions handling taken care of for you, because you have to provide an interface which does the getting and setting of security properties if you go down that route, and it looks like a lot of coding.

like image 170
softwariness Avatar answered Sep 17 '22 10:09

softwariness


Here is a utilily class that allows you to have only the security property sheet (not all sheets the shell displays).

enter image description here

You can call it like this in a console app:

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if the dialog looks old fashioned (for example if used in a console app),
        // then add an app.manifest and uncomment the dependency section about Microsoft.Windows.Common-Controls
        PermissionDialog.Show(IntPtr.Zero, @"d:\temp\killroy_was_here.png");
    }
}

or like this in a winform app

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        PermissionDialog.Show(IntPtr.Zero, @"d:\temp\killroy_was_here.png");
    }
}

And this is the main class. It's basically using the same thing as the shell, but in its own property sheet.

public static class PermissionDialog
{
    public static bool Show(IntPtr hwndParent, string path)
    {
        if (path == null)
            throw new ArgumentNullException("path");

        SafePidlHandle folderPidl;
        int hr;
        hr = SHILCreateFromPath(Path.GetDirectoryName(path), out folderPidl, IntPtr.Zero);
        if (hr != 0)
            throw new Win32Exception(hr);

        SafePidlHandle filePidl;
        hr = SHILCreateFromPath(path, out filePidl, IntPtr.Zero);
        if (hr != 0)
            throw new Win32Exception(hr);

        IntPtr file = ILFindLastID(filePidl);

        System.Runtime.InteropServices.ComTypes.IDataObject ido;
        hr = SHCreateDataObject(folderPidl, 1, new IntPtr[] { file }, null, typeof(System.Runtime.InteropServices.ComTypes.IDataObject).GUID, out ido);
        if (hr != 0)
            throw new Win32Exception(hr);

        // if you get a 'no such interface' error here, make sure the running thread is STA
        IShellExtInit sei = (IShellExtInit)new SecPropSheetExt();
        sei.Initialize(IntPtr.Zero, ido, IntPtr.Zero);

        IShellPropSheetExt spse = (IShellPropSheetExt)sei;
        IntPtr securityPage = IntPtr.Zero;
        spse.AddPages((p, lp) =>
        {
            securityPage = p;
            return true;
        }, IntPtr.Zero);

        PROPSHEETHEADER psh = new PROPSHEETHEADER();
        psh.dwSize = Marshal.SizeOf(psh);
        psh.hwndParent = hwndParent;
        psh.nPages = 1;
        psh.phpage = Marshal.AllocHGlobal(IntPtr.Size);
        Marshal.WriteIntPtr(psh.phpage, securityPage);

        // TODO: adjust title & icon here, also check out the available flags
        psh.pszCaption = "Permissions for '" + path + "'";

        IntPtr res;
        try
        {
            res = PropertySheet(ref psh);
        }
        finally
        {
            Marshal.FreeHGlobal(psh.phpage);
        }
        return res == IntPtr.Zero;
    }

    private class SafePidlHandle : SafeHandle
    {
        public SafePidlHandle()
            : base(IntPtr.Zero, true)
        {
        }

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

        protected override bool ReleaseHandle()
        {
            if (IsInvalid)
                return false;

            Marshal.FreeCoTaskMem(handle);
            return true;
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct PROPSHEETHEADER
    {
        public int dwSize;
        public int dwFlags;
        public IntPtr hwndParent;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public string pszCaption;
        public int nPages;
        public IntPtr nStartPage;
        public IntPtr phpage;
        public IntPtr pfnCallback;
    }

    [DllImport("shell32.dll")]
    private static extern IntPtr ILFindLastID(SafePidlHandle pidl);

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    private static extern int SHILCreateFromPath(string pszPath, out SafePidlHandle ppidl, IntPtr rgflnOut);

    [DllImport("shell32.dll")]
    private static extern int SHCreateDataObject(SafePidlHandle pidlFolder, int cidl, IntPtr[] apidl, System.Runtime.InteropServices.ComTypes.IDataObject pdtInner, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out System.Runtime.InteropServices.ComTypes.IDataObject ppv);

    [DllImport("comctl32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr PropertySheet(ref PROPSHEETHEADER lppsph);

    private delegate bool AddPropSheetPage(IntPtr page, IntPtr lParam);

    [ComImport]
    [Guid("1f2e5c40-9550-11ce-99d2-00aa006e086c")] // this GUID points to the property sheet handler for permissions
    private class SecPropSheetExt
    {
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("000214E8-0000-0000-C000-000000000046")]
    private interface IShellExtInit
    {
        void Initialize(IntPtr pidlFolder, System.Runtime.InteropServices.ComTypes.IDataObject pdtobj, IntPtr hkeyProgID);
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("000214E9-0000-0000-C000-000000000046")]
    private interface IShellPropSheetExt
    {
        void AddPages([MarshalAs(UnmanagedType.FunctionPtr)] AddPropSheetPage pfnAddPage, IntPtr lParam);
        void ReplacePage(); // not fully defined, we don't use it 
    }
}
like image 27
Simon Mourier Avatar answered Sep 18 '22 10:09

Simon Mourier