Thursday, August 20, 2015

How To Fix Workplace And Resource Center Icons In CRM 2015.1

CRM 2015 Update 1 removed the workplace and Resource Center from the site map.  It also replaced all the existing icons that were still in use.  By default this gives a very poor looking sitemap:
image
Here is how to update the icons to give a much more polished look:
image
  1. Create or find the icons that you want to use.  The new CRM Icons are 85x71, so you’ll want to follow that standard for optimal display.  (You can download my icons here.  I made them myself.  Feel free to donate using the Donate button on the right.)
  2. Upload the icons as web resources (The XrmToolBox has a great plugin for this, or you can do it manually).  Record the name you give for each icon.
  3. Download and run the XrmToolBox.
  4. Open the SiteMap Editor plugin, and login to the CRM instance to be updated.
  5. Click the “Load SiteMap” button to load the site map from CRM.
  6. -Optional- Click “Save SiteMap” to make a backup incase the changes made need to be reverted.
  7. Click on the “Area (Workplace)” node and update the Icon path to the path of the appropriate web resource. image
  8. Click Save.
  9. Click on the “Area (ResourceCenter)” node and update the Icon path to the path of the appropriate web resource.
  10. Click Save.
  11. Click “Update SiteMap” to push the changes.
  12. Stop staring at the old nasty blown up 32x32 icons in your SiteMap.

Monday, July 13, 2015

How To Display A Web Resource In CRM 2015 Without Loosing Column Width

I was recently adding JavaScript to display an iFrame when the fields required by the web resource had been populated on the form.  I noticed that the width of the iFrame was getting squished when it was being displayed:

Default:
Before


After calling setVisible():
After


It took some time stepping through the CRM libraries to figure out what was going on, but I believe it to be a bug within CRM (having the same issue?  Upvote this ticket!)  By default, CRM will attempt to update the CSS Table-Layout of the control when setting it’s visibility.  The fix is unsupported, but resolves the re-sizing issue.  After setting the visibility, check to see if the control is an iFrame, and the visibility is true.  If so, update the css of the parent table to be “fixed” *UPDATE 7/14/2015* Also need to set the height if there is a data-height attribute specified.

if (controlName.indexOf("IFRAME") >= 0 && visibility) {
    // https://connect.microsoft.com/dynamicssuggestions/feedback/details/1541195
    // THIS IS A FIX FOR A BUG WITH CRM WHEN SETTING IFRAMES AS VISIBLE.
    var ctrl = $("#" + controlName + "_d");
    ctrl.parents("table").last().css("table-layout", "fixed");
    var height = ctrl.attr("data-height");
    if (height) {
        // Set the height if defined
        ctrl.css("height", height + "px");
        ctrl.css("padding-bottom", "0");
    }
}

Friday, April 24, 2015

How To Ignore Mismatch Processor Architecture Warnings In Unit Test Assemblies With Faked Assemblies

I was attempting to cleanup the warnings in our solution build and ran into this one:
There was a mismatch between the processor architecture of the project being built "MSIL" and the processor architecture of the reference "Microsoft.TeamFoundation.WorkItemTracking.Client.DataStoreLoader", "x86". This mismatch may cause runtime failures. Please consider changing the targeted processor architecture of your project through the Configuration Manager so as to align the processor architectures between your project and references, or take a dependency on references with a processor architecture that matches the targeted processor architecture of your project. [C:\_Dev\Current Sprint\Xrm.Common.Tests\obj\Debug\Fakes\xrmt\f.csproj]    Xrm.Common.Tests
Researching the error on the web I found that I should be able to use the ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch Project property to ignore the warning.  I added the property to my Unit Test project and it didn't do anything.  After searching for another 20 minutes I ended up re-reading the warning, and noticed that it was referencing a csproj file other than the one that VS was showing as containing the warning. 
A couple more tests and I finally found that you can include MSBuild properties in the Fakes csproj by using the Compilation Element:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
  <Assembly Name="Xrm.Common"/>
  <Compilation>
    <Property Name="ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch">None</Property>
  </Compilation>
