Thursday, February 23, 2012

MS CRM 2011: Bulk refresh of user details from AD

When you create user in CRM all the available information is populated from AD into CRM user form. But in case information was changed in AD (email box, phone e.t.c.) - information will remain unchanged till the moment you will open form of user and change it. In case a lot of information was changed it will be quite boring to update users information one-by-one.

Following post describes how to allow bulk update of user details.

First step - JavaScript that will do update of data - create webresource, put inside following code, save and publish:

function RefreshUsersADInfo(selectedusers)
{
var orgserviceurl = Xrm.Page.context.prependOrgName("/XRMServices/2011/Organization.svc/web");

for(var i = 0; i < selectedusers.length; i++)
{
var domainname = GetDomainName(selectedusers[i], orgserviceurl);
UpdateUserADInfo(selectedusers[i], domainname, orgserviceurl);
}

crmGrid.Refresh();
}

function UpdateUserADInfo(userid, domainname, orgserviceurl)
{
var oCommand=new RemoteCommand("UserManager","RetrieveADUserProperties");
if(oCommand!=null)
{
oCommand.SetParameter("domainAccountName", domainname);
var oResult = oCommand.Execute();

var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">"+
"<s:Body>"+
"<Update xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"+
"<entity xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">"+
"<a:Attributes xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";

if(oResult.Success&&!IsNull(oResult.ReturnValue)&&oResult.ReturnValue.length>0)
for(var oUserXmlDoc=loadXmlDocument(oResult.ReturnValue),oNodeList=oUserXmlDoc.documentElement.childNodes,i=0;i<oNodeList.length;i++)
{
var oNode=oNodeList.item(i);

request += "<a:KeyValuePairOfstringanyType>"+
"<b:key>" + oNode.tagName + "</b:key>"+
"<b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + oNode.text + "</b:value>"+
"</a:KeyValuePairOfstringanyType>";
}

request +="</a:Attributes>"+
"<a:EntityState i:nil=\"true\" />"+
"<a:FormattedValues xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\" />"+
"<a:Id>" + userid + "</a:Id>"+
"<a:LogicalName>systemuser</a:LogicalName>"+
"<a:RelatedEntities xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\" />"+
"</entity>"+
"</Update>"+
"</s:Body>"+
"</s:Envelope>";

var req = new XMLHttpRequest();
req.open("POST", orgserviceurl, false);
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Update");
req.send(request);
}
}

function GetDomainName(userid, orgserviceurl)
{
var request = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">"+
"<s:Body>"+
"<Retrieve xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"+
"<entityName>systemuser</entityName>"+
"<id>" + userid + "</id>"+
"<columnSet xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">"+
"<a:AllColumns>false</a:AllColumns>"+
"<a:Columns xmlns:b=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">"+
"<b:string>domainname</b:string>"+
"</a:Columns>"+
"</columnSet>"+
"</Retrieve>"+
"</s:Body>"+
"</s:Envelope>";

var req = new XMLHttpRequest();
req.open("POST", orgserviceurl, false);
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Retrieve");
req.send(request);

var attributes = req.responseXML.getElementsByTagName("a:KeyValuePairOfstringanyType");
if (attributes == null || attributes.length == 0)
return null;

for(var i = 0; i < attributes.length; i++)
if (attributes[i].selectSingleNode("./b:key").text == "domainname")
{
var node = attributes[i].selectSingleNode("./b:value");
if (node != null)
return node.nodeTypedValue;
}

return null;
}


Second step - modify ribbon to add "Refresh" button to users grid. I prefer to use tools for it and here are screenshots:






Here is RibbonXml:

<RibbonDiffXml>
<CustomActions>
<CustomAction Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.CustomAction" Location="Mscrm.HomepageGrid.systemuser.MainTab.Management.Controls._children" Sequence="41">
<CommandUIDefinition>
<Button Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo" Command="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.Command" Sequence="96" ToolTipTitle="$LocLabels:xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.LabelText" LabelText="$LocLabels:xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.LabelText" ToolTipDescription="$LocLabels:xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.Description" TemplateAlias="o2" Image16by16="/_imgs/grid/refresh16.gif" />
</CommandUIDefinition>
</CustomAction>
</CustomActions>
<Templates>
<RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
</Templates>
<CommandDefinitions>
<CommandDefinition Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.Command">
<EnableRules>
<EnableRule Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.Command.EnableRule.SelectionCountRule" />
</EnableRules>
<DisplayRules />
<Actions>
<JavaScriptFunction Library="$webresource:xs_refreshusers.js" FunctionName="RefreshUsersADInfo">
<CrmParameter Value="SelectedControlSelectedItemIds" />
</JavaScriptFunction>
</Actions>
</CommandDefinition>
</CommandDefinitions>
<RuleDefinitions>
<TabDisplayRules />
<DisplayRules />
<EnableRules>
<EnableRule Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.Command.EnableRule.SelectionCountRule">
<SelectionCountRule Minimum="1" AppliesTo="SelectedEntity" />
</EnableRule>
</EnableRules>
</RuleDefinitions>
<LocLabels>
<LocLabel Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.LabelText">
<Titles>
<Title languagecode="1033" description="Refresh" />
</Titles>
</LocLabel>
<LocLabel Id="xs.HomepageGrid.systemuser.MainTab.Management.refreshadinfo.Description">
<Titles>
<Title languagecode="1033" description="Refreshes Users' Info from AD" />
</Titles>
</LocLabel>
</LocLabels>
</RibbonDiffXml>

In the case you have performed everything correctly in users ribbon you will find new button:



Functions that are used for fetching of users' information are undocumented so I assume that in common this customization is unsupported.

I want to say special thanks to my friend Artem Grunin who is former MVP and employee of Microsoft. Here is his article regarding similar issue for CRM 4.0 - http://fixrm.wordpress.com/2011/02/02/how-to-update-crm-user-profile-from-active-directory/