Thursday, September 25, 2014

How to delete an old component from your managed solution in CRM 2011/2013

Hi Guys,

Every CRM 2011/2013 developer that create his own managed solution will, sooner or later, face this problem.

If you work in a development environment on an unmanaged solution called Solution Alpha and you want ti export it to a managed solution with version 1.0.0.0 when you have finished your development, that generate a compressed file called : SolutionAlpha_1_0_0_0_managed.zip.

This solution contains for exemple :
 - Three custom entities : (sa_business, sa_turnover and sa_relationship)
 - One custom dashboard : (Actual Turnover)

Now after two or three months, you worked hard and you upgraded your unmanaged solution. You will exported it in a managed solution with version 2.0.0.0, that generate a compressed file called : SolutionAlpha_2_0_0_0_managed.zip.


This new version of your solution contains :
 - Four custom entities : (sa_business, sa_turnover, sa_marketingplan and sa_statistics )
 - Two custom dashboards : (Actual Turnover, Statistics)

But there is no sa_relationship entity in the new version because in the new version, this entity is obselete.

When your client import the new version in his organization, he will see that the version changed but when he see the entities list, he will find the sa_relationship entity although this entity has been removed from the new version.

And because, it's a managed solution, your client will not be able to remove this entity mannually.

Here is an illustration of the situation on the organization of the client :


If your client thinks to remove the version 1.0.0.0 of the solution before importing the new version, he will lost all data created with the first version.

Because, when we remve a managed solution we remove the data also,

WHAT CAN YOU DO TO RESOLVE THIS PROBLEM  ??????

Here is the solution :

Inevitably, you should use a Holding Solution.

the principle of Holding Solution is to hold data available in the organization when you delete the old version.

To do it you should fellow these steps:

1 - You should extract the new solution version SolutionAlpha_2_0_0_0_managed.zip in a separate folder.
2 - Open the solution.xml file.
3 - Go to Uniquename tag and change it to HoldingSolutionAlpha like this :

  1. <ImportExportXml version="6.0.0001.0061" SolutionPackageVersion="6.0" languagecode="1033" generatedBy="OnPremise" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  2.   <SolutionManifest>
  3.     <UniqueName>HoldingSolutionAlpha</UniqueName>
  4.     <LocalizedNames>
  5.       <LocalizedName description="Holding Solution Alpha" languagecode="1033" />
  6.     </LocalizedNames>

4 - Save the solution.xml file.
5 - Re-compress and rename the solution floder as HoldingSolutionAlpha_2_0_0_0_managed.zip.
6 - Now, if we make an inventory of solutions we will have three solutions:
    * Solution Alpha version 1.0.0.0 already installed in your client organization.
    * Solution Alpha version 2.0.0.0
    * Holding Solution Alpha version 2.0.0.0
7 - Connect to the client organization, and go to Settings > Solutions.
8 - Import the solution HoldingSolutionAlpha_2_0_0_0_managed.zip
9 - Delete the solution Solution Alpha version 1.0.0.0
     don't worry, the holding solution will keep your data available.
10 - Import the solution Solution Alpha version 2.0.0.0
11 - Delete the solution Holding Solution Alpha version 2.0.0.0

Now if you look at your solutions subgrid you will see only the Solution Alpha version 2.0.0.0.
And guess what ? There is no sa_relationship entity in the client organization.

Good Luck.

Tuesday, August 19, 2014

How to manage ODATA 50 records limitation

Hi Guys,

When we use javascript in Dynamics CRM webresources we often ask this question : Did I use SOAP or ODATA ?
For those who have chosen ODATA, when they need to retrieve many recods they are quickly confronted with a classic problem . This problem is : Your resultset contains only 50 records and if you need to get the 200 accounts that you need you should use pagination.

There are three ways to manage this limitation:
- First One: is to accept it and to use pagination to get your data.
In this case, here is the way to use pagination in ODATA queries.

You can create an array to get all records like this array : relatedAccounts[] and a javascript function called GetRecords(url) with a parameter containning the url of your ODATA Request.

The callback success  of the ajax method of jQuery library retrieve the first page of the resultset.

But if you __next member of the data.d object you will got the url of the next page of your resultset.