</Fakes>

Problem Solved!

Now on to clearing out the rest of the warnings...

Friday, February 20, 2015

How to Import a Solution With a Custom Action and a Plugin Step Registered Against the Custom Action in CRM 2013 SP 1 UR 1

This is a very specific bug that was fixed with CRM 2013 SP1 UR 2.  But if you aren't there yet, here is the issue and the steps to fix it:

Issue:

Attempting to import a Solution with a Plugin Step(s) registered against Custom Action results in the following import error: "sdkmessage with Id = {Guid} Does Not Exist"

Cause:

When CRM 2013 SP1 UR 1 (could be earlier versions as well) imports a Custom Action it is imported as a workflow and the XML in the customizations.xml file looks something like this:

  <Workflows>
    <Workflow WorkflowId="{cf3d243d-e065-4da1-b882-adfecdb6fe9d}" Name="DDLCustomAction">
      <XamlFileName>/Workflows/DDLCustomAction-CF3D243D-E065-4DA1-B882-ADFECDB6FE9D.xaml</XamlFileName>
      <Type>1</Type>
      <Subprocess>0</Subprocess>
      <Category>3</Category>
      <Mode>0</Mode>
      <Scope>4</Scope>
      <OnDemand>1</OnDemand>
      <TriggerOnCreate>0</TriggerOnCreate>
      <TriggerOnDelete>0</TriggerOnDelete>
      <AsyncAutodelete>0</AsyncAutodelete>
      <SyncWorkflowLogOnFailure>1</SyncWorkflowLogOnFailure>
      <StateCode>1</StateCode>
      <StatusCode>2</StatusCode>
      <CreateStage>40</CreateStage>
      <RunAs>1</RunAs>
      <SdkMessageId>{63d9c5b6-74b8-e411-80ef-000d3a100fad}</SdkMessageId>
      <UniqueName>DDLCustomAction</UniqueName>
      <IsTransacted>1</IsTransacted>
      <IntroducedVersion>1.0</IntroducedVersion>
      <IsCustomizable>1</IsCustomizable>
      <PrimaryEntity>none</PrimaryEntity>
    </Workflow>
  </Workflows>


Notice the SdkMessageId and UniqueName.  When this gets imported, CRM only uses the UniqueName, it doesn't use the SDK Message Id, it actually creates a new SDK Message Id.  This is the bug.  If you're just importing a Custom Action, no issues, but when you attempt to import a Plugin Step registered against the Custom Action, the Id's won't match up:

  <SdkMessageProcessingSteps>
    <SdkMessageProcessingStep Name="Xrm.Plugins.Merge: new_DDLCustomAction of  any Entity" SdkMessageProcessingStepId="{0f4fdb54-72b8-e411-9c1a-005056827e6d}">
      <PluginTypeName>Xrm.Plugins.Merge, Xrm.Plugins, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0c1faab69f9562d5</PluginTypeName>
      <AsyncAutoDelete>0</AsyncAutoDelete>
      <InvocationSource>0</InvocationSource>
      <Mode>0</Mode>
      <Rank>1</Rank>
      <SdkMessageId>{63d9c5b6-74b8-e411-80ef-000d3a100fad}</SdkMessageId>
      <EventHandlerTypeCode>4602</EventHandlerTypeCode>
      <Stage>40</Stage>
      <IsCustomizable>1</IsCustomizable>
      <IsHidden>0</IsHidden>
      <SupportedDeployment>0</SupportedDeployment>
      <IntroducedVersion>1.0</IntroducedVersion>
      <SdkMessageProcessingStepImages />
    </SdkMessageProcessingStep>
  </SdkMessageProcessingSteps>


Notice there is no UniqueName, just the SDK Message ID.  This is why the "sdkmessage with Id = {Guid} Does Not Exist" error is getting created. 

Workaround:

