Request a topic or
contact an Arke consultant
404-812-3123
Microsoft Dynamics CRM 4.0

Arke Systems Blog

Useful technical and business information straight from Arke.

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Archive

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2024

Loading a CRM page from a post-build event

CRM uses NTLM authentication, so you can’t just pull down a page using any simple thing.

Luckily, “curl” is a nice command-line tool for loading webpages that supports NTLM.

The only tricky part is, not all builds support the “--ntlm” and ”-u :” features you need for NTLM to work.  The Win32 Generic 7.21.2 binary build at http://curl.haxx.se/download.html is working for me (the MSVC build supports ntlm but “-u :” silently doesn’t work. You can tell it failed because you get back a 401.)

So, I now put a copy of curl in my project’s references folder, and have added to my post-build events that bounce IIS:

SETLOCAL ENABLEDELAYEDEXPANSION
set URL=http://localcrm/orgname/loader.aspx

. . .

set loop=0
:TRYCURL
rem Load front page to get app pool started up again
set /a loop=%loop%+1
echo Loading site to initialize app pool %URL%
rem curl should use --ntlm -u : to pull user from the environment.
"$(SolutionDir)\references\curl\curl" --user-agent "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)" --location --location-trusted --ntlm -u : --silent --show-error -w"%%{http_code}" "%URL%"  > NUL
if !ERRORLEVEL! NEQ 0 GOTO CURLFAIL
goto DONE
:CURLFAIL
if %LOOP% LEQ 4 GOTO TRYCURLSLEEP
goto FAIL
:TRYCURLSLEEP
echo sleeping before retry
sleep 1
goto TRYCURL
:DONE
echo OK at %TIME%


Posted by David Eison on Sunday, November 28, 2010 2:14 AM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM API – Picklist details

Just thought I’d share a bug I ran into with everyone and hope it helps you avoid it.

I see plenty of code that sets picklist values.  You might do this in javascript, or in the CRM API.  Set a new value, submit the update, picklist value is changed. 

But, it’s easy to miss that picklist has two fields: a Name, and a Value.  Value is something boring like 2, name is something to show to the user, like “Critical”. 

You would think that writing code like this was great:

Picklist prop = source.Properties[attribute] as Picklist;
if (prop == null || prop.IsNull)
{
    return defaultvalue;
}
return prop.name;

However, a problem crops up when dealing with client code written by people who didn’t understand that the name field is important.  If you put that code into a plugin, you get passed straight the name + value that the API client specified – so you could run into javascript code that only changed the value, or into other CRM API code that only changed the value, and name can either be completely not set, or worse, set to an old previous value.

So, when writing server side code that handles data submitted by the client, it looks like you’ll need to only trust the value and ignore the name.  It’s possible you could audit your client code and make sure that everywhere a new value is set a new name is set too.. but one day somebody will find some code from another project, add it to yours, and your picklist handling will be wrong.

Happy Thanksgiving!


Posted by David Eison on Saturday, November 27, 2010 11:57 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and ViewState

Microsoft Dynamics CRM 4.0 doesn’t use ViewState, or Sessions.  Indeed, they are disabled in the web.config file.  This can be a bit of a surprise to an ASP.NET developer working on a custom page in the /ISV directory. 

Three possible solutions on ViewState:

1) Don’t use ViewState. 

This requires re-initializing any data with every postback.  One thing you can do is minimize postbacks.  You’ll see CRM does this in many places by encouraging the use of Javascript and popping extra windows to handle immediate-response things like filling in or validating a lookup field.  If you are displaying a grid, it will be empty after postback because it wasn’t re-populated from viewstate, so you need to repopulate it on every request – which means you also need to deal with the possibility that values may have changed due to another user editing records in the meantime.  Some simple strategies to start with are to refer to records by guids instead of by row numbers and to minimize updates to only the fields your user actually changed, and to code defensively.

2) You can enable viewstate for a particular page by adding it to the page directive at the top of the page:

