Search code examples
c#.netinstallationgroup-policyvisual-studio-setup-proje

How to change Windows 8.1 Group Policy programmatically in c#?


I am developing a Windows 8.1 kiosk-type application,and I need to set the group policy custom user interface setting to the .exe of the application. This, of course, can be accomplished by hand, but I would like to be able to change it via c#, or alternatively, an installer (I'm using Visual Studio Setup right now).

Thanks for the help!


Solution

  • The way to go is:

    1. Create a Custom Action and add it to the Install section of your installer.
    2. Use the unmanaged LsaAddAccountRights method to set any policy to any user account or group (e.g. Administrators, Users, Everyone). All you need to know is the right Policy Handle and the User Right that you want to assign.

    Now, down to the implementation :-)

    Please check the code below. This is the implementation I ended up with by analyzing and borrowing different code snippets from different resources:

    public class LSAManager
    {
        [DllImport("advapi32.dll", PreserveSig = true)]
        private static extern uint LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, Int32 DesiredAccess, out IntPtr PolicyHandle);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
        private static extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr pSID, LSA_UNICODE_STRING[] UserRights, int CountOfRights);
        [DllImport("advapi32.dll")]
        public static extern void FreeSid(IntPtr pSid);
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)]
        private static extern bool LookupAccountName(string lpSystemName, string lpAccountName, IntPtr psid, ref int cbsid, StringBuilder domainName, ref int cbdomainLength, ref int use);
        [DllImport("advapi32.dll")]
        private static extern bool IsValidSid(IntPtr pSid);
        [DllImport("advapi32.dll")]
        private static extern int LsaClose(IntPtr ObjectHandle);
        [DllImport("advapi32.dll")]
        private static extern int LsaNtStatusToWinError(int status);
    
        [DllImport("kernel32.dll")]
        private static extern int GetLastError();
    
        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_UNICODE_STRING
        {
            public UInt16 Length;
            public UInt16 MaximumLength;
            public IntPtr Buffer;
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_OBJECT_ATTRIBUTES
        {
            public int Length;
            public IntPtr RootDirectory;
            public LSA_UNICODE_STRING ObjectName;
            public UInt32 Attributes;
            public IntPtr SecurityDescriptor;
            public IntPtr SecurityQualityOfService;
        }
    
        private enum LSA_AccessPolicy : long
        {
            POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
            POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
            POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
            POLICY_TRUST_ADMIN = 0x00000008L,
            POLICY_CREATE_ACCOUNT = 0x00000010L,
            POLICY_CREATE_SECRET = 0x00000020L,
            POLICY_CREATE_PRIVILEGE = 0x00000040L,
            POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
            POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
            POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
            POLICY_SERVER_ADMIN = 0x00000400L,
            POLICY_LOOKUP_NAMES = 0x00000800L,
            POLICY_NOTIFICATION = 0x00001000L
        }
    
        /// <summary>Adds a privilege to an account</summary>
        /// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
        /// <param name="privilegeName">Name of the privilege</param>
        /// <returns>The windows error code returned by LsaAddAccountRights (0 = success)</returns>
        public static long SetRight(String accountName, String privilegeName)
        {
            long winErrorCode = 0; //contains the last error
    
            //pointer an size for the SID
            IntPtr sid = IntPtr.Zero;
            int sidSize = 0;
            //StringBuilder and size for the domain name
            StringBuilder domainName = new StringBuilder();
            int nameSize = 0;
            //account-type variable for lookup
            int accountType = 0;
    
            //get required buffer size
            LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
    
            //allocate buffers
            domainName = new StringBuilder(nameSize);
            sid = Marshal.AllocHGlobal(sidSize);
    
            //lookup the SID for the account
            bool result = LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
    
            // check SID if required: IsValidSid(sid));
    
            if (!result)
            {
                winErrorCode = GetLastError();
            }
            else
            {
                //initialize an empty unicode-string
                LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING();
                //combine all policies
                int access = (int)(
                    LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
                    LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
                    LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
                    LSA_AccessPolicy.POLICY_CREATE_SECRET |
                    LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
                    LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
                    LSA_AccessPolicy.POLICY_NOTIFICATION |
                    LSA_AccessPolicy.POLICY_SERVER_ADMIN |
                    LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
                    LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
                    LSA_AccessPolicy.POLICY_TRUST_ADMIN |
                    LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
                    LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
                    );
                //initialize a pointer for the policy handle
                IntPtr policyHandle = IntPtr.Zero;
    
                //these attributes are not used, but LsaOpenPolicy wants them to exists
                LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
                ObjectAttributes.Length = 0;
                ObjectAttributes.RootDirectory = IntPtr.Zero;
                ObjectAttributes.Attributes = 0;
                ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
                ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
    
                //get a policy handle
                uint resultPolicy = LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);
                winErrorCode = LsaNtStatusToWinError((int)resultPolicy);
    
                if (winErrorCode == 0)
                {
                    //Now that we have the SID and the policy,
                    //we can add rights to the account.
    
                    //initialize an unicode-string for the privilege name
                    LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
                    userRights[0] = new LSA_UNICODE_STRING();
                    userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
                    userRights[0].Length = (UInt16)(privilegeName.Length * UnicodeEncoding.CharSize);
                    userRights[0].MaximumLength = (UInt16)((privilegeName.Length + 1) * UnicodeEncoding.CharSize);
    
                    //add the right to the account
                    uint res = LsaAddAccountRights(policyHandle, sid, userRights, 1);
    
                    winErrorCode = LsaNtStatusToWinError((int)res);
    
                    LsaClose(policyHandle);
                }
                FreeSid(sid);
            }
    
            return winErrorCode;
        }
    }
    

    I've put this code in a separate class called LSAManager for convenience. In order to use this code, simply call:

    string userName = "alex";
    // Set "Logon As a Service" privilege (for example):
    if (LSAManager.SetRight(userName, "SeServiceLogonRight") != 0)
    {
        // handle error here
    }