So, Now you should call the same method GetRecords by passing the new url. Look at this javascript source code :

  1. relatedAccounts = [];
  2.  
  3. function GetAllAccountsRecords() {
  4.     var serverUrl = Xrm.Page.context.getServerUrl();
  5.     var oDataUri = serverUrl + "/xrmservices/2011/OrganizationData.svc/AccountSet?$select=AccountId,Name,&$filter=StateCode/Value eq 0";
  6.     GetRecords(oDataUri);
  7.     var totalRecords = relatedAccounts.length;
  8. }
  9.  
  10. function GetRecords(url) {
  11.     jQuery.ajax({
  12.         type: "GET",
  13.         contentType: "application/json; charset=utf-8",
  14.         datatype: "json",
  15.         url: url,
  16.         async: false,
  17.         beforeSend: function (XMLHttpRequest) {
  18.             XMLHttpRequest.setRequestHeader("Accept", "application/json");
  19.         },
  20.         success: function (data, textStatus, XmlHttpRequest) {
  21.             if (data && data.d != null && data.d.results != null) {
  22.                 AddRecordsToArray(data.d.results);
  23.                 FetchRecordsCallBack(data.d);
  24.             }
  25.         },
  26.         error: function (XmlHttpRequest, textStatus, errorThrown) {
  27.             alert("Error :  has occured during retrieval of the records ");
  28.         }
  29.     });
  30. }
  31.  
  32. function AddRecordsToArray(records) {
  33.     for (var i = 0; i < records.length; i++) {
  34.         relatedAccounts.push(records[i]);
  35.     }
  36. }
  37.  
  38. function FetchRecordsCallBack(records) {
  39.     if (records.__next != null) {
  40.         var url = records.__next;
  41.         GetRecords(url);
  42.     }
  43. }

That means that you use a reccursive method. This method will stop executing when the data.d.__next = null.

When this method stops, you will got all your records in the relatedAccounts array.

- Second One: is to say no I should increase this limitation.
In this case, It's possible but if you increase this limitation to 250 you can got a retrieve that returns 300 records for example and you will be in the same case of 50 records.
But if you say no matter I need to increase it to 250 you have to modify MaxResultsPerCollection property in ServerSettings configuration table in MSCRM_Config database.

You should know that this modification of server settings impact all the server CRM organizations.

Now you can do it using Deployment Service like this :


Or you can do it also by PowerShell cmdlets like this :

  1. Add-PSSnapin Microsoft.Crm.PowerShell
  2. $setting = New-Object "Microsoft.Xrm.Sdk.Deployment.ConfigurationEntity"
  3. $setting.LogicalName = "ServerSettings"
  4. $setting.Attributes = New-Object "Microsoft.Xrm.Sdk.Deployment.AttributeCollection"
  5. $attribute = New-Object "System.Collections.Generic.KeyValuePair[String, Object]" ("MaxResultsPerCollection", 250)
  6. $setting.Attributes.Add($attribute)
  7. Set-CrmAdvancedSetting -Entity $setting

After executing this cmdlets run IISRESET ou restart IIS service because these kind of settings are cached.

By the way ServerSettings table contains many other settings that you can show in this link : http://msdn.microsoft.com/en-us/library/gg334675.aspx


- Third One: is to get only 50 records and you show a message to say: This resultset is capped with 50 records you should use more filters to refine search.
That's all :)

Thursday, May 8, 2014

How to use Plugin Profiler to debug Plugins with Plugin Registration Tool in CRM 2011/2013

Hi Guys,

You are a Dynamics CRM 2011/2013 Developer and you need to develop some plugnis to perform some synchronous/asynchronous operations that need realy a plugin development.

You develop your plugins on Visual Studio and you build. You build succesfully your plugins assembly and all seems fine.

You use Plugin Registration Tool (PRT) to register your plugin, steps and images.

And Now you are doing your test on your records to verify if your plugins works fine or not, and like for me, nothing works from the first time :)

So you had a Busines Process Error and you want to know why ?

It means, you should debug your code.

And There is three ways to do it :

- First way : If you are using a OnPremise version of Dynamics CRM 2011/2013 and you have your own development server with Visual Studio:
* you should copy your code in your server
* open it with Visual Studio
* put your breakpoints
* attach w3p process
* And debug

When it's ok you can copy your code, build your plugin assembly and update your register assembly with the new one.
It's the easiest way but every developer don't have a personal development server.

- Second way : If you are using OnPremise version of Dynamics CRM 2011/2013 and you don't have access to your own development server but you have access to a team-owned development server without Visual Studio:
* In this case you should use the remote debugger (we will see this way in an upcoming post)

- Third way : If you are using Online version of Dynamics CRM or OnPremise but you don't have access to your team-owned development server using remote debugger.
So you should use Plugin Profiler.

Plugin Profiler it's a powerful PRT component that allows users to debug plugins. This component should be installed on every organization that need plugins debugging.

Let's Go, there are steps to do when you want to use Plugin Profiler:

1 - Open PRT and connect to your CRM Organization
2 - Click on "Install Profiler" button


3 - Wait a few minutes until Profiler is installed successfully


4 - "Install Profiler" becomes "Uninstall Profiler", and a particular Plug-in Profiler assembly is installed.
Plugin Profiler allows also Workflows profiling.


