The plugindeveloper tool as far as I know does not support automatic deployment of secure configurations, which is a bit annoying if you need to auto deploy a big bunch of plugins which do have secure configurations. With that in mind, I've modified the tool as found in the Microsoft SDK and with a few questionable steps, have got this to work. This will work on solutions exported from the pluginregistration tool.
The other change is that it has been updated to read the password from the register.xml file to allow for continuous integration and automatic deployment to work.
Now we've tested it on this project, and it has so far worked absolutely fine with no problems, but bear in mind that it may not always work, so if you're going to use this, use it at your own risk!
Source is provided so you can check it yourself - you have been warned!!
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Download solution here
Friday, 12 February 2010
Friday, 28 November 2008
Your very own CSV file parser
So I recently found that apparently there's no support for the Microsoft Text Driver on 64 bit machines. In a nutshell, that meant all my applications which relied on it to parse CSV files and return them as a DataTable no longer worked. So up went the sleeves and out came this code snippet to manually do that parsing. This code makes the assumption that the CSV file is produced by Excel, the first row is a header row and there are no "s to encapsulate cells in the header row (all assumptions I could make in the process of writing this because I needed a quick fix). Hopefully this is easy enough to understand and modify for your own needs, but feel free to ask questions!
string COMMA = "INCCOMMAREPLACEMENT57";
string SEPA = "INCSEPARATOR57";
string[] splitstring = new string[] { SEPA };
DataTable t = new DataTable();
string[] lines = File.ReadAllLines(csvDirectory + "\\" + csvFileName);//read in file
//first line is header
string[] headers = lines[0].Split(',');
//for each subsequent row
//for row 0: add them as column headers - we know line 0 has no ""
foreach (string s in headers)
{
t.Columns.Add(s, typeof(string));
}
//foreach line
for (int i = 1; i < lines.Length; i++)
{
int first = 9999;
int second = 9999;
string s = lines[i];
//continue doing this until no more "" in the line
while (first > -1)
{
//search for "
first = s.IndexOf('"');
if (first > 0)
{
//search for closing "
second = s.IndexOf('"', first + 1);
string subS = s.Substring(first, second - first + 1);
//between the two "" find all , and replace with INCCOMMAREPLACEMENT57
string subS2 = subS.Replace(",", COMMA);
//remove the two ""
subS2 = subS2.Replace("\"", "");
s = s.Replace(subS, subS2);
}
else
{
break;
}
}
//replace all , with INCSEPARATOR57
s = s.Replace(",", SEPA);
//replace INCCOMMAREPLACEMENT57 with ,
s = s.Replace(COMMA, ",");
//split the line using INCSEPARATOR57
string[] data = s.Split(splitstring, StringSplitOptions.None);
DataRow r = t.NewRow();
for (int j = 0; j < data.Length; j++)
{
r[j] = data[j];
}
t.Rows.Add(r);
}
return t;
Thursday, 20 November 2008
Firing callouts on receipt of emails into a CRM system
Here's a cool little callout we wrote over here at Increase to create a new contact whenever we receive an email from an unknown person into our CRM system. It'll check to make sure it's not creating any duplicate contacts, and you can extend it to do all sorts of things with the contact that sent you that email. You could create a support case, fire off a thank you email, add them to a marketing list...completely up to you, really! I assume you know how to write a callout so here's the code we used. Register this callout on the post stage of the DeliverIncoming message to fire on receipt of an incoming email. You could also adapt this to fire on the DeliverPromote, which fires when you track an email from Outlook into CRM. Basically, if you use your imagination, emails in CRM will never be the same again! Code as below:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using System.Diagnostics;
using Microsoft.Crm.SdkTypeProxy;
public class CreateContact : IPlugin
{
public void Execute(IPluginExecutionContext context)
{
Guid emailId = new Guid();
if (context.MessageName == MessageName.DeliverIncoming)
{
emailId = (Guid)context.OutputParameters.Properties["EmailId"];
}
CrmService incService = context.CreateCrmService();
email thisEmail = (email)incService.Retrieve(EntityName.email.ToString(), emailId, new AllColumns());
Guid contactid = new Guid();
//Ensure this email is from someone
if (thisEmail.from != null && thisEmail.from.Length > 0)
{
activityparty from = thisEmail.from[0];
if (from.partyid == null)
{
//I've noticed that sometimes even though the partyid is null there's already a contact with the related email address, so do an additional check just in case
ColumnSet contactcol = new ColumnSet();
contactcol.AddColumn("emailaddress1");
contactcol.AddColumn("contactid");
QueryByAttribute q = new QueryByAttribute();
q.Attributes = new string[] { "emailaddress1", "statecode" };
q.ColumnSet = contactcol;
q.EntityName = EntityName.contact.ToString();
q.Values = new object[] { from.addressused, ContactState.Active.ToString() };
BusinessEntityCollection contacts = incService.RetrieveMultiple(q);
//That should be a list of all active contacts with the particular emailaddress1, extend this to as many emailaddress fields as you like
if (contacts.BusinessEntities.Count > 0)
{
if (contacts.BusinessEntities.Count == 1)
{
//So there actually is a contact with this email address
contactid = ((contact)contacts.BusinessEntities[0]).contactid.Value;
}
else
{
//You might want to flag up the fact that there's more than one contact with this email address
contactid = ((contact)contacts.BusinessEntities[0]).contactid.Value;
}
}
else
{
//We'll need to create a new contact with this email address
contact c = new contact();
c.emailaddress1 = from.addressused;
c.lastname = from.addressused.Remove(from.addressused.IndexOf('@'));
contactid = incService.Create(c);
}
//we will need to add the partyid no matter what since it is currently null and that shouldn't be the case
thisEmail.from[0].partyid = new Lookup();
thisEmail.from[0].partyid.Value = contactid;
thisEmail.from[0].partyid.type = EntityName.contact.ToString();
incService.Update(thisEmail);
}
else
{
contactid = from.partyid.Value;
}
//Do all sorts of cool stuff with the contact id you've just extracted/created.
}
}
}
Friday, 3 October 2008
Microsoft CRM on the Nintendo Wii?
So a completely random (and probably pointless) thought occurred to me as I was walking home from the Lab last night. Would we be able to get Microsoft CRM working on the Wii's Internet channel? I managed to check my work email on the Wii the other day, really loving the wireless keyboard support I recently discovered (it's great - I now keep the keyboard next to the sofa, and we never let a pointless argument last longer than 5 minutes before pulling it out and doing a quick Goog Live Search and proving the superiority of my opinion), and how cool would it be to be able to use CRM on the Wii? And then I realised - CRM does a browser compatibility check, and if it didn't work on Firefox, my guess is it won't work on Opera. But then homebrew applications allow for all sorts of unsupported custom apps running on the Wii, surely something similar would allow for MS CRM?
But why would people want to use a gaming machine for CRM? Check on campaign responses in between games of Mario Kart or Wii Sports tennis at home? Or would it be THE ultimate excuse to get a Wii for the office? Honestly speaking, it probably would be quite a pointless exercise, so why try it? My answer to that would have to be because I can. And that's when another thought struck me. I am an addict.
But why would people want to use a gaming machine for CRM? Check on campaign responses in between games of Mario Kart or Wii Sports tennis at home? Or would it be THE ultimate excuse to get a Wii for the office? Honestly speaking, it probably would be quite a pointless exercise, so why try it? My answer to that would have to be because I can. And that's when another thought struck me. I am an addict.
Wednesday, 30 July 2008
Hiding borders on IFrames with Related Regarding objects
I won't bore you with the details of how to add an iFrame that will display related regarding objects on the parent object's form, and why you might want to do that - there've already been plenty of blogs out there about that (Eg Huib Aarts' article on the subject). However, I've noticed that when I implemented my version of it, the pesky borders wouldn't go away, and the article linked to has some code in it that is meant to make the borders disappear - unfortunately, it didn't work for me, and I'm not sure why. So rather than sit through and debug it, I modified that code so that now, instead of modifying the style elements of those tables, it just rips up the child table with the important information and dumps it in the main body of the iFrame. Problem solved. There's still a slight border to the right that I can't get rid of, but if you turn the border displays off it's barely noticeable and I can live with that.
Here's the code if you're interested:
var frameName = 'IFRAME_Name';
var objFrame = document.getElementById(frameName);
var objWindow = document.frames[frameName];
objFrame.allowTransparency=true;
objFrame.onreadystatechange = function ()
{
if (objWindow.document.readyState=='complete')
{
objWindow.document.body.style.backgroundColor='transparent';
if (objWindow.document.getElementsByTagName('BODY')[0]!=null)
{
objWindow.document.getElementsByTagName('BODY')[0].style.padding='0px';
if (objWindow.document.getElementsByTagName('TABLE')[0] != null)
{
if (objWindow.document.getElementsByTagName('TABLE')[0].getElementsByTagName('TD')[0] != null)
{
objWindow.document.getElementsByTagName('BODY')[0].innerHTML = objWindow.document.getElementsByTagName('TABLE')[0].getElementsByTagName('TD')[0].innerHTML;
}
}
}
}
}
Here's the code if you're interested:
var frameName = 'IFRAME_Name';
var objFrame = document.getElementById(frameName);
var objWindow = document.frames[frameName];
objFrame.allowTransparency=true;
objFrame.onreadystatechange = function ()
{
if (objWindow.document.readyState=='complete')
{
objWindow.document.body.style.backgroundColor='transparent';
if (objWindow.document.getElementsByTagName('BODY')[0]!=null)
{
objWindow.document.getElementsByTagName('BODY')[0].style.padding='0px';
if (objWindow.document.getElementsByTagName('TABLE')[0] != null)
{
if (objWindow.document.getElementsByTagName('TABLE')[0].getElementsByTagName('TD')[0] != null)
{
objWindow.document.getElementsByTagName('BODY')[0].innerHTML = objWindow.document.getElementsByTagName('TABLE')[0].getElementsByTagName('TD')[0].innerHTML;
}
}
}
}
}
Friday, 25 July 2008
Calling CRM Webservices via Javascript
I used to be really intimidated by the idea of calling the CRM Webservice via JavaScript, but recently I have had the opportunity (read: didn't have a choice) to use it a lot, both in internal projects and for customer systems. And I realise now how stupid I was in keeping my head in the sand - the JS required to call the webservice does LOOK rather intimidating...but with some help from a fantastic tool on Stunnware (http://www.stunnware.com/crm2/topic.aspx?id=JSWebService), I soon found that, much like myself, it looks a lot worse than it actually is. I've now got a copy of that tool on my desktop set up so that I can quickly switch between the CRM3 and CRM4 versions, and am actually starting to learn to write the XML from scratch (or at least edit it to do different things without having to go through the tool).
There's just so many possibilities that this opens up. Some of the applications we've had for it include:
There's just so many possibilities that this opens up. Some of the applications we've had for it include:
- Counters for related records (much like Outlook mail folders) - one caveat is that the more of these counters you want to have, the higher the overhead and therefore the longer the page takes to load
- Autopopulating fields - for example populating a Customer field with the parent customer of a responsible contact in the Incident entity upon population of the responsible contact
- Summarising details from other entities on the header of another entity - best explained with a screenshot:
The circled data is data pulled from 2 different entities, linked by the contact - and it allows our support desk personnel to quickly find out the support level on a given customer's contract.
Wednesday, 29 August 2007
Deploying an Outlook 2007 application written with VSTO 2005 SE
I've just spent a ridiculously long time trying to deploy an add-in for Outlook 2007 running on Windows Vista, written in C# with Visual Studio 2005 Tools for Office Second Edition. There's a pretty good walkthrough on MSDN, but it's got way too much information that's irrelevant (ie for Word and Excel document add-ins) and the print is just so small when it's printed...!
Anyway, after scouring a few blogs, I managed to get it to work, so for the convenience of anyone else attempting this, here are the steps I took, along with the pitfalls I encountered. For the purposes of this, I'm making the same assumptions as in the MSDN Walkthrough, ie:
You will need:
The MSI included with the MSDN walkthrough
(http://go.microsoft.com/fwlink/?linkid=83721)
Windows SDK Components for Windows Installer Developers
(You only really need WiRunSQL.vbs, which can be found in C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\SysMgmt\MSI\scripts after you install this in the default directory)
(http://www.microsoft.com/downloads/details.aspx?familyid=C2B1E300-F358-4523-B479-F53D234CDCCF&displaylang=en)
Aaron Stebner's script for setting the NoImpersonate flag
(http://blogs.msdn.com/astebner/archive/2006/10/23/mailbag-how-to-set-the-noimpersonate-flag-for-a-custom-action-in-visual-studio-2005.aspx)
Office 2007 Primary Interop Assemblies
(http://www.microsoft.com/downloads/details.aspx?familyid=59daebaa-bed4-4282-a28c-b864d8bfa513)
Office 2003 Primary Interop Assemblies (optional, but I put them in anyway)
(http://www.microsoft.com/downloads/details.aspx?familyid=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad)
VSTO Runtime Redistributable
(http://go.microsoft.com/fwlink/?linkid=49612)
------------------------------------------------------------------------------------------------------------
As a reference, the walkthrough is still good reading for background, so here you go:
Part 1: http://msdn2.microsoft.com/en-us/library/bb332051.aspx
Part 2: http://msdn2.microsoft.com/en-us/library/bb332052.aspx
Okay, now for the steps:
Anyway, after scouring a few blogs, I managed to get it to work, so for the convenience of anyone else attempting this, here are the steps I took, along with the pitfalls I encountered. For the purposes of this, I'm making the same assumptions as in the MSDN Walkthrough, ie:
-
There is only one customization assembly; there are no other referenced or satellite assemblies deployed with the solution.
-
Deployment is to a local file folder and not to some other location, such as a Web server or network file share.
-
Your solution does not use the optional deployment manifest.
You will need:
The MSI included with the MSDN walkthrough
(http://go.microsoft.com/fwlink/?linkid=83721)
Windows SDK Components for Windows Installer Developers
(You only really need WiRunSQL.vbs, which can be found in C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\SysMgmt\MSI\scripts after you install this in the default directory)
(http://www.microsoft.com/downloads/details.aspx?familyid=C2B1E300-F358-4523-B479-F53D234CDCCF&displaylang=en)
Aaron Stebner's script for setting the NoImpersonate flag
(http://blogs.msdn.com/astebner/archive/2006/10/23/mailbag-how-to-set-the-noimpersonate-flag-for-a-custom-action-in-visual-studio-2005.aspx)
Office 2007 Primary Interop Assemblies
(http://www.microsoft.com/downloads/details.aspx?familyid=59daebaa-bed4-4282-a28c-b864d8bfa513)
Office 2003 Primary Interop Assemblies (optional, but I put them in anyway)
(http://www.microsoft.com/downloads/details.aspx?familyid=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad)
VSTO Runtime Redistributable
(http://go.microsoft.com/fwlink/?linkid=49612)
------------------------------------------------------------------------------------------------------------
As a reference, the walkthrough is still good reading for background, so here you go:
Part 1: http://msdn2.microsoft.com/en-us/library/bb332051.aspx
Part 2: http://msdn2.microsoft.com/en-us/library/bb332052.aspx
Okay, now for the steps:
- Create your project, sign the assemblies and add the prerequisites to the setup project - all this is well-documented in the walkthrough. Don't forget to compile and copy the ComponentCheck. Relevant sections of the walkthroughs include:
Part 1: "Adding the Visual Studio Tools for Office Packages to the Bootstrapper", through to "To copy the packages into the bootstrapper" - Sign the assembly, add the prerequisites, and add the SetSecurity project to the solution. Relevant sections of the walkthroughs include:
Part 2: "Signing the Assembly" through to "To add the custom action data for the Uninstall Method"
Important note: The custom action data needs to be entered all in one line, and a space before every / kept. What I did was to copy the entire thing to notepad, edit it till it looked like this (I know it looks cut-off, but if you click on the start of the line and press shift + end, you should be able to highlight all of it for copying - putting it on separate lines would have made things ambiguous):/assemblyName="OutlookAddin.dll" /targetDir="[TARGETDIR]\" /solutionCodeGroupName="MyCompanyName.OutlookAddin" /solutionCodeGroupDescription="Code group for OutlookAddin" /assemblyCodeGroupName="OutlookAddin" /assemblyCodeGroupDescription="Code group for OutlookAddin" /allUsers=[ALLUSERS]and then pasted it into the CustomActionData field for the Install method. Ensure that the solutionCodeGroupName is the same for all three fields you modify. For those of you getting the "The specified solution code group name is not valid" error: this is probably the cause of it - make sure it's all in one line as above and you should be fine. - There is an excellent guide on how to do this bit at http://www.shahine.com/omar/VSTOAddinsAndVista.aspx
and given that I simply copied his steps, I really think you should take the trouble to head there and check it out.
Subscribe to:
Comments (Atom)