0

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!

Giallo
  • 785
  • 2
  • 10
  • 26

1 Answers1

1

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
}
Alex X.
  • 453
  • 5
  • 11
  • Woh, amazing! Thanks for the answer. – Giallo May 28 '15 at 13:41
  • You are welcome. By the way, to get the complete list of the **Authorization Constants** (like _SeServiceLogonRight_) check the following [MSDN article](https://msdn.microsoft.com/en-us/library/windows/desktop/aa375728%28v=vs.85%29.aspx). – Alex X. May 29 '15 at 13:59