Search code examples
.netvb.netwmiaccess-controlsystem.management

Add user to remote windows share using WMI


I have a share on the root of C:\ on a remote computer IMPC-1111, and I'm trying to add a user /w access masks:

FullControl = 2032127
Change = 1245631
[ReadOnly] = 1179817

I've figured out how to read them....but cannot figure out how to write/change them. This is the code I use to read them.

Private Function GetSharedFolderAccessRule() As DataTable

    Dim DT As DataTable = New DataTable()

    Try

        DT.Columns.Add("ShareName")
        DT.Columns.Add("Caption")
        DT.Columns.Add("Path")
        DT.Columns.Add("Domain")
        DT.Columns.Add("User")
        DT.Columns.Add("AccessMask")
        DT.Columns.Add("AceType")

        Dim Con As ConnectionOptions = New ConnectionOptions
        Con.Username = "Username"
        Con.Password = "Password"

        'Dim Scope As ManagementScope = New ManagementScope("\\.\root\cimv2", Con)
        Dim Scope As ManagementScope = New ManagementScope("\\IMPC-1111\root\cimv2", Con)
        Scope.Connect()

        Dim Query As ObjectQuery = New ObjectQuery("SELECT * FROM Win32_LogicalShareSecuritySetting")
        Dim Searcher As ManagementObjectSearcher = New ManagementObjectSearcher(Scope, Query)
        Dim QueryCollection As ManagementObjectCollection = Searcher.[Get]()

        For Each SharedFolder As ManagementObject In QueryCollection

            If True Then
                Dim ShareName As String = CType(SharedFolder("Name"), String)
                Dim Caption As String = CType(SharedFolder("Caption"), String)
                Dim LocalPath As String = String.Empty
                Dim Win32Share As ManagementObjectSearcher = New ManagementObjectSearcher("SELECT Path FROM Win32_share WHERE Name = '" & ShareName & "'")

                For Each ShareData As ManagementObject In Win32Share.[Get]()
                    LocalPath = CType(ShareData("Path"), String)
                Next

                Dim Method As ManagementBaseObject = SharedFolder.InvokeMethod("GetSecurityDescriptor", Nothing, New InvokeMethodOptions())
                Dim Descriptor As ManagementBaseObject = CType(Method("Descriptor"), ManagementBaseObject)
                Dim DACL As ManagementBaseObject() = CType(Descriptor("DACL"), ManagementBaseObject())

                For Each ACE As ManagementBaseObject In DACL
                    Dim Trustee As ManagementBaseObject = CType(ACE("Trustee"), ManagementBaseObject)
                    Dim Row As DataRow = DT.NewRow()
                    Row("ShareName") = ShareName
                    Row("Caption") = Caption
                    Row("Path") = LocalPath
                    Row("Domain") = CType(Trustee("Domain"), String)
                    Row("User") = CType(Trustee("Name"), String)
                    Row("AccessMask") = CType(ACE("AccessMask"), UInt32)
                    Row("AceType") = CType(ACE("AceType"), UInt32)
                    DT.Rows.Add(Row)
                    DT.AcceptChanges()
                Next
            End If
        Next

    Catch ex As Exception
        MessageBox.Show(ex.StackTrace, ex.Message)
    End Try

    Return DT

End Function

Can anyone point me in the right direction? Is this even possible to write using WMI?

Thanks!