5 - Now you can use your Profiler, it's a good thing because your creation of contact don't work fine and you want to know why?
In this case, you will search the pre-operation create contact step of your plugin, click on right mouse button and click on "Start Profiling" context menu item.

The dialog window "Profile Settings" appears and you can configure storage settings (in Exception mode or in a specific custom entity(Persist mode)).   


You can also configure the limit number of execution and if you want to include "Secure Configuration" if you are using Secure Configuration in your Step like this:


 Click Ok button in the Profiler Settings dialog box.

6 - return to your CRM website, go to Sales > Contacts


Click "+New" button to create a new contact :


The plugin should change the birthdate year from 5/21/2014 to 5/21/2996. When you click sur Save a Business Process Error with an incomrehensive text appears. You should download the log file and save it in the same folder of the plugin assembly. It's not necessary to save it in the same folder but it make debugging easier.


7 - Now return to your Visual Studio IDE, open your plugin solution, put your breakpoints in your source code, go to Debug menu item and click on "Attach to Process" menu item.

Select PluginRegistration.exe process and click "Attach"



8 - Now return to PRT and stop profiling


9 - It's show time, Click on Debug button.


The Debug screen appears and you should select :
> Profile Location : the error details text file that you already downloaded.
> Assembly Location : the location of the plugin assembly.    


If you have custom Secure or Unsecure configuration you can  add them in the "Secure Configuration" and "Unsecure Configuration" tabs.



 You can also change the isolation mode :


Click on "Start Execution" button and return to your source code in Visual Studio to debug.

Good Luck.

N.JL


Tuesday, April 8, 2014

SSIS Script Task using KingswaySoft for CRM 2011

Hi Guys,
kingswaysoft components allow simple operations like Retrieve, Update or Create, but when you need to do something more complex, there is no kingswaysoft components. But kingswaysoft provides two useful assemblies that you can uses them in a Script Task:
  • KingswaySoft.DynamicsCrmServices
  • KingswaySoft.IntegrationToolkit.DynamicsCrm
To do it follow this short toturial:
You need to create a Script Task in your Data Flow Process.
And to add your kingswaysoft crm connection manager to Connection Managers List:

After that you should to add kingswaysoft assemblies in reference like this :
Then, You should add kingswaysoft namespaces like this:

  1. #region Namespaces
  2. using System;
  3. using System.Data;
  4. using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
  5. using Microsoft.SqlServer.Dts.Runtime.Wrapper;
  6. using System.ServiceModel.Description;
  7. using KingswaySoft.IntegrationToolkit.DynamicsCrm;
  8. using KingswaySoft.DynamicsCrmServices.Soap2011;
  9. using KingswaySoft.DynamicsCrmServices.Soap2011.OrganizationService.Query;
  10. using KingswaySoft.DynamicsCrmServices.Soap2011.OrganizationService;
  11. #endregion