<%@ Page . . . EnableViewState="true" . . .

Note that if you have a server cluster and use viewstate, you’ll either need to set a machine key in web.config or else disable viewstate validation with another Page directive:

<%@ Page . . . EnableViewState="true" EnableViewStateMac="false" %>

Disabling ViewStateMac means your users will be able to tamper with the viewstate, so just keep that concern in mind if you have custom permissioning rules in your app beyond CRM’s built in permissioning.

3) You can enable viewstate for all of your ISV pages by setting it up as its own app. 

Create a virtual directory under /ISV, point it to your pages, and give yourself a web.config that sets enableviewstate for your pages (and a machine key).  See, for example, this guide at xrmlinq.

Remember that if you’re going to use viewstate, you should keep an eye on viewstate size ; perhaps you don’t want to transfer a megabyte of serialized grid data with every page load.  You can see how big viewstate is by viewing source on your page, or by adding some code to your pages (If Request.IsLocal is true and DEBUG is defined, I tack on a label with the size from LosFormatter; see example code at scottonwriting ); but in general, if you use viewstate on a grid or listbox, you’re going to be storing a lot of data.  If you’d rather repopulate your grid or listbox with every request instead of serializing their data, simply databind them before viewstate begins being tracked - during init instead of during load.  See ASP.NET Page Lifecycle.

As for Sessions, I would recommend avoiding them.  You’ll probably have some real headaches if you need to support the offline client and rely on sessions, and it can be hard to anticipate (and test for) how sessions will be affected by one user popping several windows open.  In general, if data is ephemeral it can be handled well by viewstate.  If data is not ephemeral, you probably want to be storing it in a database.  So pass on session.


Posted by David Eison on Tuesday, October 19, 2010 3:04 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Internet Explorer 9 Beta and Dynamics CRM

With the release of the Internet Explorer 9 Beta today, I just wanted to make sure that everyone knows not to install IE9 on any machine using CRM Online, CRM 4.0 or testing out the CRM 2011 Beta.  IE9 Beta is currently unsupported for all three CRM environments, and if the IE8 Beta is any indication, it will break things in a big way.

So enjoy testing the Internet Explorer 9 Beta, just don't do it on a machine that uses Dynamics CRM. 


Posted by Wayne Walton on Wednesday, September 15, 2010 12:24 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and external .js

As near as I can tell, if document.write is added to a CRM onload method the entire method is disabled and not served to the client.

document.write is usually a bad idea, but it is quite useful when including an external javascript page, because it blocks while waiting for the page to load. 

In CRM you have two choices for including external files:

  • Directly manipulate the DOM, which does an async load, which means your scripts might not load in order.  If you load jquery first and then something that depends on jquery, the something that depends on jquery might fail.
  • Or, you can use window.execScript, which is IE specific but synchronous.  See http://danielcai.blogspot.com/2010/02/another-talk-about-referencing-external.html – but add a ?version=whatever or ?version=Math.random parameter to give you some control over caching as versions change.

(Disclaimer: including external pages in CRM can lead to problems with the outlook client working offline – you need to include them in your /isv folder and distribute them via a separate installer to your users for them to continue to have offline functionality.  Javascript inside the entity is automatically synced by CRM to offline clients, but files in /isv aren’t.)

You could also take a look at CrmExt, which is a fully featured toolkit for handling these sorts of issues: http://blogs.msdn.com/b/kbowling/archive/2010/04/30/crmext-js-an-external-javascript-loader-for-dynamics-crm.aspx


Posted by David Eison on Wednesday, September 1, 2010 4:06 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and prepended org names

Customers with on-premise or installations of CRM will be familiar with seeing /ORGNAME/ as the root directory in their CRM URLs. 

Microsoft added this feature so that one CRM installation could support multiple different organizations (Internet Facing Deployments and CRM online have something similar but use different server names instead of the virtual directory so that cookies can be kept separate).

