Programmatically removing attributes from user views in Dynamics CRM

If you have used an attribute in a saved view in Dynamics, and the attribute is removed from the entity, you will get the following error when you try and access the view:

Whilst it is straightforward to amend the view definition in Dynamics and remove the attribute from the view, we recently had a situation where many users had taken a system view containing the now-deleted attribute as the starting point for their own views, and thus several dozen users had views with the deleted attribute in it. Rather than wait for the support calls to come flooding in when users were presented with an error message which they did not understand, we decided to write a script which would go through each users views and remove the offending attribute.

The process is quite simple:

  1. Using impersonation, loop through each user in the system and query their user views for views containing the attribute.
  2. For each view containing the attribute in question, edit the ‘fetchxml’ and ‘layoutxml’ attributes, removing the references to the attribute
  3. Save the views

Here’s some sample code which would remove two attributes from all views belonging to a particular set of users. Amend the attribute, entities and user filter to tailor to your own requirements.

void Main()
{
    Console.WriteLine("Start");

    // Connect to CRM Organisation Service - you will need to change this to suit however you normally connect to CRM
    OrganizationServiceProxy service = MyExtensions.ConnectToCrm("dev2");

    // This is the entity which has had the attribute(s) removed
    string entityName = "bsp_trv_insuranceapplication";

    EntityMetadata em = GetEntityMetadata(entityName, service);

    // List of attributes to be removed from the view
    // You must list all attributes which no longer exist here, as you will not be able to save the views if there are ANY invalid
    // attributes in the view definition you are changing
    string[] attributesToBeDeleted = new string[] { "bsp_additionalinformation", "bsp_trv_addressln3" };

    // Get the users which might have views with the deleted attributes in - you may need to alter this query - eg restrict by Business Unit?
    EntityCollection users = GetUsers(service);

    foreach (var user in users.Entities)
    {
        RemoveAttributesFromUserViews(service, user, em.ObjectTypeCode, attributesToBeDeleted);
    }

    Console.WriteLine("Finished");
}

void RemoveAttributesFromUserViews(OrganizationServiceProxy service, Entity user, int? objectTypeCode, string[] attributesToBeDeleted)
{
    try
    {
        Console.WriteLine("Checking views for user " + user["domainname"]);

        // Impersonate the user
        service.CallerId = user.Id;

        // Filter for any views for the entity which contain the attributes to be deleted and which belong to the current user            
        string filter = @"<filter type='and'>
                <condition attribute='ownerid' operator='eq' value='" + user.Id + "' />";
        filter += @"<condition attribute='returnedtypecode' operator='eq' value='" + objectTypeCode + "' />";
        filter += @"<filter type='or' >";

        foreach (string attributeToBeDeleted in attributesToBeDeleted)
        {
            filter += @"<condition attribute='fetchxml' operator='like' value='%" + attributeToBeDeleted + "%' />";
        }
        filter += @"</filter>";
        filter += @"</filter>";

        // Get their queries
        string FetchXmlUserViews = @"<fetch mapping='logical'>
                            <entity name='userquery'>
                                <all-attributes/>    
                                {0}
                            </entity>
                            </fetch>";
                            
        EntityCollection userViews = service.RetrieveMultiple(new FetchExpression(string.Format(FetchXmlUserViews, filter)));

        Console.WriteLine("Found " + userViews.Entities.Count + " views");

        if (userViews.Entities.Count > 0)
        {
            foreach (Entity userQuery in userViews.Entities)
            {
                foreach (string attributeToBeDeleted in attributesToBeDeleted)
                {
                    // Remove node from fetchxml
                    string attributeToBeAmended = "fetchxml";
                    userQuery[attributeToBeAmended] = RemoveNodeFromAttribute(userQuery, attributeToBeDeleted, attributeToBeAmended);

                    // Remove node from layoutxml
                    attributeToBeAmended = "layoutxml";
                    userQuery[attributeToBeAmended] = RemoveNodeFromAttribute(userQuery, attributeToBeDeleted, attributeToBeAmended);
                }

                // Update the view
                try
                {
                    service.Update(userQuery);
                    Console.WriteLine("Attributes removed from view " + userQuery["name"]);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error for user " + user["fullname"] + " on view " + userQuery["name"]);
                    Console.WriteLine(ex.Message);
                    Console.WriteLine("\n");
                }
            }
        }
    }
    catch (Exception ex)
    {
        if (!ex.Message.Contains("no roles are assigned to user"))
        {
            Console.WriteLine("Error for user " + user["fullname"]);
            Console.WriteLine(ex.Message);
        }
    }
}

public EntityCollection GetUsers(OrganizationServiceProxy service)
{
    EntityCollection results = service.RetrieveMultiple(new QueryExpression
    {
        EntityName = "systemuser",
        ColumnSet = new ColumnSet("fullname", "firstname", "lastname", "domainname"),
        Criteria = new Microsoft.Xrm.Sdk.Query.FilterExpression
        {
            FilterOperator = LogicalOperator.And,
            Conditions =
                {
        new Microsoft.Xrm.Sdk.Query.ConditionExpression
        {
            AttributeName = "lastname",
            Operator = Microsoft.Xrm.Sdk.Query.ConditionOperator.Equal,
            Values = { "Walker" }
        },
        new Microsoft.Xrm.Sdk.Query.ConditionExpression
        {
            AttributeName = "isdisabled",
            Operator = Microsoft.Xrm.Sdk.Query.ConditionOperator.Equal,
            Values = { false }
        }

    },
        }
    });

    return results;

}


public String RemoveNodeFromAttribute(Entity userQuery, string attributeToBeDeleted, string nameOfAttributeToBeAmended)
{
    string xpath;
    string attributeToBeAmended = userQuery[nameOfAttributeToBeAmended].ToString();

    if (nameOfAttributeToBeAmended == "fetchxml")
    {
        xpath = @"//attribute[@name='" + attributeToBeDeleted + "']";
    }
    else
    {
        xpath = "//cell[@name='" + attributeToBeDeleted + "']";
    }

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(attributeToBeAmended);

    XmlNode root = doc.DocumentElement;
    XmlNode node = root?.SelectSingleNode(xpath);

    if (node != null)
    {
        node.ParentNode?.RemoveChild(node);
    }

    return doc.InnerXml;
}

public EntityMetadata GetEntityMetadata(string entityName, IOrganizationService service)
{
    RetrieveEntityRequest retrieveRequest = new RetrieveEntityRequest
    {
        LogicalName = entityName
    };

    try
    {
        Console.WriteLine("Looking for entity called " + entityName);
        RetrieveEntityResponse retrieveEntityResponse =
            (RetrieveEntityResponse)service.Execute(retrieveRequest);

        return retrieveEntityResponse.EntityMetadata;
    }
    catch (Exception e)
    {
        Console.WriteLine("GetEntityMetadata found no data");
        Console.WriteLine(e.Message);
        return null;
    }
}

 

Posted in Dynamics CRM | Tagged | Leave a comment

Leave a Reply