January 14, 2015

Retrieve the Saved Views (UserQuery) of all CRM users

Dynamics CRM allows the users to create personal views (using Advanced Find) and eventually share them with other users.

The entity used to store these views is called UserQuery and the main properties are:
  • Name: Name given to the saved view
  • FetchXml: String that specifies the query in Fetch XML language
  • OwnerId: Unique identifier of the user or team who owns the saved view
In order to retrieve the saved views we can use FetchXml or a QueryExpression, but the result will always contain only the saved views of the user executing the query.

Consider the following simplified scenario:
  • CRM has only two users: John (System Administrator role) and Bob (Sales Manager role)
  • John has a personal view for the Account Entity (not shared)
  • Bob has a personal view for the Contact Entity (not shared)
If John executes a query to return all the UserQuery records, the result will contain only his Account personal view, despite his System Administrator role.
What if we want to retrieve all the saved views for all the users? A possibility is to impersonate each CRM user, run the query and combine the results.
To implement this solution we rely on the CallerId property of the OrganizationServiceProxy object in combination with the "Act on Behalf of Another User" privilege.
The OrganizationServiceProxy gives us the possibility to impersonate the user by code, the "Act on Behalf of Another User" privilege is required to allow this impersonation.

The following code is a simplified example of the solution:
// url and credentials
string organizationUrl = "https://mycompany.crm.dynamics.com/XRMServices/2011/Organization.svc";
string userName = "john@mycompany.onmicrosoft.com";
string password = "JohnPassword";

// authentication code
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = userName;
credentials.UserName.Password = password;
IServiceManagement<IOrganizationService> orgServiceManagement = ServiceConfigurationFactory.CreateManagement(new Uri(organizationUrl));
AuthenticationCredentials authCredentials = new AuthenticationCredentials();
authCredentials.ClientCredentials = credentials;
AuthenticationCredentials tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
SecurityTokenResponse organizationTokenResponse = tokenCredentials.SecurityTokenResponse;

// IOrganizationService and OrganizationServiceProxy objects
OrganizationServiceProxy serviceProxy;
IOrganizationService service;

using (serviceProxy = new OrganizationServiceProxy(orgServiceManagement, organizationTokenResponse))
{
    service = (IOrganizationService)serviceProxy;
    
    // Dictionary to contain all the saved views
    Dictionary<Guid, Entity> dictPersonalViews = new Dictionary<Guid, Entity>();

    // retrieve first all the CRM Users
    QueryExpression systemUsers = new QueryExpression("systemuser");
    systemUsers.ColumnSet = new ColumnSet(true);
    EntityCollection userCollection = service.RetrieveMultiple(systemUsers);


    // for each User we launch the query to retrieve the saved views
    foreach (Entity systemUser in userCollection.Entities)
    {
        QueryExpression personalViews = new QueryExpression("userquery");
        personalViews.ColumnSet = new ColumnSet(true);

        // we set the CallerId property to impersonate the current iteration user
        serviceProxy.CallerId = systemUser.Id;
        EntityCollection viewCollection = serviceProxy.RetrieveMultiple(personalViews);

        foreach (Entity personalView in viewCollection.Entities)
        {
            // we want a list without duplicates (shared views or automatically shared to SYSTEM and INTEGRATION users)
            if (!dictPersonalViews.ContainsKey(personalView.Id))
            {
                dictPersonalViews.Add(personalView.Id, personalView);
            }
        }
    }

    // we can process the values (Entity objects) of the dictionary
    foreach (Entity personalView in dictPersonalViews.Values)
    {
       string viewName = personalView["name"].ToString();
       string viewFetchXml = personalView["fetchxml"].ToString();
       EntityReference viewOwnerIdRef = (EntityReference)personalView["ownerid"];
    }
}

2 comments:

  1. Hello Guido,

    Interesting point, i'm currently migration from onprem to online and running into issues to migrate the user views.

    I agree that your approach is good for existing users.
    What if some users are disabled and not in your AD anymore to impersonate them ? (yes i need their views since they shared a lot of them with other users ...)

    Thanks,
    Clém

    ReplyDelete
    Replies
    1. tricky situation, probably the easiest way it to recreate them inside AD with the same username and re-activate the users inside CRM (I never did so I don't know if this would work). The alternative because you are onpremise is to extract the fetchxml of these views directly from the CRM DB

      Delete