Solution

  • You can use the Win32_LogicalShareSecuritySetting.SetSecurityDescriptor() method to modify the share permissions. Modifying the existing permissions works as follows...

    1. Get the desired Win32_LogicalShareSecuritySetting instance.
    2. Call GetSecurityDescriptor() to get the share's security descriptor as a Win32_SecurityDescriptor instance.
    3. Get the security descriptor's discretionary access control list through its DACL property.
    4. Create a new access control entry.
      1. Get a reference to the Win32_ACE class.
      2. Create an instance of the Win32_ACE class.
      3. Set the AccessMask, AceFlags, AceType, and Trustee properties of the access control entry.
        1. Get a reference to the Win32_Trustee class.
        2. Create an instance of the Win32_Trustee class.
        3. Specify the principal for the entry by setting the SIDString or Name (and Domain) property of the trustee.
          • Querying instances of the Win32_Account class or its derivatives would be one way to get these values.
    5. Create a new access control list combining the existing entries and the new entry.
    6. Set the DACL property of the security descriptor to the new access control list.
    7. Call SetSecurityDescriptor() to save the share's security descriptor.

    The following class contains methods to get the properties of a share and, in particular, add a new access control entry via the AddAccessControlEntry() method...

    Imports System.Management
    Imports System.Security.AccessControl
    
    Friend Class ShareSecurity
        Private ReadOnly Property ShareName As String
    
        Private ReadOnly Property GetOptions As ObjectGetOptions
    
        Private ReadOnly Property Scope As ManagementScope
    
        Public Sub New(host As String, shareName As String, username As String, password As String)
            Me.ShareName = shareName
            GetOptions = New ObjectGetOptions()
            Scope = New ManagementScope(
                New ManagementPath() With {
                    .NamespacePath = "root\cimv2",
                    .Server = host
                },
                New ConnectionOptions() With {
     _ ' ***** For demonstration purposes only! *****
     _ 'TODO: Find a secure way to store the remote password
                    .Password = password,
                    .Username = username
                }
            )
        End Sub
    
        Public Function GetShareProperties() As IDictionary(Of String, Object)
            Dim sharePath As New ManagementPath($"Win32_Share.Name=""{ShareName}""")
    
            Using share As New ManagementObject(Scope, sharePath, GetOptions)
                Return GetPropertyDictionary(share)
            End Using
        End Function
    
        Public Function GetShareSecurityDescriptorProperties() As IDictionary(Of String, Object)
            Using shareSecurity As ManagementObject = GetShareSecurity()
                Using securityDescriptor As ManagementBaseObject = GetShareSecurityDescriptor(shareSecurity)
                    Return GetPropertyDictionary(securityDescriptor)
                End Using
            End Using
        End Function
    
        Public Sub AddAccessControlEntry(entryType As AceType, entryRights As FileSystemRights, securityIdentifier As String)
            AddAccessControlEntry(
                entryType,
                entryRights,
                Sub(trustee)
                    trustee("SIDString") = securityIdentifier
                End Sub
            )
        End Sub
    
        Public Sub AddAccessControlEntry(entryType As AceType, entryRights As FileSystemRights, domain As String, account As String)
            AddAccessControlEntry(
                entryType,
                entryRights,
                Sub(trustee)
                    trustee("Domain") = domain
                    trustee("Name") = account
                End Sub
            )
        End Sub
    
        ''' <param name="trusteeInitializer">Initializes the Win32_Trustee instance for the access control entry to be added.</param>
        Public Sub AddAccessControlEntry(entryType As AceType, entryRights As FileSystemRights, trusteeInitializer As Action(Of ManagementObject))
            Using shareSecurity As ManagementObject = GetShareSecurity()
                Using securityDescriptor As ManagementBaseObject = GetShareSecurityDescriptor(shareSecurity)
                    Dim accessControlEntries As ManagementBaseObject() = DirectCast(securityDescriptor("DACL"), ManagementBaseObject())
    
                    ' The class must not be created in the remote scope otherwise CreateInstance()
                    ' throws "System.UnauthorizedAccessException: 'Access is denied'."
                    Using accessControlEntryClass As New ManagementClass("Win32_ACE")
                        Using accessControlEntry As ManagementObject = accessControlEntryClass.CreateInstance()
                            accessControlEntry("AccessMask") = CUInt(entryRights)
                            accessControlEntry("AceFlags") = CUInt(AceFlags.None)
                            accessControlEntry("AceType") = CUInt(entryType)
    
                            ' The class must not be created in the remote scope otherwise CreateInstance()
                            ' throws "System.UnauthorizedAccessException: 'Access is denied'."
                            Using trusteeClass As New ManagementClass("Win32_Trustee")
                                Using trustee As ManagementObject = trusteeClass.CreateInstance()
                                    trusteeInitializer.Invoke(trustee)
                                    accessControlEntry("Trustee") = trustee
    
                                    ' Create a new access control list including the new access control
                                    ' entry, sorted with Deny entries first (true sorts after false)
                                    ' https://learn.microsoft.com/windows/win32/secauthz/order-of-aces-in-a-dacl
                                    securityDescriptor("DACL") = accessControlEntries _
                                        .Append(accessControlEntry) _
                                        .OrderByDescending(Function(entry) CType(entry("AceType"), AceType)) _
                                        .ToArray()
    
                                    SetShareSecurityDescriptor(shareSecurity, securityDescriptor)
                                End Using
                            End Using
                        End Using
                    End Using
                End Using
            End Using
        End Sub
    
        Private Function GetShareSecurity() As ManagementObject
            Dim shareSecurityPath As New ManagementPath($"Win32_LogicalShareSecuritySetting.Name=""{ShareName}""")
    
            Return New ManagementObject(Scope, shareSecurityPath, GetOptions)
        End Function
    
        Private Function GetShareSecurityDescriptor(shareSecurity As ManagementObject) As ManagementBaseObject
            ' Create an array to store the output parameter
            Dim invokeParameters(0) As ManagementBaseObject
            Dim invokeResult As UInteger = shareSecurity.InvokeMethod("GetSecurityDescriptor", invokeParameters)
    
            If invokeResult = 0 Then
                Return invokeParameters(0)
            Else
                'TODO: Handle failure of GetSecurityDescriptor()...
                Return Nothing
            End If
        End Function
    
        Private Sub SetShareSecurityDescriptor(shareSecurity As ManagementObject, securityDescriptor As ManagementBaseObject)
            ' Create an array to store the input parameter
            Dim invokeParameters() As ManagementBaseObject = {securityDescriptor}
            Dim invokeResult As UInteger = shareSecurity.InvokeMethod("SetSecurityDescriptor", invokeParameters)
    
            If invokeResult <> 0 Then
                'TODO: Handle failure of SetSecurityDescriptor()...
            End If
        End Sub
    
        Private Shared Function GetPropertyDictionary(obj As ManagementBaseObject) As IDictionary(Of String, Object)
            Return obj.Properties _
                .Cast(Of PropertyData)() _
                .ToDictionary(
                    Function([property]) [property].Name,
                    Function([property])
                        ' Recursively create dictionaries in place of management objects
                        Dim baseObjectArray As ManagementBaseObject() = TryCast([property].Value, ManagementBaseObject())
    
                        If baseObjectArray IsNot Nothing Then
                            Return baseObjectArray.Select(AddressOf GetPropertyDictionary).ToArray()
                        Else
                            Dim baseObject As ManagementBaseObject = TryCast([property].Value, ManagementBaseObject)
    
                            If baseObject IsNot Nothing Then
                                Return GetPropertyDictionary(baseObject)
                            Else
                                Return [property].Value
                            End If
                        End If
                    End Function
                )
        End Function
    End Class
    

    The following program uses the above ShareSecurity class to display the properties of a remote share and, optionally, add a new access control entry to it...

    Imports System.Security.AccessControl
    
    Public Class Program
        Public Shared Sub Main(args As String())
            If args Is Nothing OrElse args.Length = 0 Then
                DisplayUsage()
            ElseIf args.Length < 4 Then
                DisplayError("Too few arguments.")
            Else
                Dim host As String = args(0)
                Dim shareName As String = args(1)
                Dim username As String = args(2)
                ' ***** For demonstration purposes only! *****
                'TODO: Find a secure way to store the remote password
                Dim password As String = args(3)
                Dim shareSecurity As New ShareSecurity(host, shareName, username, password)
    
                DisplayShareProperties(shareSecurity)
                DisplaySecurityDescriptor(shareSecurity)
    
                If args.Length > 4 Then
                    If args.Length <> 7 AndAlso args.Length <> 8 Then
                        DisplayError("Argument count mismatch.")
                    Else
                        Dim entryType As AceType
    
                        If Not [Enum].TryParse(args(4), True, entryType) Then
                            DisplayError($"Invalid <type> value ""{args(4)}"".")
                        Else
                            Dim entryRights As FileSystemRights
    
                            If Not [Enum].TryParse(args(5), True, entryRights) Then
                                DisplayError($"Invalid <rights> value ""{args(5)}"".")
                            Else
                                DisplayTextWithSeparator("New access control entry")
                                Console.WriteLine($"   Type: {entryType}")
                                Console.WriteLine($" Rights: {entryRights}")
    
                                If args.Length = 7 Then
                                    Dim securityIdentifier As String = args(6)
    
                                    Console.WriteLine($"    SID: {securityIdentifier}")
                                    shareSecurity.AddAccessControlEntry(entryType, entryRights, securityIdentifier)
                                Else ' args.Length = 8
                                    Dim domain As String = args(6)
                                    Dim account As String = args(7)
    
                                    Console.WriteLine($" Domain: {domain}")
                                    Console.WriteLine($"Account: {account}")
                                    shareSecurity.AddAccessControlEntry(entryType, entryRights, domain, account)
                                End If
                                Console.WriteLine()
                                DisplaySecurityDescriptor(shareSecurity)
                            End If
                        End If
                    End If
                End If
            End If
        End Sub
    
        Private Shared Sub DisplayUsage()
            Dim entryAssemblyPath As String = System.Reflection.Assembly.GetEntryAssembly().Location
            Dim entryAssemblyName As String = System.IO.Path.GetFileName(entryAssemblyPath)
    
            Console.WriteLine("Display share properties:")
            Console.WriteLine()
            Console.WriteLine($"{entryAssemblyName} <host> <share> <username> <password>")
            Console.WriteLine()
            Console.WriteLine("Add access control entry:")
            Console.WriteLine()
            Console.WriteLine($"{entryAssemblyName} <host> <share> <username> <password> <type> <rights> <sid>")
            Console.WriteLine($"{entryAssemblyName} <host> <share> <username> <password> <type> <rights> <domain> <account>")
            Console.WriteLine()
            Console.WriteLine(vbTab & $"  <type> - A {GetType(AceType).FullName} enumeration value.")
            Console.WriteLine(vbTab & $"<rights> - A {GetType(FileSystemRights).FullName} enumeration value.")
            Console.WriteLine(vbTab & "   <sid> - An account security identifier.")
            Console.WriteLine()
        End Sub
    
        Private Shared Sub DisplayError(message As String)
            Console.WriteLine($"ERROR: {message}")
            Console.WriteLine()
            DisplayUsage()
        End Sub
    
        Private Shared Sub DisplayTextWithSeparator(text As String)
            Console.WriteLine(text)
            Console.WriteLine(New String("-"c, text.Length))
        End Sub
    
        Private Shared Sub DisplayShareProperties(shareSecurity As ShareSecurity)
            Dim shareProperties As IDictionary(Of String, Object) = shareSecurity.GetShareProperties()
    
            DisplayTextWithSeparator("Share properties")
            For Each propertyName As String In New String() {"Description", "Name", "Path"}
                DisplayProperty(shareProperties, propertyName)
            Next
            Console.WriteLine()
        End Sub
    
        Private Shared Sub DisplaySecurityDescriptor(shareSecurity As ShareSecurity)
            Dim securityDescriptorProperties As IDictionary(Of String, Object) = shareSecurity.GetShareSecurityDescriptorProperties()
    
            DisplayTextWithSeparator("Share security descriptor")
            DisplayProperty(securityDescriptorProperties, "ControlFlags", Function(value) CType(value, ControlFlags))
            Console.WriteLine()
    
            Dim accessControlList As IDictionary(Of String, Object)() = securityDescriptorProperties("DACL")
            For i As Integer = 0 To accessControlList.Length - 1
                Dim accessControlEntryProperties As IDictionary(Of String, Object) = accessControlList(i)
    
                DisplayTextWithSeparator($"Access control entry #{i}")
                DisplayProperty(accessControlEntryProperties, "AccessMask", Function(value) CType(value, FileSystemRights))
                DisplayProperty(accessControlEntryProperties, "AceFlags", Function(value) CType(value, AceFlags))
                DisplayProperty(accessControlEntryProperties, "AceType", Function(value) CType(value, AceType))
                DisplayProperty(accessControlEntryProperties, "Trustee", Function(value) DirectCast(value, IDictionary(Of String, Object))("Name"))
                Console.WriteLine()
            Next
        End Sub
    
        Private Shared Sub DisplayProperty(properties As IDictionary(Of String, Object), propertyName As String)
            DisplayProperty(properties, propertyName, Nothing)
        End Sub
    
        Private Shared Sub DisplayProperty(properties As IDictionary(Of String, Object), propertyName As String, selector As Func(Of Object, Object))
            Dim propertyValue As Object = properties(propertyName)
            Dim displayValue As Object = If(
                selector IsNot Nothing,
                selector.Invoke(propertyValue),
                If(propertyValue, "<null>")
            )
    
            Console.WriteLine($"{propertyName}: {displayValue}")
        End Sub
    End Class
    

    Given a share like this...

    Share properties dialog for example My Share share

    ...with Full Control granted to Administrators and Read granted to Everyone, calling the program like this...

    SO60271689.exe MyComputer "My Share" MyUser MyPassword AccessDenied "Modify, Synchronize" S-1-5-32-546
    

    ...or like this...

    SO60271689.exe MyComputer "My Share" MyUser MyPassword AccessDenied "Modify, Synchronize" BUILTIN Guests
    

    ...produces output like this...

    Share properties
    ----------------
    Description: This is the share description.
    Name: My Share
    Path: C:\My Share
    
    Share security descriptor
    -------------------------
    ControlFlags: DiscretionaryAclPresent, SelfRelative
    
    Access control entry #0
    -----------------------
    AccessMask: ReadAndExecute, Synchronize
    AceFlags: None
    AceType: AccessAllowed
    Trustee: Everyone
    
    Access control entry #1
    -----------------------
    AccessMask: FullControl
    AceFlags: None
    AceType: AccessAllowed
    Trustee: Administrators
    
    New access control entry
    ------------------------
       Type: AccessDenied
     Rights: Modify, Synchronize
        SID: S-1-5-32-546
    
    Share security descriptor
    -------------------------
    ControlFlags: DiscretionaryAclPresent, SelfRelative
    
    Access control entry #0
    -----------------------
    AccessMask: Modify, Synchronize
    AceFlags: None
    AceType: AccessDenied
    Trustee: Guests
    
    Access control entry #1
    -----------------------
    AccessMask: ReadAndExecute, Synchronize
    AceFlags: None
    AceType: AccessAllowed
    Trustee: Everyone
    
    Access control entry #2
    -----------------------
    AccessMask: FullControl
    AceFlags: None
    AceType: AccessAllowed
    Trustee: Administrators
    

    Since VB.NET is not my native tongue, here's the equivalent C# code I started with to get things working...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Management;
    using System.Security.AccessControl;
    
    namespace SO60271689.CSharp
    {
        internal class ShareSecurity
        {
            private string ShareName
            {
                get;
            }
    
            private ObjectGetOptions GetOptions
            {
                get;
            }
    
            private ManagementScope Scope
            {
                get;
            }
    
            public ShareSecurity(string host, string shareName, string username, string password)
            {
                ShareName = shareName;
                GetOptions = new ObjectGetOptions();
                Scope = new ManagementScope(
                    new ManagementPath() {
                        NamespacePath = @"root\cimv2",
                        Server = host
                    },
                    new ConnectionOptions() {
                        // ***** For demonstration purposes only! *****
                        //TODO: Find a secure way to store the remote password
                        Password = password,
                        Username = username
                    }
                );
            }
    
            public IDictionary<string, object> GetShareProperties()
            {
                ManagementPath sharePath = new ManagementPath($"Win32_Share.Name=\"{ShareName}\"");
    
                using (ManagementObject share = new ManagementObject(Scope, sharePath, GetOptions))
                    return GetPropertyDictionary(share);
            }
    
            public IDictionary<string, object> GetShareSecurityDescriptorProperties()
            {
                using (ManagementObject shareSecurity = GetShareSecurity())
                using (ManagementBaseObject securityDescriptor = GetShareSecurityDescriptor(shareSecurity))
                    return GetPropertyDictionary(securityDescriptor);
            }
    
            public void AddAccessControlEntry(AceType entryType, FileSystemRights entryRights, string securityIdentifier)
            {
                AddAccessControlEntry(
                    entryType,
                    entryRights,
                    trustee => trustee["SIDString"] = securityIdentifier
                );
            }
    
            public void AddAccessControlEntry(AceType entryType, FileSystemRights entryRights, string domain, string account)
            {
                AddAccessControlEntry(
                    entryType,
                    entryRights,
                    trustee => {
                        trustee["Domain"] = domain;
                        trustee["Name"] = account;
                    }
                );
            }
    
            /// <param name="trusteeInitializer">Initializes the Win32_Trustee instance for the access control entry to be added.</param>
            private void AddAccessControlEntry(AceType entryType, FileSystemRights entryRights, Action<ManagementObject> trusteeInitializer)
            {
                using (ManagementObject shareSecurity = GetShareSecurity())
                using (ManagementBaseObject securityDescriptor = GetShareSecurityDescriptor(shareSecurity))
                {
                    ManagementBaseObject[] accessControlEntries = (ManagementBaseObject[]) securityDescriptor["DACL"];
    
                    // The class must not be created in the remote scope otherwise CreateInstance()
                    // throws "System.UnauthorizedAccessException: 'Access is denied'."
                    using (ManagementClass accessControlEntryClass = new ManagementClass("Win32_ACE"))
                    using (ManagementObject accessControlEntry = accessControlEntryClass.CreateInstance())
                    {
                        accessControlEntry["AccessMask"] = (uint) entryRights;
                        accessControlEntry["AceFlags"] = (uint) AceFlags.None;
                        accessControlEntry["AceType"] = (uint) entryType;
    
                        // The class must not be created in the remote scope otherwise CreateInstance()
                        // throws "System.UnauthorizedAccessException: 'Access is denied'."
                        using (ManagementClass trusteeClass = new ManagementClass("Win32_Trustee"))
                        using (ManagementObject trustee = trusteeClass.CreateInstance())
                        {
                            trusteeInitializer.Invoke(trustee);
                            accessControlEntry["Trustee"] = trustee;
    
                            // Create a new access control list including the new access control
                            // entry, sorted with Deny entries first (true sorts after false)
                            // https://learn.microsoft.com/windows/win32/secauthz/order-of-aces-in-a-dacl
                            securityDescriptor["DACL"] = accessControlEntries
                                .Append(accessControlEntry)
                                .OrderByDescending(entry => (AceType) (uint) entry["AceType"] == AceType.AccessDenied)
                                .ToArray();
    
                            SetShareSecurityDescriptor(shareSecurity, securityDescriptor);
                        }
                    }
                }
            }
    
            private ManagementObject GetShareSecurity()
            {
                ManagementPath shareSecurityPath = new ManagementPath($"Win32_LogicalShareSecuritySetting.Name=\"{ShareName}\"");
    
                return new ManagementObject(Scope, shareSecurityPath, GetOptions);
            }
    
            private ManagementBaseObject GetShareSecurityDescriptor(ManagementObject shareSecurity)
            {
                // Create an array to store the output parameter
                ManagementBaseObject[] invokeParameters = new ManagementBaseObject[1];
                uint invokeResult = (uint) shareSecurity.InvokeMethod("GetSecurityDescriptor", invokeParameters);
    
                if (invokeResult == 0)
                    return invokeParameters[0];
                else
                {
                    //TODO: Handle failure of GetSecurityDescriptor()...
                    return null;
                }
            }
    
            private void SetShareSecurityDescriptor(ManagementObject shareSecurity, ManagementBaseObject securityDescriptor)
            {
                // Create an array to store the input parameter
                ManagementBaseObject[] invokeParameters = new ManagementBaseObject[1] { securityDescriptor };
                uint invokeResult = (uint) shareSecurity.InvokeMethod("SetSecurityDescriptor", invokeParameters);
    
                if (invokeResult != 0)
                {
                    //TODO: Handle failure of SetSecurityDescriptor()...
                }
            }
    
            private static IDictionary<string, object> GetPropertyDictionary(ManagementBaseObject obj)
            {
                return obj.Properties
                    .Cast<PropertyData>()
                    .ToDictionary(
                        property => property.Name,
                        // Recursively create dictionaries in place of management objects
                        property => property.Value is ManagementBaseObject[] baseObjectArray
                            ? baseObjectArray.Select(GetPropertyDictionary).ToArray()
                            : property.Value is ManagementBaseObject baseObject
                            ? GetPropertyDictionary(baseObject)
                            : property.Value
                    );
            }
        }
    }
    
    using System;
    using System.Collections.Generic;
    using System.Security.AccessControl;
    
    namespace SO60271689.CSharp
    {
        class Program
        {
            public static void Main(string[] args)
            {
                if (args == null || args.Length == 0)
                    DisplayUsage();
                else if (args.Length < 4)
                    DisplayError("Too few arguments.");
                else
                {
                    string host = args[0];
                    string shareName = args[1];
                    string username = args[2];
                    // ***** For demonstration purposes only! *****
                    //TODO: Find a secure way to store the remote password
                    string password = args[3];
                    ShareSecurity shareSecurity = new ShareSecurity(host, shareName, username, password);
    
                    DisplayShareProperties(shareSecurity);
                    DisplaySecurityDescriptor(shareSecurity);
    
                    if (args.Length > 4)
                    {
                        if (args.Length != 7 && args.Length != 8)
                            DisplayError("Argument count mismatch.");
                        else
                        {
                            if (!Enum.TryParse(args[4], true, out AceType entryType))
                                DisplayError($"Invalid <type> value \"{args[4]}\".");
                            else if (!Enum.TryParse<FileSystemRights>(args[5], true, out FileSystemRights entryRights))
                                DisplayError($"Invalid <rights> value \"{args[5]}\".");
                            else
                            {
                                DisplayTextWithSeparator("New access control entry");
                                Console.WriteLine($"   Type: {entryType}");
                                Console.WriteLine($" Rights: {entryRights}");
    
                                if (args.Length == 7)
                                {
                                    string securityIdentifier = args[6];
    
                                    Console.WriteLine($"    SID: {securityIdentifier}");
                                    shareSecurity.AddAccessControlEntry(entryType, entryRights, securityIdentifier);
                                }
                                else // args.Length == 8
                                {
                                    string domain = args[6];
                                    string account = args[7];
    
                                    Console.WriteLine($" Domain: {domain}");
                                    Console.WriteLine($"Account: {account}");
                                    shareSecurity.AddAccessControlEntry(entryType, entryRights, domain, account);
                                }
                                Console.WriteLine();
                                DisplaySecurityDescriptor(shareSecurity);
                            }
                        }
                    }
                }
            }
    
            private static void DisplayUsage()
            {
                string entryAssemblyPath = System.Reflection.Assembly.GetEntryAssembly().Location;
                string entryAssemblyName = System.IO.Path.GetFileName(entryAssemblyPath);
    
                Console.WriteLine("Display share properties:");
                Console.WriteLine();
                Console.WriteLine($"{entryAssemblyName} <host> <share> <username> <password>");
                Console.WriteLine();
                Console.WriteLine("Add access control entry:");
                Console.WriteLine();
                Console.WriteLine($"{entryAssemblyName} <host> <share> <username> <password> <type> <rights> <sid>");
                Console.WriteLine($"{entryAssemblyName} <host> <share> <username> <password> <type> <rights> <domain> <account>");
                Console.WriteLine();
                Console.WriteLine($"\t  <type> - A {typeof(AceType).FullName} enumeration value.");
                Console.WriteLine($"\t<rights> - A {typeof(FileSystemRights).FullName} enumeration value.");
                Console.WriteLine("\t   <sid> - An account security identifier.");
                Console.WriteLine();
            }
    
            private static void DisplayError(string message)
            {
                Console.WriteLine($"ERROR: {message}");
                Console.WriteLine();
                DisplayUsage();
            }
    
            private static void DisplayTextWithSeparator(string text)
            {
                Console.WriteLine(text);
                Console.WriteLine(new string('=', text.Length));
            }
    
            private static void DisplayShareProperties(ShareSecurity shareSecurity)
            {
                IDictionary<string, object> shareProperties = shareSecurity.GetShareProperties();
    
                DisplayTextWithSeparator("Share properties");
                foreach (string propertyName in new string[] { "Description", "Name", "Path" })
                    DisplayProperty(shareProperties, propertyName);
                Console.WriteLine();
            }
    
            private static void DisplaySecurityDescriptor(ShareSecurity shareSecurity)
            {
                IDictionary<string, object> securityDescriptorProperties = shareSecurity.GetShareSecurityDescriptorProperties();
    
                DisplayTextWithSeparator("Share security descriptor");
                DisplayProperty(securityDescriptorProperties, "ControlFlags", value => (ControlFlags) (uint) value);
                Console.WriteLine();
    
                IDictionary<string, object>[] accessControlList = (IDictionary<string, object>[]) securityDescriptorProperties["DACL"];
                for (int i = 0; i < accessControlList.Length; i++)
                {
                    IDictionary<string, object> accessControlEntryProperties = accessControlList[i];
    
                    DisplayTextWithSeparator($"Access control entry #{i}");
                    DisplayProperty(accessControlEntryProperties, "AccessMask", value => (FileSystemRights) (uint) value);
                    DisplayProperty(accessControlEntryProperties, "AceFlags", value => (AceFlags) (uint) value);
                    DisplayProperty(accessControlEntryProperties, "AceType", value => (AceType) (uint) value);
                    DisplayProperty(accessControlEntryProperties, "Trustee", value => ((IDictionary<string, object>) value)["Name"]);
                    Console.WriteLine();
                }
            }
    
            private static void DisplayProperty(IDictionary<string, object> properties, string propertyName)
            {
                DisplayProperty(properties, propertyName, null);
            }
    
            private static void DisplayProperty(IDictionary<string, object> properties, string propertyName, Func<object, object> selector)
            {
                object propertyValue = properties[propertyName];
                object displayValue = selector != null
                    ? selector.Invoke(propertyValue)
                    : propertyValue ?? "<null>";
    
                Console.WriteLine($"{propertyName}: {displayValue}");
            }
        }
    }