So, Now you need to create a IOrganizationService Object
  1. [Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
  2. public class ScriptMain : UserComponent
  3. {
  4.  
  5.     KingswaySoft.DynamicsCrmServices.Soap2011.OrganizationService.IOrganizationService orgService;
  6.     ...

In PreExecute Methode, intialize your IOrganizationService Object using the AcquireConnection methode:

  1. public override void PreExecute()
  2. {
  3.     base.PreExecute();
  4.     var conn = (CrmConnection)Connections.CrmConnection.AcquireConnection(null);
  5.  
  6.     orgService = (KingswaySoft.DynamicsCrmServices.Soap2011.OrganizationService.IOrganizationService)conn.GetCrmService();
  7.     
  8. }
After that you can use all needed methods like RetrieveMultiple, Execute, Update etc...
Good Luck

N.JL

Thursday, April 3, 2014

How to Close Incident using SOAP on CRM 2011

Hi Guys,

The IncidentResolution entity is a particular entity that we can not modify or form neither fields.

So when you need to put an automatic value in Resolution field (subject) you cannot customize form by adding jscript code.

The only way to do it, is by hiding the Resolve Case button:on Incident form/home page Ribbon, and to create your own custom resolution window or dialog and should send a SOAP request to the CRM Server to close the incident.

There is the most complete source code that you allow to close your incident by sending : Subject, TimeSpent and Description data. This code is synchronous.

Source Code
  1. if (typeof (Sdk) == "undefined")
  2. { Sdk = { __namespace: true }; }
  3. //This will establish a more unique namespace for functions in this library. This will reduce the
  4. // potential for functions to be overwritten due to a duplicate name when the library is loaded.
  5. Sdk.Tools = {
  6.     _getServerUrl: function () {
  7.         ///<summary>
  8.         /// Returns the URL for the SOAP endpoint using the context information available in the form
  9.         /// or HTML Web resource.
  10.         ///</summary>
  11.         var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
  12.         var serverUrl = "";
  13.         if (typeof GetGlobalContext == "function") {
  14.             var context = GetGlobalContext();
  15.             serverUrl = context.getServerUrl();
  16.         }
  17.         else {
  18.             if (typeof Xrm.Page.context == "object") {
  19.                 serverUrl = Xrm.Page.context.getServerUrl();
  20.             }
  21.             else { throw new Error("Unable to access the server URL"); }
  22.         }
  23.         if (serverUrl.match(/\/$/)) {
  24.             serverUrl = serverUrl.substring(0, serverUrl.length - 1);
  25.         }
  26.         return serverUrl + OrgServicePath;
  27.     },
  28.     CloseIncidentRequest: function (incidentId, subject, resolutionTypeValue, durationValue, description) {
  29.  
  30.         var requestMain = ""
  31.         requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
  32.         requestMain += "  <s:Body>";
  33.         requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
  34.         requestMain += "      <request i:type=\"b:CloseIncidentRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
  35.         requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
  36.         requestMain += "          <a:KeyValuePairOfstringanyType>";
  37.         requestMain += "            <c:key>IncidentResolution</c:key>";
  38.         requestMain += "            <c:value i:type=\"a:Entity\">";
  39.         requestMain += "              <a:Attributes>";
  40.         requestMain += "                <a:KeyValuePairOfstringanyType>";
  41.         requestMain += "                  <c:key>incidentid</c:key>";
  42.         requestMain += "                  <c:value i:type=\"a:EntityReference\">";
  43.         requestMain += "                    <a:Id>" + incidentId + "</a:Id>";
  44.         requestMain += "                    <a:LogicalName>incident</a:LogicalName>";
  45.         requestMain += "                    <a:Name i:nil=\"true\" />";
  46.         requestMain += "                  </c:value>";
  47.         requestMain += "                </a:KeyValuePairOfstringanyType>";
  48.         requestMain += "                <a:KeyValuePairOfstringanyType>";
  49.         requestMain += "                  <c:key>subject</c:key>";
  50.         requestMain += "                  <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + subject + "</c:value>";
  51.         requestMain += "                </a:KeyValuePairOfstringanyType>";
  52.         requestMain += "                <a:KeyValuePairOfstringanyType>";
  53.         requestMain += "                  <c:key>description</c:key>";
  54.         requestMain += "                  <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + description + "</c:value>";
  55.         requestMain += "                </a:KeyValuePairOfstringanyType>";
  56.         requestMain += "                <a:KeyValuePairOfstringanyType>";
  57.         requestMain += "                  <c:key>timespent</c:key>";
  58.         requestMain += "                  <c:value i:type=\"d:int\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + durationValue + "</c:value>";
  59.         requestMain += "                </a:KeyValuePairOfstringanyType>";
  60.         requestMain += "              </a:Attributes>";
  61.         requestMain += "              <a:EntityState i:nil=\"true\" />";
  62.         requestMain += "              <a:FormattedValues />";
  63.         requestMain += "              <a:Id>00000000-0000-0000-0000-000000000000</a:Id>";
  64.         requestMain += "              <a:LogicalName>incidentresolution</a:LogicalName>";
  65.         requestMain += "              <a:RelatedEntities />";
  66.         requestMain += "            </c:value>";
  67.         requestMain += "          </a:KeyValuePairOfstringanyType>";
  68.         requestMain += "          <a:KeyValuePairOfstringanyType>";
  69.         requestMain += "            <c:key>Status</c:key>";
  70.         requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
  71.         requestMain += "              <a:Value>" + resolutionTypeValue + "</a:Value>";
  72.         requestMain += "            </c:value>";
  73.         requestMain += "          </a:KeyValuePairOfstringanyType>";
  74.         requestMain += "        </a:Parameters>";
  75.         requestMain += "        <a:RequestId i:nil=\"true\" />";
  76.         requestMain += "        <a:RequestName>CloseIncident</a:RequestName>";
  77.         requestMain += "      </request>";
  78.         requestMain += "    </Execute>";
  79.         requestMain += "  </s:Body>";
  80.         requestMain += "</s:Envelope>";
  81.         var req = new XMLHttpRequest();
  82.         req.open("POST", Sdk.Tools._getServerUrl(), false)
  83.         // Responses will return XML. It isn't possible to return JSON.
  84.         req.setRequestHeader("Accept", "application/xml, text/xml, */*");
  85.         req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
  86.         req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
  87.         req.send(requestMain);
  88.  
  89.         //work with response here
  90.         return req.responseXML.xml;
  91.     },
  92.     __namespace: true
  93. };

Enjoy yourself,

N.JL