There is a workaround if you're unable to upgrade to UR2, update the GUIDs in the solution to match the newly created Id.
  1. Import just the Custom Action Process if it doesn't already exists. This will create the SDK Message Id.
    image


  2. Get the newly created Id.  (There are multiple ways, this is the easiest universal way I could think of) do an advanced Find on Sdk Messages, where the Category Name is "CustomOperation".
    image


  3. Locate the Custom Action that you want, and use the debugger tools (F12) to get the actual html itself for the action.  In it you can find the GUID: 
    <a tabindex="0" title="new_DDLCustomAction" class="ms-crm-List-Link" id="gridBodyTable_primaryField_{8DEDACA9-F32C-4F90-BF6E-A87464C70F72}_1" href="#" target="_self">new_DDLCustomAction</a>

  4. Do a search and replace in the customizations.xml for the current SdkMessageId listed (in my example it would be "63d9c5b6-74b8-e411-80ef-000d3a100fad") and replace all with the newly created Guid ("8DEDACA9-F32C-4F90-BF6E-A87464C70F72").


  5. Rezip your solution.

That's it, your import will work just fine.  The biggest headache is you'll need to do this for each and every org that you import it in, making it a nasty fix if you have multiple environments / org.

Thursday, November 6, 2014

How to Prevent FaultException<TDetail> From Eating Your Stack Trace

Eating 
Any action that is performed by the CRM SDK that results in an exception on the CRM server, is returned as a System.ServiceModel.FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> exception.  The interesting problem that this creates, is the FaultException<TDetail> exception class overrides the ToString() method of the Exception class, eating the entire stack trace.  So given the following hypothetical program:
public void Test()
{
    using (var service = GetOrganizationServiceProxy())
    {
        try
        {
            return service.GetEntity<SystemUser>(Guid.NewGuid());
        }
        catch (FaultException<OrganizationServiceFault> ex)
        {
            Log(ex);
            throw ex;
        }
    }
}

The log would only contain the following text:


System.ServiceModel.FaultException`1[Microsoft.Xrm.Sdk.OrganizationServiceFault]: systemuser With Id = f483d6ed-ed8c-4d5d-b6e4-145e8d8ec993 Does Not Exist (Fault Detail is equal to Microsoft.Xrm.Sdk.OrganizationServiceFault).

Which doesn’t contain the stack trace at all. For a simple program like this, it’s pretty easy to figure out where the exception occurred, but in an enterprise level application with hundreds or thousands of sdk calls, it becomes nearly impossible. After looking at the base Microsoft Exception class implementation I came up with the FullStackTraceException class:

/// <summary>
/// Provides a method for handling any exception types that "eat" the stacktrace in it's implementation of ToString()
/// </summary>
[Serializable]
public class FullStackTraceException : Exception
{
    private readonly Exception _exception;

    private FullStackTraceException(Exception ex)
    {
        _exception = ex;
    }

    public static Exception Create(Exception exception)
    {
        return CreateInternal((dynamic)exception);
    }

    private static Exception CreateInternal(Exception exception)
    {
        return exception;
    }

    public static FullStackTraceException Create<TDetail>(FaultException<TDetail> exception)
    {
        return new FullStackTraceException(exception);
    }

    private static FullStackTraceException CreateInternal<TDetail>(FaultException<TDetail> exception)
    {
        return new FullStackTraceException(exception);
    }

    public override String ToString()
    {
        String s = _exception.ToString();

        if (_exception.InnerException != null)
        {
            s = String.Format("{0} ---> {1}{2}   --- End of inner exception stack trace ---{2}", s, _exception.InnerException, Environment.NewLine);
        }

        string stackTrace = _exception.StackTrace;
        if (stackTrace != null)
        {
            s += Environment.NewLine + stackTrace;
        }

        return s;
    }
}



It uses static factories to determine if the exception is of the FaultException<TDetail> type or not.  If it isn’t, the exception is returned unchanged.  If it is, a new version of FullStackTraceException is returned, with an overridden ToString method that includes the stack trace as it would normally appear.  So if the first example was changed to:


public void Test()
{
    using (var service = GetOrganizationServiceProxy())
    {
        try
        {
            return service.GetEntity<SystemUser>(Guid.NewGuid());
        }
        catch (FaultException<OrganizationServiceFault> ex)
        {
            Log(FullStackTraceException.Create(ex));
            throw ex;
        }
    }
}

The ToString() call in Log would return


System.ServiceModel.FaultException`1[Microsoft.Xrm.Sdk.OrganizationServiceFault]: systemuser With Id = f483d6ed-ed8c-4d5d-b6e4-145e8d8ec993 Does Not Exist (Fault Detail is equal to Microsoft.Xrm.Sdk.OrganizationServiceFault).