However, it can lead to some confusion when wanting to link to CRM files.  Say you put a .html file in an iframe – do you use orgname or not?

The /ORGNAME/ handling is done via an ASP.NET virtual path provider – which means that it only kicks in if the ASP.NET engine is processing the request.  By default, IIS is normally configured to process requests for URLs ending in .aspx via a “Handler Mapping” .aspx to the ASP.NET engine, but resources like .html or .js or .gif or .jpg usually get processed by the default Static File handler.

So a .html file under http://[crm server]/ORGNAME/isv/myapp/myapp.html will 404, but the same file as a .aspx would happily show up.  To get to the static file, you need to leave out the /ORGNAME/: http://[crm server]/isv/myapp/myapp.html

Note that if you do want to link to a .aspx, the global JavaScript function prependOrgName is the best way to add the org name:

prependOrgName(‘/isv/myapp/myapp.aspx’)


Posted by David Eison on Wednesday, September 1, 2010 3:32 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Elmah and SoapException

A web app I’m working on which uses web service calls a lot is using Elmah for error logging.

Unfortunately web service calls consistently result in the useless ‘Server was unable to process request.’ message.

Originally I thought I could patch Elmah to log session variables then log the detail in the session in a Global.asax application_error handler.  Unfortunately, the Elmah error handler appears to consistently run before the application_error handler, so that doesn’t work (although it’s still nice to have session variables logged).

So I did some quick hacking on Elmah to get it to pull out the useful SoapException.Detail message. 

In Elmah’s Error.cs file, after:

if (httpException != null)
{
    _statusCode = httpException.GetHttpCode();
    _webHostHtmlMessage = Mask.NullString(httpException.GetHtmlErrorMessage());
}

I added:

// If this is a SOAP exception, then replace the message information with more useful info
System.Web.Services.Protocols.SoapException soapException = baseException as System.Web.Services.Protocols.SoapException;
if (soapException != null && soapException.Detail != null)
{
    _detail = baseException.Message 
        + System.Environment.NewLine + System.Environment.NewLine 
        + "Soap Detail: " + Mask.NullString(soapException.Detail.InnerText) 
        + System.Environment.NewLine + System.Environment.NewLine 
        + "Regular detail: " + System.Environment.NewLine + _detail;
    _message = Mask.NullString(soapException.Detail.InnerText);
    // not sure how big these detail strings get, so arbitrarily cap the size on the short message
    if (_message.Length > 100)
    {
        _message = _message.Substring(0, 100);
    }
}

And now I get Elmah error logs like:

elmah_log_new

Instead of:

elmah_log_old

And detail messages like:

elmah_log_newdetail

Instead of:

elmah_log_olddetail


Posted by David Eison on Thursday, August 19, 2010 2:33 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Microsoft Dynamics CRM 4.0 – Guids ToString – Even simple things can be complicated

One common need when working with CRM is to convert GUIDs to String.  Say, you want to build up some fetchxml.  No problem, .ToString(), right?

Well, Guids can actually be converted to strings in many different ways – even just the hexadecimal string can be with brackets, without brackets, uppercase, lowercase, with or without hyphens, etc. 

To keep your code as reliable and readable as possible, you’ll want to do this consistently – CRM uses uppercase hexadecimal, with brackets.  Guid has a ToString that takes a format string that you can pass it several different options.  “B” is closest to what we want, but not quite right, because it returns lowercase.  So you should convert to uppercase… but you’ll want to be careful, because converting to uppercase behavior can vary depending on the locale of the box running the code.  Usually your code will run on a server and it won’t matter, but what if you are writing a plugin which is running offline on an outlook client?  I don’t actually know if there is a locale where the hexadecimal characters A-F don’t convert right, but I know that in Turkey the uppercase of i is not I, so I don’t want to push my luck that no culture does something similar with A-F (update: Writing culture-safe managed code says Turkish and Azeri are the only languages with single character case differences, but this points out there may be multiple character case differences, such as Ff in welsh, and then there are some languages that don’t have some letters, such as Cryllic has no C for example…).  So, to be as safe as possible, specify the ‘InvariantCulture’ for any string comparisons or conversions that should deal with strings in a program-readable consistent manner.

