Setting file permissions in languages other than English (Windows Installer, WIX)

Answered Setting file permissions in languages other than English (Windows Installer, WIX)

  • Tuesday, October 02, 2007 8:31 PM
     
     

    I have an application that I am developing that needs to write Per-Machine data. This data has been put in

    %CommonApplicationData% ("C:\Documents and Settings\All Users\Local Settings" on Windows XP and "C:\ProgramData\" on Windows Vista).

     

    In order for the application to be able to write to this folder, I have to set permissions. To do this, I use the LockPermissions table. This works fine in English, however under other language operating systems, the user groups "Users" and "Administrators" does not correctly map to the groups I want.

    • How do I map permissions to the "Users" and "Administrators" groups in alternative languages?
    • Or, how can I set the permissions on the files and folders in order to allow any user to be able to read and write to these folders?

    In addition, the documentation for the LockPermissions.User column is not very specific as to how to format a string to specify multiple users, and if you can specify well known SID's.

    • How do you specify multiple users or groups in the "User" column?
    • Can you specify well known SID's in the "User" column?

    There are many related articles that I have pursued that cannot answer my question either:

All Replies

  • Wednesday, October 03, 2007 8:20 PM
     
     Answered

    So, I’ve spent all day working on this, so I might as well post the solution I created. This workaround is a bit complicated. Let me start by explaining.

     

    The LockPermissions table in the Windows Installer MSI package is used to apply permissions to files and folders. As documented, when specifying “Everyone” or “Administrators”, it is localized on the target system. The problem is that nothing else is localized. I need to also set permissions for “Users”. There is no condition support on the LockPermissions table.

     

    However the User column can accept a formatted text string, meaning a property can be used, and it will retrieve the value of this property.

     

    So, the solution is to create a set of properties, custom actions to set those properties, and then set the custom actions to happen during the install sequence depending on the system language.

     

    A very complicated solution to what could have just been support for conditions in the LockPermissions table, or localization of all well known user groups and users.

     

    On to the code…

     

    As a quick note, I am using Windows Installer XML (WIX) tool to create an MSI. This seems considerably easier than some of the other tools we have used in the past (like Install Shield). Code samples are in the WIX language.

     

    First l should start with the code that I started with. This code would generate an error during the installation on any non-English system. The installer would complain about the group “Users” not existing.

     

     

    <Component Id="cmpSetFolderPermissions" Guid="UNIQUE_GUID_HERE">

       

        <CreateFolder Directory="ProgramMenuApplicationFolder" >

            <Permission User="Users"

                GenericAll="yes" ChangePermission="yes" />

            <Permission User="Administrators"

                GenericAll="yes" ChangePermission="yes" />

        </CreateFolder>                       

       

        <CreateFolder Directory="ApplicationAppDataFolder" >

            <Permission User="Users"

                GenericAll="yes" ChangePermission="yes" />

            <Permission User="Administrators"

                GenericAll="yes" ChangePermission="yes" />

        </CreateFolder>

       

    </Component>

     

     

     

    So the first order of business is to declare the properties. As you can see, I didn’t bother to document the localized values for the groups “Everyone” and “Administrators”, since these are automatic. I have properties for them for consistency; you could simply use the raw string.

     

    I’ve also included some other values, just in case I need them later. I’m not going to bother to localize them unless I need to.

     

    Why all caps? Because that makes these values global. There not set correctly unless there global.

     

     

    <Property Id="USERGROUP_EVERYONE"       Value="Everyone"       />

    <Property Id="USERGROUP_ADMINISTRATORS" Value="Administrators" />

    <Property Id="USERGROUP_USERS"          Value="Users"          /><!--

        English         German           French           Spanish

        Users           Benutzer         Utilisateurs     Usuarios     -->

    <Property Id="USERGROUP_GUESTS"         Value="Guests"         /><!--

        English         German           French           Spanish

        Guests          Gäste            Invités          Invitados    -->

    <Property Id="USER_GUEST"               Value="Guest"          /><!--

        English         German           French           Spanish

        Guest           Gast             Invité           Invitado     -->

    <Property Id="USER_ADMINISTRATOR"       Value="Administrator"  /><!--

        English         German           French           Spanish

        Administrator                    Administrateur   Administrador -->

     

     

    Here are the custom actions to apply the localized name for the “Users” group.

     

     

    <CustomAction Id="SetUserGroup_Users_De"

        Property="USERGROUP_USERS"

        Value="Benutzer"

        Return="check" />

     

    <CustomAction Id="SetUserGroup_Users_Es"

        Property="USERGROUP_USERS"

        Value="Usuarios"

        Return="check" />

     

    <CustomAction Id="SetUserGroup_Users_Fr"

        Property="USERGROUP_USERS"

        Value="Utilisateurs"

        Return="check" />

     

     

    Here is the scheduling of the custom actions. This is what updates the values of the properties above to the localized values after the “LaunchConditions” event.

     

     

    <InstallUISequence>

                     

        <Custom Action="SetUserGroup_Users_De" After="LaunchConditions">

            SystemLanguageID = "1031"

        </Custom>

     

        <Custom Action="SetUserGroup_Users_Es" After="LaunchConditions">

            SystemLanguageID = "3082"

        </Custom>

     

        <Custom Action="SetUserGroup_Users_Fr" After="LaunchConditions">

            SystemLanguageID = "1036"

        </Custom>

     

    </InstallUISequence>

     

     

    Here you can see the same component as I started with, but with some modifications to use the properties above.

     

     

    <Component Id="cmpSetFolderPermissions" Guid="UNIQUE_GUID_HERE">

     

        <CreateFolder Directory="ProgramMenuZeroClickFolder" >

            <Permission User="[UserGroup_Administrators]"

                GenericAll="yes" ChangePermission="yes" />

            <Permission User="[USERGROUP_USERS]"

                GenericAll="yes" ChangePermission="yes" />

        </CreateFolder>                      

     

        <CreateFolder Directory="ZeroClickAppDataFolder" >

            <Permission User="[UserGroup_Administrators]"

                GenericAll="yes" ChangePermission="yes" />

            <Permission User="[USERGROUP_USERS]"

                GenericAll="yes" ChangePermission="yes" />

        </CreateFolder>

     

    </Component>

     

     

     

    Hopefully this helps anyone else who is having these types of issues. It seems the security model in Vista has proven to be a big hassle to most application developers.

     

    (By the way, as a small constructive criticism: the post editor on this forum is terrible. Adding code blocks appeared to erase my code in other code blocks, and selecting fonts and pasting text seemed very buggy and to behave unexpectedly. While trying to write this reply, I was suddenly logged out for unknown reasons.)

  • Tuesday, October 16, 2007 5:31 AM
     
     

    Thank you for this imformation. I had the same problem. But using the SystemLanguageID did not always turn out to have the correct value. I deployed my msi on a Windows Server 2003 EN system but the installation failed with the message that a group or user  with the name "Gebruikers" (Dutch word for Users) couldn't be found. So the SystemLanguageID contained the value 1043 (NL-nl) but the system was an English system. So I know use a custom action written in C to translate a string representation of a wellknown SID to the actual name on the system. In this way your are not dependent on the default system language because that can be changed.

     

    The custom action requires two properties to be set: first the TRANSLATE_SID property must contain the string representation of the SID e.g. for the Users group that is S-1-5-32-545. The second property that is required is the TRANSLATE_SID_PROPERTY containing the name of property to set with the translated name.

     

    My wix file contains the following statements:

     

    <Binary Id="TranslateSid" SourceFile="S:\ExtRef\TranslateSid\TranslateSid.dll"/>
        
    <CustomAction Id="SetUserGroupUsers"

    Property="TRANSLATE_SID"

    Value="S-1-5-32-545"

    Return="check" />

     

    <CustomAction Id="SetUserGroupUsersProp"

    Property="TRANSLATE_SID_PROPERTY"

    Value="USERGROUP_USERS"

    Return="check" />
        

    <CustomAction Id="TranslateUserGroupUsers"

    BinaryKey="TranslateSid"

    DllEntry="TranslateSid"/>

     

    <InstallExecuteSequence>
         <Custom Action="SetUserGroupUsers"

    After="LaunchConditions">NOT Installed</Custom>

         <Custom Action="SetUserGroupUsersProp"

    After="SetUserGroupUsers">NOT Installed</Custom>

         <Custom Action="TranslateUserGroupUsers"

    After="SetUserGroupUsersProp">NOT Installed</Custom>    

    </InstallExecuteSequence>

     

    The C function looks like the following:

     

    extern "C" UINT __stdcall TranslateSid (MSIHANDLE hInstall)

    {

    TCHAR szSid[MAX_PATH] = {0};

    TCHAR szSidProperty[MAX_PATH] = {0};

    TCHAR szName[MAX_PATH] = {0};

    DWORD size = MAX_PATH;

    UINT ret = 0;

    ret = MsiGetProperty (hInstall, _T("TRANSLATE_SID"), szSid, &size);

    if(ret != ERROR_SUCCESS)

    {

    return 4444;

    }

    size = MAX_PATH;

    ret = MsiGetProperty (hInstall, _T("TRANSLATE_SID_PROPERTY"), szSidProperty, &size);

    if(ret != ERROR_SUCCESS)

    {

    return 4445;

    }

    PSID pSID = NULL;

    if(!ConvertStringSidToSid(szSid, &pSID))

    {

    return 4446;

    }

    size = MAX_PATH;

    TCHAR szRefDomain[MAX_PATH] = {0};

    SID_NAME_USE nameUse;

    DWORD refSize = MAX_PATH;

    if(!LookupAccountSid(NULL, pSID, szName, &size, szRefDomain, &refSize, &nameUse))

    {

    if(pSID != NULL)

    {

    LocalFree(pSID);

    }

    return 4447;

    }

    ret = MsiSetProperty (hInstall, szSidProperty, szName);

    if(!ConvertStringSidToSid(szSid, &pSID))

    {

    if(pSID != NULL)

    {

    LocalFree(pSID);

    }

    return 4448;

    }

    if(pSID != NULL)

    {

    LocalFree(pSID);

    }

    return ERROR_SUCCESS;

    }

     

     

    This works for me on a lot of different windows versions with different languages.