Monday, May 9, 2011

Bulk updating user's preferences (UserSettings) in CRM: PART I


I’ve realized it has been some time since my last post. I was quite busy with a CRM project, and I’d like to share some of the lessons I have learned. Sometimes we need to change the user settings for multiple users in CRM and we don’t want to ask each user to do it themselves. For example, for a Canadian organization we are often given a list of users with their corresponding time zone and preferred language. However, there is no functionality in the CRM application to modify someone else’s personal options, even if you log in as a system administrator. In this blog post I will share a tool that I have developed for easily managing user preferences and I will walk you through the design so you can extend the tool if necessary.

 I have split this post into 3 parts:
·         Part One: Retrieving a spreadsheet with the list of available time zones and languages
·         Part Two: Generating a spreadsheet for entering the user preferences for each user
·         Part Three: Uploading the updated spreadsheet to update user preferences




Part One: Retrieving a spreadsheet with the list of available time zones and languages
Once you have installed all the language packs you will require, it will be useful to have a list of all available time zones and languages available. Because we have to deal with time zone and language codes, we need to keep a mapping between codes and names (e.g. 1033 = English). The mappings will be useful later on in part two and three. Note that in my sample code I am using strong types so you need to generate the strong types using crmsvcutil.exe. For convenience you can download mine here and include it in your project. I will also post the final VS Solution that I used at the end of part three.
 
1.       The program!

Let’s start with the skeleton of the program we will use:
class UpdateCRMSystemUserSettings
{
    static string serverName = "192.168.1.68:5555";
    static string organizationName = "Avanade";
    static string userMame = "administrator";
    static string userDomain = "avanadedom";
    static string userpassword = "xxxxx";
    static string usersFileName = @"C:\users.xml";
    static string timeZonesFile = @"C:\TimeZones.xml";
    static string languagesFile = @"C:\Languages.xml";
    static string usersCompletedFileName = @"C:\usersCompleted.xml";
    static Dictionary<int, string> TimeZoneCodeToName = new Dictionary<int, string>();
    static Dictionary<string, int> TimeZoneNameToCode = new Dictionary<string, int>();
    static Dictionary<int, string> LanguageCodeToName = new Dictionary<int, string>();
    static Dictionary<string, int> LanguageNameToCode = new Dictionary<string, int>();

    static void Main(string[] args)
    {
        try
        {
            Organization org = new Organization();
            IOrganizationService service = org.Open(serverName, organizationName, userMame, userDomain, userpassword);

            CreateLanguageMappings(service, languagesFile);
            CreateTimeZoneMappings(service, timeZonesFile);
               
            //CreateUserSettingsSpreadsheet(service, usersFileName);
            //UploadUserSettingsSpreadsheet(service, usersCompletedFileName);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            Console.ReadLine();
        }
    }
}

You might notice we are keeping mappings between codes and names for languages and time zones in memory as well. You might also notice that I am using an Organization class that I have defined for connecting to an On-Premise deployment, but you can change those lines to get the IOrganizationService for your own organization.


2.       Create the language mappings
In this method I am retrieving the available languages in the organization and creating a two-way mapping between the language code and the language name. I am saving the mapping as an XML file (languagesFile) which is a spreadsheet that we can open in Excel. I am also keeping the mapping in memory using the dictionaries I defined (will be useful later):
private static void CreateLanguageMappings(IOrganizationService service, string fileName)



{
    XmlDocument xDoc = new XmlDocument();
    xDoc.AppendChild(xDoc.CreateElement("Languages"));
    RetrieveProvisionedLanguagesRequest req = new RetrieveProvisionedLanguagesRequest();
    RetrieveProvisionedLanguagesResponse resp = (RetrieveProvisionedLanguagesResponse)service.Execute(req);
    foreach (int lcid in resp.RetrieveProvisionedLanguages)
    {
        CultureInfo culture = CultureInfo.GetCultureInfo(lcid);

        XmlNode languageNode = xDoc.CreateElement("Language");
        XmlNode lcidNode = xDoc.CreateElement("LanguageID");
        XmlNode languageNameNode = xDoc.CreateElement("Name");
        xDoc.DocumentElement.AppendChild(languageNode);
        languageNode.AppendChild(lcidNode);
        languageNode.AppendChild(languageNameNode);

        lcidNode.InnerText = lcid.ToString();
        languageNameNode.InnerText = culture.EnglishName;

        LanguageCodeToName[lcid] = culture.EnglishName;
        LanguageNameToCode[culture.EnglishName] = lcid;
    }
    xDoc.Save(fileName);
}



3.       Create the time zone mappings


Now I am doing the same as I did with the languages but for the time zones:
private static void CreateTimeZoneMappings(IOrganizationService service, string fileName)
{
    XmlDocument xDoc = new XmlDocument();
    xDoc.AppendChild(xDoc.CreateElement("TimeZones"));

    QueryExpression query = new QueryExpression(TimeZoneDefinition.EntityLogicalName);
    query.ColumnSet = new ColumnSet("timezonecode", "standardname");

    EntityCollection coll = service.RetrieveMultiple(query);
    foreach (Entity e in coll.Entities)
    {
        XmlNode timeZoneNode = xDoc.CreateElement("TimeZone");
        XmlNode timeZoneCodeNode = xDoc.CreateElement("TimeZoneCode");
        XmlNode timeZoneNameNode = xDoc.CreateElement("Name");
        xDoc.DocumentElement.AppendChild(timeZoneNode);
        timeZoneNode.AppendChild(timeZoneCodeNode);
        timeZoneNode.AppendChild(timeZoneNameNode);

        int timeZoneCode = (int)e["timezonecode"];
        string timeZoneName =  e["standardname"] as string;
        timeZoneCodeNode.InnerText = timeZoneCode.ToString();
        timeZoneNameNode.InnerText = timeZoneName;

        TimeZoneCodeToName[timeZoneCode] = timeZoneName;
        TimeZoneNameToCode[timeZoneName] = timeZoneCode;
    }
    xDoc.Save(fileName);
}





4.       Verify your spreadsheets
By now you should have produced two XML files that when you open with Excel should look like this:


LanguageID

Name

1033

English (United States)

1036

French (France)



TimeZoneCode

Name

256

Canberra, Melbourne, Sydney (Commonwealth Games 2006)

55

SA Western Standard Time

85

GMT Standard Time

180

Ekaterinburg Standard Time

158

Arabic Standard Time

196

Bangladesh Standard Time

29

Central Standard Time (Mexico)

165

Arabian Standard Time

…list continues…

…list continues…



1 comment: