Monday, 20 January 2014

Turn off IE Compatibility mode in MS Dynamics CRM 2011 or why indexOf doesn't work.

We'd been having navigation issues in MS Dynamics CRM 2011 for a while: Mouse scrolling wouldn't work and double clicking was necessary to select anything from a drop down menu. The issue would disappear if IE was using IE 9 document standards. The problem was that IE would "downgrade" itself to IE 7.

Somewhat bizarrely this wasn't occurring everywhere, it was just occurring in one of our environments causing loads of headaches for everybody. The testers were annoyed that they had, effectively limited functionality and kept forgetting to set document standards to IE 9 via developer tools. We weren't sure why this was happening in only one environment.

Things came to a head last week when I used a variation of my function getAllUserRoles and due to indexOf only being supported on IE > 9, it was working everywhere apart from on this environment. 

It turns out that there is a setting, introduced in UR12, called Load Pages in the most recent version of Internet Explorer, which modifies the headers so that the pages are rendered in the most recent version of IE.

Navigate to Settings -> Administration -> System Settings -> Customization.


Ticking this setting, will ensure that the default doc standards will be used and the heat death of the universe will have been staved off for another tick or two.

Monday, 13 January 2014

Get user's roles in MS Dynamics Crm 2011/2013

In MS Dynamics CRM 2011/13 there exists a JavaScript function to get the user's roles (Xrm.Page.context.getUserRoles) unfortunately, there are two problems with this function:
  1. It does not retrieve all roles, just those directly assigned to the user. In other words, if the user is a member of a team and that team has a role, this function will not find it.
  2. It returns the guids of the user roles rather than the names.

So in order to get around those shortcomings I wrote this function, which will return an array with all roles the user has including team roles. Note that this function is will only work in IE9+ as neither IE7 nor IE8 support indexOf, something that I will discuss in an upcoming post. It seems to work fine in Chrome (31) and Firefox (25).

function getAllUserRoles()
{
    var guid = "[A-z0-9]{8}-[A-z0-9]{4}-[A-z0-9]{4}-[A-z0-9]{4}-[A-z0-9]{12}";

    var serverUrl = Xrm.Page.context.getClientUrl();         
    var userId = Xrm.Page.context.getUserId();
    userId = userId.match(guid);
             
    var teamQuery = "TeamMembershipSet?$select=TeamId&$filter=SystemUserId eq guid'"+userId +"'";
    var teamRoleQuery = "TeamRolesSet?$select=RoleId&$filter=";
    var roleQuery = "RoleSet?$select=Name&$filter=";
    
    var teams = makeRequest(serverUrl,teamQuery,0);
    
    teamRoleQuery = composeQuery(teamRoleQuery,"TeamId",teams);
    var teamRoles = makeRequest(serverUrl,teamRoleQuery,1);

    userRoles = Xrm.Page.context.getUserRoles();
 
    if(userRoles != null){
     for(var i =0; i< userRoles.length;i++){
     teamRoles.push(userRoles[i].match(guid));
     }
    }

    roleQuery = composeQuery(roleQuery,"RoleId",teamRoles);
    var roles = makeRequest(serverUrl,roleQuery,2);
    
    return roles;
}

function makeRequest(serverUrl, query, type)
{

    var oDataEndpointUrl = serverUrl + "/XRMServices/2011/OrganizationData.svc/";
    oDataEndpointUrl += query;

    var service = GetRequestObject();

    if (service != null)
    {

        service.open("GET", oDataEndpointUrl, false);
        service.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        service.setRequestHeader("Accept", "application/json, text/javascript, */*");
        service.send(null);

        var retrieved = $.parseJSON(service.responseText).d;

        var results =  new Array();
                               
        switch (type)
        {

        case 0:
        for (var i=0; i < retrieved.results.length;i++){
          results.push(retrieved.results[i].TeamId);
          }
          break;
        case 1:
        for (var i=0; i < retrieved.results.length;i++){
          results.push(retrieved.results[i].RoleId);
          }
          break;

        case 2:
        for (var i=0; i < retrieved.results.length;i++){
          if(results.indexOf(retrieved.results[i].Name)==-1){
           results.push(retrieved.results[i].Name);
           }
          }
          break;
         }                                     
        return results;
     }
return null;
}

function GetRequestObject()
{

    if (window.XMLHttpRequest)
    {
        return new window.XMLHttpRequest;
    }
    else
    {
        try
        {
            return new ActiveXObject("MSXML2.XMLHTTP.3.0");
        }
        catch (ex)
        {
            return null;
        }
    }
}

function composeQuery (queryBase, attribute, items )
{
 if (items != null){
     for(var i=0; i < items.length; i++){
      if (i==0)
      {
       queryBase += attribute + " eq (guid'"+items[i]+"')";
      }
      else
      {
       queryBase += " or " + attribute + " eq (guid'"+items[i]+"')";
      }
     }
    }
 return queryBase;
}

 
I think one of these days I will get the hang of JavaScript but I still find it really obnoxious to work with.

Monday, 6 January 2014

Permissions issues affecting Views in Ms Dynamics CRM 2011

A few weeks back we had an interesting issue come up.

We have a custom entity called centre with a 1:N to another custom entity called transaction (wen_trans) [transaction has a relationship with experience (wen_exp)] and in the centre record we display the related transactions. Except that in this case it wasn't showing anything and there was an interesting error message for a particular set of users:



To make matters interesting, using Advanced Find revealed all the (transaction) records that should have been displayed in the associated view and not only that, but the users were able to open the records, so I can't have been permissions right?

This is the error message in the crm logs:
Principal user (Id=666dd230-5b57-e311-8eab-0050568e2c11, type=8) is missing prvReadwen_exp privilege (Id=1a23a4a4-6f81-4410-9df8-d6a7a052ae72), ErrorCode: -2147220960, InnerException: Microsoft.Crm.CrmSecurityException: Principal user (Id=666dd230-5b57-e311-8eab-0050568e2c11, type=8) is missing prvReadwen_exp privilege (Id=1a23a4a4-6f81-4410-9df8-d6a7a052ae72)
· MessageProcessor fail to process message 'RetrieveMultiple' for 'wen_trans'.
So we modified the roles temporary giving read access to experience and the issue was sorted but why was the issue occurring in the first place?

Well, it turns out that the trace log was right all along and we needed read access to the experience entity as the associated view for transaction had some columns that were displaying data from the experience entity.

This is one of those issues that are very obvious once you know the answer but pretty tricky to find said answer, so hope it helps.

Monday, 30 December 2013

Actions (Custom Messages) in MS Dynamics CRM 2013

Actions are another new feature in MS Dynamics CRM 2013. They are an intriguing new feature, which I must admit don't see much of a use case for right now.

In essence, they are a custom message, something like Create, Update, etc... but the actions that take place when the custom message is invoked are defined as a sort of Workflow, which allows custom code via Custom Workflow activities.

It then it gets a bit weird in that this can only be invoked via code. A potential workaround is to use a workflows with a custom Workflow activity or presumably, JavaScript hooked up to a button or other event.

I thought I would try to create a sample action. The action will create a task related to a Stamp (a custom entity), which is a mandatory input to the request. There is another non-mandatory input variable, if set to false, the action will not trigger. Finally, there will be an output variable, which will effectively confirm whether it's been actioned ...

At any rate, this is how one would create such an action. Navigate to Settings -> Processes -> New

I created this action to be global, it can be limited to an entity.
I added a few arguments and there are a few things that are annoying about this. The first one is that unless the Entity Type is set for Entity or EntityReference, you will not be able to use the argument, at least not from the GUI, so this limits a bit what can be done. Although I suspect no such limitations apply to code ... The second annoying thing is best explained by the next screenshot:
Essentially, 0 will be false, everything else is True, if the field is boolean, not sure why not just have True or False?



Make sure that the action is activated before calling it from code.

This is how you would call it:
private static void supadupaaction(IOrganizationService service, EntityReference target, Entity stamp, bool carryOut=true)
        {
            var parameters = new ParameterCollection();
            parameters.Add(new KeyValuePair<string, object>("Target", target));
            parameters.Add(new KeyValuePair<string, object>("RealTarget", stamp));
            parameters.Add(new KeyValuePair<string, object>("AreYouSure", carryOut));

            OrganizationRequest request = new OrganizationRequest()
            {
                RequestName = "new_SupaDupaAction",
                Parameters = parameters
            };

            var result = (OrganizationResponse)service.Execute(request);

            foreach (var item in result.Results)
            {
                Console.WriteLine("Result - Name: {0}. Value: {1}", item.Key, item.Value);
            }
         }
Target is an entity reference to the, yep, target entity, i.e. the entity against which the action/custom message is running. Stamp is the instance of the Stamp entity against which the task will be created.

Note that the RequestName is not all lower case, not sure if this is only for actions or a more generic shift in MS Dynamics CRM 2013 in general.

Frankly, I need to think what the benefit of this is/can be, because with this noddy example I am seeing none. I guess defining a type of operation can be quite handy as stuff can happen pre and post operation, e.g. register plug-ins to do this, so nothing ground breaking but perhaps simplifies things ...

Feel free to enlighten me...

Friday, 27 December 2013

Business Rules in MS Dynamics CRM 2013

I finally gave MS Dynamics CRM 2013 a whirl last night and found an interesting new feature: Business Rules.

Not sure what the exact definition is, but the idea is that these rules can be used to modify the form without the need to resorting to JavaScript. This is not only good because it means that non-developers can do it but also because it means no more JavaScript !!!!!, well maybe.

This is one of the myriad of features in MS Dynamics CRM that are always mentioned as non-developer feature, but in reality it's always the developers that work with these, but I digress.

Business rules seem to be entity wide, which is a bit of a shame as it would be handy to have business rules that apply to various entities, not entirely sure I see a reasonable business case, but, generally, the fewer limitations the better.

At any rate, I created a new Entity, added a few fields and edited the form, from where I added a Business rule.

The rule will make the Class field mandatory if the Cost field is greater than £10, for instance if this were an expense you could make a explanation/justification field become mandatory when a certain threshold was exceeded.


The conditions, as well as actions, can be chained, e.g. in the invoice example, make the explanation/justification field become mandatory if it's above a certain threshold AND it's a certain type of expense only. 


Not 100% what the description is supposed to do, as it does not appear to be displayed anywhere, probably it's just another field that will never be filled in.

An immediate downside is that in this example, you would need another business rule to set it back to not required if the field is changed to a value below £10.

Anyway, here's the result:


Another downside is the lack of OR on the rules. Is it really that complicated for non technical staff to understand the or operation? or is it complex for Microsoft to implement? I don't know but there is an or feature in Advanced Find. At any rate, the lack of or in the Check Condition Actions was really annoying on Dialogs and Workflows in 2011 and this seems to be still missing in 2013.

It's of interest to note that for numeric fields, simple formulas can be used instead of fixed values, no regex for string fields though :(, again not surprising given the audience, but it would've been nice.

Also the comparison can be made against another field in the entity.

The range of actions is interesting:
  • Show Error Message - Yahoo!!! no more javascript validation (in your dreams, sunshine)
  • Set Field Value 
  • Set Business Required
  • Set Visibility
  • Lock or Unlock Field
I guess this feature is a typical MS Dynamics CRM feature, where it seems like a great idea in principle but then in practice it's too limited or cumbersome but for the most basic of tasks. Designed for Power Users, used and hated by developers, more of the same.

All in all not a bad feature, I just need to start working on some MS Dynamics CRM 2013 projects now :).

Thursday, 26 December 2013

Add/Remove Programs Product Icon with Wix

This is a very simple change that can help your application look that little bit more professional.

Just add a Add/Remove Programs Product Icon with Wix:

<Icon Id="ProductIcon" SourceFile="Icon\myicon.ico"/>    
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />

See What I mean:


You need to make sure that you store myicon.ico on a directory called Icon.

Friday, 20 December 2013

Get user group membership in Sharepoint from Powershell

A few days back I needed to find out whether the System account (SharePoint\System) was a member of a particular group and it turns out that this cannot be done from the GUI as it doesn't show up in the list of accounts that are members of that group so ... PowerShell to the rescue.

The following snippet will provide a list of users belonging to the desired group <Group Name>:
$site = Get-SPSite -WebApplication "<url>"
$mainsite = (Get-SPWeb -Site $site)[0]
$group = $mainsite.SiteGroups | ?{$_.Name -match "<Group Name>"}
foreach($user in $group.Users){$user}
As usual it needs to be run from the SharePoint PowerShell console or the Sharepoint PowerShell snap in needs to be loaded before running the above commands.

Monday, 25 November 2013

TFS Error - There is not enough space on the disk

I've been working on trying to automate our development workflow or in other words I've been banging my head against the TFS wall trying to set up CI.

I thought i had everything working when I encountered this little beauty for one of the ASP.NET projects:

The build server had over 20 GB of free space, so I was somewhat baffled by this error message, so I did what every self respecting IT professional does: I started bouncing stuff.

After I bounced, the build agent, build service and the server itself, all to no avail, I hit the interwebs where I found the solution.

This error has got nothing to do with disk space, it's related to tempory files not being deleted in this folder:
C:\Windows\Microsoft.net\Framework64\<version>\Temporary ASP.NET Files
I checked the folder and sure enough there was a subfolder with the name of one of the projects. I deleted this and the builds started to complete again.

Wednesday, 20 November 2013

ADFS - Turning Debug Tracing on

I was struggling last week with some ADFS issues and I decided to turn debug tracing on to see if it threw any light as to why it wasn't working, it didn't, the issue was somewhere else, but it might be useful for future reference, so here it goes:

1. Start the event viewer:
Start | Run | eventvwr
2. Disable ADFS event log:
3. Run the following command with elevated permissions (i.e. Run as Administrator):
wevtutil sl "AD FS 2.0 Tracing/Debug" /l:5
4. Enable ADFS event log.
5. Ensure that Analytic and Debug logs are enabled
  View | Show Analytic and Debug logs
6. Enjoy all the debug output goodness.

Friday, 15 November 2013

Exit codes in msi installers created with Wix

There was (is?) a bug in Wix that prevented successful creation of an SSL binding in IIS 7.5, so in order to get around this issue, I wrote a custom action to do this.

A failure in this custom action will not stop the installation*, which means that the exit code will be driven by the custom action failure, which will lead to the exit code being 1603 but the product installing, since the failure is relating to setting the certificate for a webpage, this actually has no effect as the newly installed website picks up the old certificate.

The problem was in our install scripts, where we check on the exit code to ascertain whether the installation was successful, which it wasn't as the exit code was 1603 but since the app was actually there this lead to a lot of confusion and head scratching.

Thought I'd share in case somebody does something as a stupid as I did.

*I've edited the custom action code so that it does not always return success now. There was a perfectly valid reason for the code to always return a success value and I will talk about it as soon as I find it.

Selected output from the install log file:

CustomAction UpdateBinding returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)
UpdateBinding. Return value 3.
INSTALL. Return value 3.

Product:  Portal -- Installation failed.
Windows Installer installed the product. Product Name: Portal. Product Version: 1.3.3.7. Product Language: 1033. Manufacturer: Yo! Ltd. Installation success or error status: 1603.