Server stack trace:
   at System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)


Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at Microsoft.Xrm.Sdk.IOrganizationService.Retrieve(String entityName, Guid id, ColumnSet columnSet)
   at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.RetrieveCore(String entityName, Guid id, ColumnSet columnSet)
   at Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy.Retrieve(String entityName, Guid id, ColumnSet columnSet)
   at DLaB.Utilities.ClientSideOrganizationService.Retrieve(String entityName, Guid id, ColumnSet columnSet) in c:\TFS\DLaB.Utilities\ClientSideOrganizationService.cs:line 88
   at DLaB.Extensions.GetEntity[T](IOrganizationService service, Guid id, ColumnSet columnSet) in c:\TFS\DLaB.Extensions.cs:line 1214
   at DLaB.Extensions.GetEntity[T](IOrganizationService service, Guid id) in c:\TFS\DLaB.Extensions.cs:line 1185
   at DLaB.Example.Test() in c:\TFS\Example.cs:line 7

Which includes the appropriate client side stack trace, allowing for much simpler debugging!  (In actual use, the FullStackTraceException.Create(ex) would occur in the Log method itself to keep the code DRY.)



Enjoy!

Wednesday, August 27, 2014

How To Make It Easier To Create A Plugin For The XrmToolBox

The XrmToolBox is an awesome collection of tools for CRM.  If you haven’t used it before, stop reading this and download it now.  Go ahead.  I’ll wait.

Welcome back…

To date I’ve created 2 plugins for XrmToolBox, Option Set Converter and Early Bound Generator.  While developing them I discovered that there really needed to be a base plugin control to make it simpler to perform CRM calls and perform asynchronous calls.  The base class that I ended up with made development a lot easier for me, and could be very helpful for the rest of the community, so I’m going to highlight it now, the PluginUserControlBase.

UpdateConnection

The first improvement the base makes is simply to implement the required IMsCrmToolsPluginUserControl interface.  The documentation shows UpdateConnection has to check the string name being being passed in to determine which method to call:

public void UpdateConnection(IOrganizationService newService,
                     string actionName = "",
                     object parameter = null)
{
    Service = newService;

    if (actionName == "WhoAmI")
    {
        ProcessWhoAmI();
    }
}


This is a really painful implementation since each method that you need a connection to CRM, will require an if statement for the method to actually call.  It also allows for late bound exceptions if the action name is updated.  Here is my implementation:



public virtual void UpdateConnection(IOrganizationService newService, ConnectionDetail detail, string actionName, object parameter)
{
    Service = newService;
    ConnectionDetail = detail;
    OnConnectionUpdated(new ConnectionUpdatedEventArgs(newService, detail));
    if (actionName == String.Empty)
    {
        return;
    }

    MethodInfo method;
    if (parameter == null)
    {
        method = GetType().GetMethod(actionName);
        if (method == null)
        {
            throw new Exception("Unable to find method " + GetType().Name + "." + actionName);
        }
        method.Invoke(this, null);
    }
    else
    {
        var externalCaller = parameter as ExternalMethodCallerInfo;
        if (externalCaller == null)
        {
            method = GetType().GetMethod(actionName, new[] {parameter.GetType()});
            if (method == null)
            {
                throw new Exception("Unable to find method " + GetType().Name + "." + actionName);
            }
            method.Invoke(this, new[] {parameter});
        }
        else
        {
            externalCaller.ExternalAction();
        }
    }
}