Finally, to make this easily accessible to all of your code, you can add a utility method directly to the Guid class as an Extension Method

using System;
using System.Globalization;

namespace arkesystems.crm
{
    public static class CRMUtilExtensionmethods
    {
        public static string ToStringForCRM(this Guid guid)
        {
            return guid.ToString("B", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture);
        }
    }
}

 

Now whenever I want to turn a guid into a string, I just have to have

using arkesystems.crm;

at the top of the class, and my extension method is available directly on the Guid itself (handily also in the autocomplete dropdown):

Guid taskGuid = crmservice.Create(t);
string strTaskGuid = taskGuid.ToStringForCRM();

Posted by David Eison on Friday, August 6, 2010 6:44 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Update Rollup 12 for Dynamics CRM has been released

First off, here's the Knowledge Base Article and the Download page.

A couple of big issues have been resolved with this update. The first of which is the Email Router Rule Deployment Wizard now supports Exchange 2010.  Also, the Quick Find should be faster, as it no longer occasionally runs the search twice.

For those of you having trouble with Report Wizard Reports, the issue where the error  "Query execution failed for data set DSMain." has been resolved!

A lot of good updates in this Rollup, I highly recommendinstalling it soon.


Posted by Wayne Walton on Tuesday, August 3, 2010 9:38 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Microsoft Dynamics CRM 4 and systemuserid

Sometimes you need to know how User ID works “under the hood.”

We see ‘systemuserid’ all over the database identifying a user … but how does it decide which systemuserid to use?

CRM appears to figure out which user is logging in by querying the MSCRM_CONFIG database systemuserauthentication table looking for a value in the authinfo column that matches the objectsid for the user from activedirectory (SID, *not* GUID).

You can get the user SID (and GUID) from active directory by registering a dll from the windows server 2003 resource toolkit ( http://download.microsoft.com/download/8/e/c/8ec3a7d8-05b4-440a-a71e-ca3ee25fe057/rktools.exe ) on a 32-bit OS (I don't know how to make it work on 64 bit; docs say 64 bit is not supported) - install toolkit, copy acctinfo.dll to c:\windows\system32, "regsvr32 acctinfo.dll" , then browse to user in AD ( start/run/dsa.msc ) and look at the 'additional account info' tab (doesn't show up when user is found using find, unfortunately).

Then you can dig up the entry for the sid in systemuserauthentication, which gives you a userid, which you can find in systemuserorganization, which gives you a crmuserid, which you can go find as systemuser in the [orgname]_mscrm database (which is NOT the same as systemuser in the mscrm_config database).

If you have a problem with a mismapping between the two databases (for example, somebody mistakenly doing a backup of one but not the other), there are two easy ways to fix the problem:

The first thing to try is to edit the user the in the normal CRM interface and change the AD login name to a not-yet-used-by-CRM value, save the entity, then change it back to the value you want to be used.  This often cleans up a broken mapping.

The next thing to try is to use the crm deployment manager to export and import the organization; during the import there is a user mapping step where you can have CRM set up these relationships properly for you.

Finally, filtered views have their own method of identifying the user – they use [fn_FindUserGuid] which checks for a GUID on context_info(), and if not found, they query SystemUserBase where DomainName = SUSER_SNAME() .

I recommend against trying to operate on this data at the raw sql level, system user id is simply referenced in too many places to reliably get everything.  You would end up with orphaned records at best, for example due to all the ‘createdby’ and ‘modifiedby’ references on every entity.  So ‘select’ is fine to learn things, but ‘delete’ or ‘update’ at your own risk and preferably not even then.  Still, if you have to, the AD SID is where to start.


Posted by David Eison on Monday, June 7, 2010 5:45 PM
Permalink | Comments (0) | Post RSSRSS comment feed