There was a time when you could just create a new project in Visual Studio as a Windows Service and you could install it using the installutil command.
Somewhere along the way that changed so now you have to add a Project Installer class to the Windows Service.
So lets assume you've created your windows service and changed the name from Service1 to say MyService. Microsoft explain the steps for adding an installer and I repeat them here.
1. In Solution Explorer, access Design view for the service for which you want to add an installation component.
2. Click the background of the designer to select the service itself, rather than any of its contents.
3. With the designer in focus, right-click, and then click Add Installer.
4. A new class, ProjectInstaller, and two installation components, ServiceProcessInstaller and ServiceInstaller, are added to your project, and property values for the service are copied to the components.
5. Click the ServiceInstaller component and verify that the value of the ServiceName property is set to the same value as the ServiceName property on the service itself. In my example you would have to change Service1 to MyService.
6. Change the StartType to Manual, Automatic or Disabled.
7. Click on the ServiceProcesInstaller and set the User property to Local serice, Network Service, System or User.
That's it. Open the Visual Studio command prompt using "Run as Administrator", chage to the directory where your EXE file is and run
installutil MyWindowsService.exe
You might be interested in my next post about debugging a Windows Service. Frankly I find debugging Windows Services a pain, so I tend to write and test my code as a Console Application, and then I convert it to a Windows Service when its all working.
Saturday, September 10, 2011
Sunday, August 21, 2011
Moving CRM Attachments to SharePoint
CRM 2011 allows you to store documents in SharePoint and view them from within CRM. Someone called Maria left me a comment asking if you could move attachments from CRM to SharePoint. My previous blogs about creating SharePoint document locations from CRM were actually prerequisites for doing exactly this. I wanted to take a CRM attachment and move it to SharePoint. In my case it was an attachment on a letter activity as the result of a mailmerge. To be accurate it is an attachment on an annotation (or note) on a letter activity. I needed to move the attachment to SharePoint and then leave a link to the document on the letter activity. To achieve this we created a plugin on the PreCreate action of an annotation. Then in the execute method of the plugin you can instantiate the annotation entity
Once you have the annotation then you can get the contents of the attachment as a byte array.
To remove the attachment you need to use this code
The helper method for decoding the attachment to a byte array
To upload the document to SharePoint you should use the CopyIntoItems method of the Copy web service located at http://sharepointurl/_vti_bin/Copy.asmx. But before you do, you need to replace any illegal characters in the filename - in our case we replaced an illegal character with a hyphen.
I may have posted this code too late to help Maria, but I hope someone else finds it useful.
if (null != context && null != context.InputParameters) { if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) { // Obtain the target entity from the input parmameters. Entity entity = (Entity)context.InputParameters["Target"]; } } |
// Retrieve the base64Encoding document body // and convert it to byte base64encoding // see below for the DecodeFrom64 function byte[] documentBody = DecodeFrom64(entity.Attributes["documentbody"].ToString()); // get the filename System.IO.FileInfo annotationFileInfo = new System.IO.FileInfo(fileName); |
entity.Attributes["documentbody"] = null; entity.Attributes["filename"] = null; entity.Attributes["filesize"] = null; |
The helper method for decoding the attachment to a byte array
internal static byte[] DecodeFrom64(string encodedData) { byte[] encodedDataAsBytes = System.Convert.FromBase64String(encodedData); return encodedDataAsBytes; } |
To upload the document to SharePoint you should use the CopyIntoItems method of the Copy web service located at http://sharepointurl/_vti_bin/Copy.asmx. But before you do, you need to replace any illegal characters in the filename - in our case we replaced an illegal character with a hyphen.
internal static string ReplaceSpecialCharacters(string input) { Regex r = new Regex("(?:[^a-z0-9 ]|(?<=['\"])s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); return r.Replace(input, "-"); } |
Labels:
annotation,
attachments,
CRM 2011,
SharePoint 2010
Same blog, different theme
I've grown bored of the theme that I use for the blog and somebody posted a comment about changing the theme so I've done so.
I hope this makes the blogs easier to read.
I hope this makes the blogs easier to read.
Monday, July 4, 2011
Transforming XML using BizTalk Mapper generated XSLT
If I have to write XSLT I try and make use of the BizTalk Mapper because it can generate XSLT in a fraction of the time. It also supplies a test harness so you can use an input file to test out your map.
You can add functoids to your map and I make use of the Scripting functoid so that I can write inline C# script. Here is an example of a function that takes a string source element and will truncate it if it is longer than the string length the target element can handle. It writes out an Information message to the event log if the maximum length is exceeded.
The function takes two extra parameters apart from the source element: the name of the source element and the maximum length. Add them by clicking on the ellipses next to Configure Functoid Inputs.
Here is the inline C# script.
public string Transform(string param1, string fieldname, int32 maxlength)
{
if (param1.Length > maxlength)
{
System.Diagnostics.EventLog.WriteEntry("Transformation", fieldname + " in excess of " + maxlength.ToString() + " chars, truncating", System.Diagnostics.EventLogEntryType.Warning);
return param1.Substring(0, 20);
}
else
{
return param1;
}
}
What BizTalk does is to write this out as a function at the botton of the XSLT. To produce the XSLT, use Validate Map and then navigate to where the output window has written the file.
Now I struggled a bit to get this XSLT to work until I finally found the answer was to use an XPathDocument instead of an XmlDocument. That's it. A transformation using the output from the BizTalk Mapper. Deep, deep joy.
Add the following using statements
using System.Xml;
using System.Xml.Xpath;
using System.Xml.Xslt;
// Transforms an XML document
// using an XSLT generated by BizTalk
// note use of XPathDocument
private XmlDocument Transform()
{
XslCompiledTransform xslt = new XslCompiledTransform();
// load the xslt
xslt.Load(@"C:\Projects\Import\MySchema.xsl", new XsltSettings(false, true), new XmlUrlResolver());
string filePathName = @"C:\Projects\Import\MyXML.xml";
//Load the XML data file.
XPathDocument doc1 = new XPathDocument(filePathName);
// create a memory stream
MemoryStream ms = new MemoryStream();
//Create an XmlTextWriter to write to the memory stream
XmlTextWriter writer = new XmlTextWriter(ms, Encoding.Unicode);
writer.Formatting = Formatting.Indented;
//Transform the file.
xslt.Transform(doc1, null, writer, null);
ms.Seek(0, SeekOrigin.Begin); // ** UPDATE changed from ms.Position=0 ****//
if (ms.Length == 0)
{
Exception ex = new Exception ("Transform error , output is null");
throw ex;
}
// load the memory stream into a XML document
XmlDocument output = new XmlDocument();
output.Load(ms);
writer.Close();
return output;
}
You can add functoids to your map and I make use of the Scripting functoid so that I can write inline C# script. Here is an example of a function that takes a string source element and will truncate it if it is longer than the string length the target element can handle. It writes out an Information message to the event log if the maximum length is exceeded.
The function takes two extra parameters apart from the source element: the name of the source element and the maximum length. Add them by clicking on the ellipses next to Configure Functoid Inputs.
Here is the inline C# script.
public string Transform(string param1, string fieldname, int32 maxlength)
{
if (param1.Length > maxlength)
{
System.Diagnostics.EventLog.WriteEntry("Transformation", fieldname + " in excess of " + maxlength.ToString() + " chars, truncating", System.Diagnostics.EventLogEntryType.Warning);
return param1.Substring(0, 20);
}
else
{
return param1;
}
}
What BizTalk does is to write this out as a function at the botton of the XSLT. To produce the XSLT, use Validate Map and then navigate to where the output window has written the file.
Now I struggled a bit to get this XSLT to work until I finally found the answer was to use an XPathDocument instead of an XmlDocument. That's it. A transformation using the output from the BizTalk Mapper. Deep, deep joy.
Add the following using statements
using System.Xml;
using System.Xml.Xpath;
using System.Xml.Xslt;
// Transforms an XML document
// using an XSLT generated by BizTalk
// note use of XPathDocument
private XmlDocument Transform()
{
XslCompiledTransform xslt = new XslCompiledTransform();
// load the xslt
xslt.Load(@"C:\Projects\Import\MySchema.xsl", new XsltSettings(false, true), new XmlUrlResolver());
string filePathName = @"C:\Projects\Import\MyXML.xml";
//Load the XML data file.
XPathDocument doc1 = new XPathDocument(filePathName);
// create a memory stream
MemoryStream ms = new MemoryStream();
//Create an XmlTextWriter to write to the memory stream
XmlTextWriter writer = new XmlTextWriter(ms, Encoding.Unicode);
writer.Formatting = Formatting.Indented;
//Transform the file.
xslt.Transform(doc1, null, writer, null);
ms.Seek(0, SeekOrigin.Begin); // ** UPDATE changed from ms.Position=0 ****//
if (ms.Length == 0)
{
Exception ex = new Exception ("Transform error , output is null");
throw ex;
}
// load the memory stream into a XML document
XmlDocument output = new XmlDocument();
output.Load(ms);
writer.Close();
return output;
}
Labels:
BizTalk Server,
Mapper,
Transform,
XML,
XSLT
Monday, April 4, 2011
Darwin Awards 2010
A non-technical blog this month, dear reader, forced upon me because of a misleading blog I read today. Imagine that! What is the Internet coming too when you can't trust a blog?
Anyway, I Googled for the "Darwin Awards 2010" and stumbled on a blog that claimed to announce the Darwin Awards results for 2010. While some of the alleged awards were amusing - I particularly liked the guy who tried to siphon off diesel from a camper van but mistakenly put the hose into the sceptic tank - they were clearly not true Darwin Awards. I need scarcely remind you dear reader that the Darwin Awards "commemorate those who improve our gene pool by removing themselves from it". In short, they are only ever awarded posthumously for unparalleled stupidity.
The real Darwin Awards web site can be found at this location so please don't confuse it with imitations.
For reasons of good taste, I won't reproduce the winning entry here. However I do recount this runner up which unfortunately has not been confirmed as true.
In the late fall and early winter months, snow-covered mountains become infested with hunters. One ambitious pair climbed high up a mountain in search of their quarry. The trail crossed a small glacier that had crusted over. The lead hunter had to stomp a foot-hold in the snow, one step at a time, in order to cross the glacier.
Somewhere near the middle of the glacier, his next stomp hit not snow but a rock. The lead hunter lost his footing and fell. Down the crusty glacier he zipped, off the edge and out of sight.
Unable to help, his companion watched him slide away. After a while, he shouted out, "Are you OK?"
"Yes!" came the answer.
Reasoning that it was a quick way off the glacier, the second hunter plopped down and accelerated down the ice, following his friend. There, just over the edge of the glacier, was his friend...holding onto the top of a tree that barely protruded from the snow.
There were no other treetops nearby, nothing to grab, nothing but a hundred-foot drop onto the rocks below. As the second hunter shot past the first, he uttered his final epitaph: a single word, which we may not utter lest our mothers soap our mouths.
Anyway, I Googled for the "Darwin Awards 2010" and stumbled on a blog that claimed to announce the Darwin Awards results for 2010. While some of the alleged awards were amusing - I particularly liked the guy who tried to siphon off diesel from a camper van but mistakenly put the hose into the sceptic tank - they were clearly not true Darwin Awards. I need scarcely remind you dear reader that the Darwin Awards "commemorate those who improve our gene pool by removing themselves from it". In short, they are only ever awarded posthumously for unparalleled stupidity.
The real Darwin Awards web site can be found at this location so please don't confuse it with imitations.
For reasons of good taste, I won't reproduce the winning entry here. However I do recount this runner up which unfortunately has not been confirmed as true.
In the late fall and early winter months, snow-covered mountains become infested with hunters. One ambitious pair climbed high up a mountain in search of their quarry. The trail crossed a small glacier that had crusted over. The lead hunter had to stomp a foot-hold in the snow, one step at a time, in order to cross the glacier.
Somewhere near the middle of the glacier, his next stomp hit not snow but a rock. The lead hunter lost his footing and fell. Down the crusty glacier he zipped, off the edge and out of sight.
Unable to help, his companion watched him slide away. After a while, he shouted out, "Are you OK?"
"Yes!" came the answer.
Reasoning that it was a quick way off the glacier, the second hunter plopped down and accelerated down the ice, following his friend. There, just over the edge of the glacier, was his friend...holding onto the top of a tree that barely protruded from the snow.
There were no other treetops nearby, nothing to grab, nothing but a hundred-foot drop onto the rocks below. As the second hunter shot past the first, he uttered his final epitaph: a single word, which we may not utter lest our mothers soap our mouths.
Monday, March 7, 2011
Windows Azure AppFabric and Integration with CRM 2011 Online
This post describes how to send messages in real time from CRM 2011 Online back to a company premises using Windows Azure. If you were using CRM 2011 On Premise you would use a plug-in and attach it to the create or update event of an entity. The plug-in code might write out the mesage in XML format for integration with an internal system. So how do you do this with CRM 2011 Online? The answer is to use the Windows Azure AppFabric because there is built in support for this in CRM 2011 Online.
Basically what you need to do is create a Service Bus on Windows Azure and then create a namespace. So you need to go through the steps of creating a Windows Azure account and set up a unique namespace. That will generate a "Current Management Key" whic you will need when you register the endpoint in the Plugin-tool. This post has a good description on how to do it.
You need to log into CRM Online and dowlnload the certificate that you need for the Plugin. Goto Settings -> Customizations -> Developer Resources and select download certificate. Make a note of the Issuer name directly above the link (e.g. crm4.dynamics.com).
Back in your development environment you will need to ensure you have the Windows Azure SDK and CRM 2011 SDK installed. You can use the sample code provided in the CRM SDK located at C:\CRMSDK\sdk\samplecode\cs\azure\onewaylistener. This is the application that will listen for messages that are sent to the Azure endpoint. The class implements IServiceEndpointPlugin and the class name serves as your "Contract" (you will use it as the "Path" when you configure the endpoint).
Open the CRM Plugin tool and configure a Service EndPoint. This post describes how to configure the Service Endpoint. The Solution Namespace is the same as the namespace you created in Azure. The Path is where you enter the class name of your listener application. Click Save and Configure ACS. You will be prompted for the location of the certificate and the issuer name. You are also prompted for the Management Key and this is current Management Key from the Windows Azure namespace. Click on "Save and verify authentication" which should verify the connection with a success message. Click on "Close".
Beneath the Service Endpoint you can now add a Step and configure it as you would have done for an On Premise plugin only now the event handler is the service endpoint you just created. You could for example set the plugin to fire on the Create event for the Contact entity.
That done you can now configure the app.comfig with your (Azure) Service Namespace, Isssuer Secret (your Azure Management Key) and the Service Path which is the name of your listener class. Run your OneWayListener application, and that should open a connection to your Azure endpoint and will print out messages as they are received to the console window.
So now go to CRM 2011 Online and perform the action that your Plugin is attached to (e.g. create contact). After you save the Contact, the Asynchronous process runs and your listener will receive the message.
The Onewaylistener code uses a RemoteExecutionContext object which has all the information you need from the User Guid to the Organization Guid. The interesting stuff is in the context.InputParameters collection. It contains all the data you entered. Its a simple task to turn this into XML and then you are all set to integrate with whatever you want.
Basically what you need to do is create a Service Bus on Windows Azure and then create a namespace. So you need to go through the steps of creating a Windows Azure account and set up a unique namespace. That will generate a "Current Management Key" whic you will need when you register the endpoint in the Plugin-tool. This post has a good description on how to do it.
You need to log into CRM Online and dowlnload the certificate that you need for the Plugin. Goto Settings -> Customizations -> Developer Resources and select download certificate. Make a note of the Issuer name directly above the link (e.g. crm4.dynamics.com).
Back in your development environment you will need to ensure you have the Windows Azure SDK and CRM 2011 SDK installed. You can use the sample code provided in the CRM SDK located at C:\CRMSDK\sdk\samplecode\cs\azure\onewaylistener. This is the application that will listen for messages that are sent to the Azure endpoint. The class implements IServiceEndpointPlugin and the class name serves as your "Contract" (you will use it as the "Path" when you configure the endpoint).
Open the CRM Plugin tool and configure a Service EndPoint. This post describes how to configure the Service Endpoint. The Solution Namespace is the same as the namespace you created in Azure. The Path is where you enter the class name of your listener application. Click Save and Configure ACS. You will be prompted for the location of the certificate and the issuer name. You are also prompted for the Management Key and this is current Management Key from the Windows Azure namespace. Click on "Save and verify authentication" which should verify the connection with a success message. Click on "Close".
Beneath the Service Endpoint you can now add a Step and configure it as you would have done for an On Premise plugin only now the event handler is the service endpoint you just created. You could for example set the plugin to fire on the Create event for the Contact entity.
That done you can now configure the app.comfig with your (Azure) Service Namespace, Isssuer Secret (your Azure Management Key) and the Service Path which is the name of your listener class. Run your OneWayListener application, and that should open a connection to your Azure endpoint and will print out messages as they are received to the console window.
So now go to CRM 2011 Online and perform the action that your Plugin is attached to (e.g. create contact). After you save the Contact, the Asynchronous process runs and your listener will receive the message.
The Onewaylistener code uses a RemoteExecutionContext object which has all the information you need from the User Guid to the Organization Guid. The interesting stuff is in the context.InputParameters collection. It contains all the data you entered. Its a simple task to turn this into XML and then you are all set to integrate with whatever you want.
Labels:
Cloud,
CRM 2011,
Integration,
Plugins,
Windows Azure
Saturday, March 5, 2011
Connecting to CRM 2011 in the Cloud
Connecting to CRM 2011 in the Cloud is trickier than connecting to CRM 2011 On Premise.
There is a great article by Deepak Kumar but it is worth recapping on his points.
Check c:\Program Files to see if you have Windows Identity Foundation. If not you will need to install it.
You need to get some device credentials and this is done using a utility in the CRMSDK called DEVICEREGISTRATION. Go to \sdk\tools\deviceregistration directory and load deviceregistration.csproj. Compile the application and navigate to the \bin\debug folder. Open a command window and type
deviceregistration.exe /operation:Register
The Device ID and Password that are generated are used in the method GetDeviceCredentials (that comes next).
Preparation work done you can now create your VS 2010 project, in this example I am using a WCF Service (I left the default name of Service)
Copy the CRMServiceHelper.cs file from \sdk\samplecode\cs\helpercode into your project and modify three methods:
GetDeviceCredentials
GetUserLogonCredentials
GetServerConfiguration
I used the code in Deepak Kumar's blog and set my Windows LiveID credentials in the GetUserLogonCredentials method. When you set the endpoints in GetServerConfiguration be aware that endpoints vary depending where you are in the world so see this blog .
Then if you want Intellisense on your custom entities and attributes you need to generate a class with them all in using CRMSVCUTIL.EXE. This tool is in the SDK too in the \sdk\bin directory. Amend your Environment Path so you have a reference to its location. Open a Command window, navigate to where you want the class to be generated then use
CrmSvcUtil.exe /url:https://{organisation}.crm4.dynamics.com/XRMServices/2011/Organization.svc /out:GeneratedCode.cs /username:"windows live id" /password:"live id password" /deviceid:"deviceid" /devicepassword:"device password"
Add the class you just generated to your project. OK, this takes a while and you will have to keep regenerating it every time you add an attribute but think of the time you'll save by having fewer bugs.
You will need to add references to the CRM SDK dlls (microsoft.crm.sdk.proxy.dll, microsoft.xrm.sdk.dll and microsoft.crm.sdk.dll) and then add these using statements
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Sdk.Messages;
Add 3 declarations in the class Service.
private OrganizationServiceContext context;
private OrganizationServiceProxy proxy;
private IOrganizationService service;
In the class constructor add a call to ConsumeIOrganization().
public Service()
{
ConsumeIOrganization();
}Then add two methods
public void ConsumeIOrganization()
{
ServerConnection serverConnection = new ServerConnection();
ServerConnection.Configuration serverConfig = serverConnection.GetServerConfiguration();
Connect(serverConfig);
}
public void Connect(ServerConnection.Configuration serverConfig)
{
try
{
using (proxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri,
serverConfig.Credentials, serverConfig.DeviceCredentials))
{
proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
context = new OrganizationServiceContext(proxy);
service = (IOrganizationService)proxy;
}
}
catch (FaultException <microsoft.xrm.sdk.organizationservicefault>)
{
throw;
}
}
Now you can add your code and reference service or context methods depending on which you prefer. Welcome to programming in the cloud!
There is a great article by Deepak Kumar but it is worth recapping on his points.
Check c:\Program Files to see if you have Windows Identity Foundation. If not you will need to install it.
You need to get some device credentials and this is done using a utility in the CRMSDK called DEVICEREGISTRATION. Go to \sdk\tools\deviceregistration directory and load deviceregistration.csproj. Compile the application and navigate to the \bin\debug folder. Open a command window and type
deviceregistration.exe /operation:Register
The Device ID and Password that are generated are used in the method GetDeviceCredentials (that comes next).
Preparation work done you can now create your VS 2010 project, in this example I am using a WCF Service (I left the default name of Service)
Copy the CRMServiceHelper.cs file from \sdk\samplecode\cs\helpercode into your project and modify three methods:
GetDeviceCredentials
GetUserLogonCredentials
GetServerConfiguration
I used the code in Deepak Kumar's blog and set my Windows LiveID credentials in the GetUserLogonCredentials method. When you set the endpoints in GetServerConfiguration be aware that endpoints vary depending where you are in the world so see this blog .
Then if you want Intellisense on your custom entities and attributes you need to generate a class with them all in using CRMSVCUTIL.EXE. This tool is in the SDK too in the \sdk\bin directory. Amend your Environment Path so you have a reference to its location. Open a Command window, navigate to where you want the class to be generated then use
CrmSvcUtil.exe /url:https://{organisation}.crm4.dynamics.com/XRMServices/2011/Organization.svc /out:GeneratedCode.cs /username:"windows live id" /password:"live id password" /deviceid:"deviceid" /devicepassword:"device password"
Add the class you just generated to your project. OK, this takes a while and you will have to keep regenerating it every time you add an attribute but think of the time you'll save by having fewer bugs.
You will need to add references to the CRM SDK dlls (microsoft.crm.sdk.proxy.dll, microsoft.xrm.sdk.dll and microsoft.crm.sdk.dll) and then add these using statements
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Sdk.Messages;
Add 3 declarations in the class Service.
private OrganizationServiceContext context;
private OrganizationServiceProxy proxy;
private IOrganizationService service;
In the class constructor add a call to ConsumeIOrganization().
public Service()
{
ConsumeIOrganization();
}Then add two methods
public void ConsumeIOrganization()
{
ServerConnection serverConnection = new ServerConnection();
ServerConnection.Configuration serverConfig = serverConnection.GetServerConfiguration();
Connect(serverConfig);
}
public void Connect(ServerConnection.Configuration serverConfig)
{
try
{
using (proxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri,
serverConfig.Credentials, serverConfig.DeviceCredentials))
{
proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
context = new OrganizationServiceContext(proxy);
service = (IOrganizationService)proxy;
}
}
catch (FaultException <microsoft.xrm.sdk.organizationservicefault>)
{
throw;
}
}
Now you can add your code and reference service or context methods depending on which you prefer. Welcome to programming in the cloud!
Labels:
Cloud,
CRM 2011,
data connections,
Online,
OrganizationService,
Windows Live ID
Subscribe to:
Posts (Atom)