The Service and ConnectionDetail get set, then an Event is raised for any plugins that need to know when the connection is updated.  Then, rather than having an if statement, reflection is used to lookup the correct method to execute.  The ExternalMethodCallerInfo check also allows an action to be used, incase a method needs to be called with more than one parameter.



 



ExecuteMethod



The documentation shows a button clicked event handler having to check if the Service is populated, and then passing in the string name of the method to call:



private void BtnWhoAmIClick(object sender, EventArgs e)
{
    if (Service == null)
    {
        if (OnRequestConnection != null)
        {
            var args = new RequestConnectionEventArgs {ActionName = "WhoAmI", Control = this};
            OnRequestConnection(this, args);
        }
    }
    else
    {
        ProcessWhoAmI();
    }
}


This is also really annoying, having to check each time if the service is populated, and calling it differently depending on it being populated or not.  If the plugin has 3 or 4 actions that require the Crm Service, the OnRequestConnection logic will have to be duplicated multiple times.  I created an ExecuteMethod method to do all of that automagically, using actions rather than strings:



/// <summary>
/// Checks to make sure that the Plugin has an IOrganizationService Connection, before calling the action.
/// </summary>
/// <param name="action"></param>
public void ExecuteMethod(Action action)
{
    if (Service == null)
    {
        var name = action.GetMethodInfo().Name;
        if (name.Contains("__"))
        {
            throw new ArgumentOutOfRangeException("action",
                @"The Action of an Execute Method must not be a lambda.  Use the ExecuteAction(action, parameter) Method.");
        }

        OnRequestConnection(this, new RequestConnectionEventArgs
        {
            ActionName = action.GetMethodInfo().Name,
            Control = this
        });
    }
    else
    {
        action();
    }
}

/// <summary>
/// Checks to make sure that the Plugin has an IOrganizationService Connection, before calling the action.
/// </summary>
/// <param name="action"></param>
/// <param name="parameter"></param>
public void ExecuteMethod<T>(Action<T> action, T parameter)
{
    var caller = parameter as ExternalMethodCallerInfo;
    if (Service == null)
    {
        if (caller == null)
        {
            OnRequestConnection(this, new RequestConnectionEventArgs
            {
                ActionName = action.GetMethodInfo().Name,
                Control = this,
                Parameter = parameter
            });
        }
        else
        {
            OnRequestConnection(this, new RequestConnectionEventArgs
            {
                ActionName = "Recaller",
                Control = this,
                Parameter = parameter
            });
        }
    }
    else if (caller == null)
    {
        action(parameter);
    }
    else
    {
        caller.ExternalAction.Invoke();
    }
}

private void Recaller(ExternalMethodCallerInfo info)
{
    info.ExternalAction.Invoke();
}


WorkAsync



The final improvement that I wanted to highlight was WorkAsync methods which eliminate the need to have duplicated code to create a background worker and update statuses: 



WorkAsync(
    "Message to Display when Starting a CRM Call",
    (w, e) => { /* Work To Do Asynchronously */ },
         e => { /* Cleanup when work has completed */ });


The end result is a much much more simplified plugin:



public partial class SampleTool : PluginUserControlBase
{


    private void BtnWhoAmIClick(object sender, EventArgs e)
    {
        ExecuteMethod(ProcessWhoAmI);
    }

    private void ProcessWhoAmI()
    {
        WorkAsync("Retrieving your user id...",
            (e) => // Work To Do Asynchronously
            {  
                var request = new WhoAmIRequest();
                var response = (WhoAmIResponse) Service.Execute(request);

                e.Result = response.UserId;
            },
            e =>  // Cleanup when work has completed
            {  
                MessageBox.Show(string.Format("You are {0}", (Guid)e.Result));
            }
            );
    }


    private void BtnCloseClick(object sender, EventArgs e)
    {
        base.CloseToolPrompt();
    }
}


Interested in creating a plugin for the XrmToolBox?  Just download the Early Bound Generator and reference the DLab.XrmToolboxCommon dll.



Happy Coding!

Monday, March 17, 2014

How To Interpret AccessRights Numbers In CRM Security Exceptions

When a user attempts to preform an action in CRM there are two potential types of security issues that could arise.
  1. The user’s access level for the privilege required to perform the action is none e.g., attempting to share a contact:
    Contact_No_Share
  2. The user has an access level for the privilege required to perform the action, but it isn’t generous enough e.g., attempting to share a contact with a user outside of the business unit:
    Contact_Org_Share
In either case, if action requires a single privilege, and the user doesn’t have the privilege (or doesn’t have high enough access rights for the privilege), you’ll get an error like the following:
CrmException:
 SecLib::AccessCheckEx failed. Returned hr = -2147187962,
 ObjectID: abe86dd6-d7aa-e311-ade6-cfcd73da4a68,
 OwnerId: db2f6110-1a9d-df11-9d85-005056bb728e, 
 OwnerIdType: 8 and
 CallingUser: edce293b-789d-e311-950c-ed9561bb126a.

 ObjectTypeCode: 4201,
 objectBusinessUnitId: 966fe7c8-89c1-de11-853f-005056bb728e,
 AccessRights: prvShareActivity


This gives combined with the Security Role UI to Privilege Mapping gives all the information required to determine the security rights required.  The Calling user, object, object’s type, object’s owner, object’s owner type, and object’s business, as well as the Access Right required.  Sometimes though, the error looks like this:


CrmException:
 SecLib::AccessCheckEx failed. Returned hr = -2147187962,
 ObjectID: abe86dd6-d7aa-e311-ade6-cfcd73da4a68,
 OwnerId: db2f6110-1a9d-df11-9d85-005056bb728e, 
 OwnerIdType: 8 and
 CallingUser: edce293b-789d-e311-950c-ed9561bb126a.

 ObjectTypeCode: 4201,
 objectBusinessUnitId: 966fe7c8-89c1-de11-853f-005056bb728e,
 AccessRights: 262153


In this case the Access Rights is a little more cryptic.  This is what happens when an operation requires multiple privileges, e.g. Share and Append.  To “decrypt” it you’ll need to know how enum flags work, and what the actual powers of two are in this case.


Below are the values as taken from the Microsoft.Crm.Sdk.Messages.AccessRights Enum

Name Value Binary Value Description
ReadAccess 1 0001 Specifies the right to read the specified type of object.
WriteAccess 2 0010 Specifies the right to update (write to) the specified object.
AppendAccess 4 0100 Specifies the right to append the specified object to another object.
AppendToAccess 16 0001 0000 Specifies the right to append another object to the specified object.
CreateAccess 32 0010 0000 Specifies the right to create an instance of the object type.
DeleteAccess 65536 0001 0000 0000 0000 0000 Specifies the right to delete the specified object.
ShareAccess 262144 0100 0000 0000 0000 0000 Specifies the right to share the specified object.
AssignAccess 524288 1000 0000 0000 0000 0000 Specifies the right to assign the specified object to another security principal.


In the error above, 262153 is

0100 0000 0000 0000 1001

But wait, There is no Access Right with a binary value of 1000.  Since I’ve been unable to find any other documentation, my guess is that something on our installation (that was an upgrade from a CRM 4.0 server) still has a reference to the CRM 4.0 enum values in which AppendToAccess is actually 8 (1000) rather than 16 (0001 0000).


Using that assumption and the table above, 2621534 is made up of ShareAccess, AppendToAccess, and ReadAccess.  This means the user has to have sufficient Access Levels for Share, Append, and Read privileges of the entity, in order to successfully perform the requested operation.