Wednesday, March 30, 2005

VS.NET wizards and "Library not registered"

I have had an annoying problem with VS.NET 2003 for several months now, after uninstalling some trial add-ins for Office. The problem is that all of the C# 'add new' wizards in VS.NET would fail with "library not registered", e.g. I could not add a new project, a new class, etc. So I have had to copy and modify existing files to be able to add new stuff to my solutions.

After a lot of googling, I tried all of the solutions suggested on the 'net:

  • registering the C++ COM component \Vc7\vcpackages\csproj.dll
  • registering all the COM components in \Microsoft Visual Studio .NET 2003\ including all sub directories
  • repairing VS.NET
  • uninstalling VS.NET, reboot, installing VS.NET, reboot
...but none of these actions helped me. The first action is what fixes this problem in most cases.

When the problem was not fixed even after a complete reinstall of VS.NET, I finally tried installing Windows Script 5.6, even if I thought that it seemed like a far shot. And know what; it actually fixed the problem! After several hours of grief, I am now able to create a new Infopath project in VS.NET.

Monday, March 21, 2005

Customizing IBF for MSCRM

I have been playing around with the IBF 1.0 package for MSCRM 1.2 to find out how we can modify it to fit the needs of our customers. It is quite easy to modify the XML as described on MSDN, but the article is mostly cut'n'paste with very brief explanations.

What I would like to achieve is to add a hyperlink to a WSS team site in the account details view in the IBF task pane. We use one SharePoint team site per MSCRM record applicable for collaboration (e.g. account) in our solution. This allows for collaboration on both structured (MSCRM data) and unstructured (documents, e-mails, meetings, etc) information needed in the business processes, such as lead qualification and opportunity management. The hyperlink in the Office 2003 task pane will allow for easy access to the team sites from Word and Outlook.

I cannot find out how to add working hyperlinks to the IBF view. MSCRM IBF does not use HTML regions, but rather uses custom region types. After a bit of looking around in the XML definitions used in the above article (mailto and URLs), I added this code:

< xsl:template match="account/accountid" >
< iwbui:FormItem name="GotoAccountTeamSite" type="URL" Caption="Team site:"
Value="
http://server/sites/{substring(.,2,36)}/" >
< /iwbui:FormItem >
< /xsl:template >

It looks like a duck, walks like a duck, but it does not quack like a hyperlink :) The URL is correct, the text is blue, underlined and has the hand cursor, but it never navigates. Maybe the MSCRM custom regions does not support hyperlinks ? None of the mailto: links in the MSCRM IBF package works either. It would also be nice to allow for setting target="blank".

I guess I will add a IBF 'title bar' menu item (custom region menu) to provide the users with the navigation option, just like the standard 'Go to Microsoft CRM record' menu item. This approach is also recommended at MSDN/OfficeZealot.

I have googled quite a bit to find detailed documentation on the IBF UI schema without success. There is a lot of information about IBF on MSDN and elsewhere, but most of it is too general for problem solving. The most useful introduction to the inner details of IBF that I found was this: “Building a Simple IBF solution – For Beginners”.

PS! be careful about installing the IBF 1.5 client if you use the MSCRM IBF package as this can cause your smart tags and other stuff to stop working. Microsoft is aware of these problems and will hopefully release a fix before too long.

Friday, March 18, 2005

Outlook .NET add-ins connected to Exchange services

In some solutions we implement Outlook COM add-ins with .NET using the templates provided by Microsoft on MSDN. We typically add some menus, toolbars and dialog boxes that allow the users to manage stuff in their mailboxes or public folders, using client-side CDO. To avoid the Outlook security popup we utilize the Redemption component, which is widely recommended on the 'net for this purpose.

In addition, we sometimes need to access extended MAPI properties that are not exposed in CDO, and Redemption gives us easy access to these properties and other stuff that is not supported by client-side CDO. Redemption is a component that is really useful, so is the OutlookSpy tool. Recommended!

In an earlier posting, I wrote about how we prefer to implement code for managing ActiveDirectory and Exchange and its mailbox and public folder stores as server-side services. These services uses e.g. CDOEX and CDOEXM to perform the logic provided by our services.

One of our service methods is to copy a mail message from a mailbox store to a public folder store in response to an action initiated by an Outlook user through our COM add-in. This is not easy to do using CDO because it involves separate stores and also involves having sufficient permissions on the stores. This operation is, however, quite easy to implement using Exchange OleDB on the server-side and impersonating a specific identity that has the required rights. The ExOleDB methods for operating on different items in the store requires either the HTTP: or the File: URL Scheme, of which Microsoft recommends the HTTP: scheme.

The HTTP: URL scheme for mail messages uses the .EML name of the message item, and this URL is easy to see when using Outlook Web Access (OWA). This URL is available only through extended MAPI, thus we use Redemption to get it from the message item:

//get MAPI IMessage propery["PR_URL_COMP_NAME"]
int PR_URL_COMP_NAME = 0x10F3001E;
Redemption.SafeMailItem message = new Redemption.SafeMailItemClass();
message.Item = this._mailItem;
string url = message.get_Fields(PR_URL_COMP_NAME).ToString();

The string returned by Redemption is just the 'name' component of the URL (i.e. the message item subject). It must be encoded before passing it to the Exchange service for processing by ExOleDB:

//removed code from sample:
//
handle that Redemption returns '?' for '/'
//and other quircks with the char encoding

//encode message item URL
url = url.Replace("/", "_xF8FF_");
url = Util.MailUrlEncode(url);
//clean up
Redemption.MAPIUtils util = new Redemption.MAPIUtilsClass();
util.Cleanup();

Note that not all characters in the URL needs to be encoded, and I have some small utility functions to handle the encoding:

public static string MailUrlEncode(string input)
{
string output="";
char[] uniChars = input.ToCharArray();
foreach(char uniChar in uniChars)
{
if(uniChar <= 'z') output += uniChar.ToString(); else
output += Util.ExchangeEncode(uniChar.ToString());
}
return output;
}

public static string ExchangeEncode(string uniChar)
{
string output="";
byte[] encoded = System.Text.Encoding.UTF8.GetBytes(uniChar);
foreach(byte utf8Char in encoded)
{
output += "%" + utf8Char.ToString("X2");
}
return output;
}

Finally, the URL must be prepended with the path to the message item store (mailbox or public folder) before it becomes a complete URL that ExOleDB can operate on.

The server-side ExOleDB code for copying the message item and its attachment is not included here. I might publish a posting on this later on.

Monday, March 14, 2005

Character encoding in SPS2003 and regional settings

Most of our SharePoint installations, both SPS and WSS, uses the Norwegian version or at least require that areas, surveys, listings and fields added to SPS can handle Norwegian characters (ÆØÅ). The character encoding is configured in WEB.CONFIG using the globalization element, and this defaults to fileEncoding="utf-8". What ever you do, do not change this into e.g. fileEncoding="iso-8859-1" to get support for the encoding you use in your web parts.

The problem you can get into is that SPS requires unicode text (utf-8 encoding) and when a listing field or a web part contains non-unicode text (e.g. iso-8859-1 encoded), this will cause the unicode parsing to fail because the Norwegian characters signals an escape sequence to the parser, which it really is not. The parser detects an invalid utf-8 escape sequence and outputs a ? instead of the expected and following characters. This is very annoying in listing field names as it causes the list rendering to fail and then the list cannot be modified to fix this problem.

We have experienced problems with the encoding when deploying web parts that are really ASP.NET user controls. To make this kind of web parts render correctly, and allow the standard SPS stuff to work correctly with ÆØÅ, you must ensure that the globalization element has this configuration:

fileEncoding="utf-8" requestEncoding="utf-8" responseEncoding="utf-8"

Then you must ensure that your ASP.NET user control's .ASCX file uses unicode as the encoding by using "File - Advanced Save Options" in VS.NET. Select "Unicode (UTF-8 with signature) - Codepage 65001" as the encoding, then save the file. Switch to HTML view if the menu item is not visible in design mode. The same procedure must be applied to all .ASCX and .ASPX files that you use as part of a SPS solution. Read more about Unicode in ASP.NET in this blog.

In addition, you must always set the correct regional settings for your SPS portal site (once for all areas) and for all your WSS team sites (on every single site).

Note that the WEB.CONFIG file is replaced when restoring a SharePoint portal site, and this will cause all your changes to the configuration to be lost. Always make a backup copy of WEB.CONFIG before restoring portal sites.

Friday, March 04, 2005

Unable to deploy SharePoint web parts added to a package

There are several things that are easily forgotten when adding a new web part to a package, that can cause the web part to not show up in MSIE when trying to add it to a web part page. Typically the new .DWP file does not have 'Build Action = Content', or it is not added to Manifest.XML, or the SafeControls settings are not correct, or someting simple as a typo somewhere. And the new web parts are not added and are missing from the \WPCatalog\ folder when you run the STSADM tool.

I recently had to go to a customer to add some new stuff to a solution that had been implemented by someone else, and had just such a problem. The deployment script looked like this:

stsadm -o deletewppack -name Objectware.Lawyer.WebPartsSetup.CAB

stsadm -o addwppack -filename Objectware.Lawyer.WebPartsSetup.CAB

The package is first removed and then added by the above script, but my new stuff did not show up. I tried several times, and even watched \WPCatalog\ during deployment and saw that the web parts were removed and added, but not the new web parts. I ensured that it was actually my newly built .CAB that was deployed, but still no success.

I had never had this problem before, so I took at look at some deploy scripts I had made myself in other projects, and then I found a small, but important, difference:

stsadm -o addwppack -filename Objectware.MsCrmSales.WebParts.CAB -force

By adding -force to the STSADM command, my new web parts were successfully deployed to the SPS/WSS site. It seems that it is not enough to first remove, then add a web part package, to update its information in the web part gallery. The -force parameter is what actually makes it possible to reinstall a package.

From the WSS admin guide: Use the force parameter to overwrite an existing Web Part package with a new version, or to repair a Web Part package by reinstalling it.

Wednesday, March 02, 2005

Get AD objects by GUID using System.DirectoryServices

All objects in ActiveDirectory are uniquely identified by their distinguishedName or objectGuid attributes. One of these identifiers are typically used to store references to an AD object and to retrieve it. The strenght of the GUID is that it stays the same even when an object is moved and thus gets a new distinguishedName. We typically stores an AD object GUID in a SQL Server database in the standard .NET GUID format (DirectoryEntry.Guid) in uniqueidentifier columns.

Searching for an object by a standard .NET GUID using the DirectorySearcher class is not as straightforward as you may think. A GUID must be formatted as an "octet string" (ActiveDirectory native format) to be able to use it in a DirectorySearcher filter as a filter clause. I have used this function to convert a standard GUID string to an octet string:

public static string Guid2OctetString(string objectGuid)
{
System.Guid guid = new Guid(objectGuid);
byte[] byteGuid = guid.ToByteArray();
string queryGuid = "";
foreach(byte b in byteGuid)
{
queryGuid += @"\" + b.ToString("x2");
}
return queryGuid;
}

Note that if you have a DirectoryEntry object instead of just a standard GUID string, then the .NativeGuid will give you the octet string.

All AD objects have a specific class such as "user", "contact" or "group", which helps on narrowing the number of entries to search through. Always apply an objectClass filter in your AD searches. With the GUID, octet string method and the objectClass at hand, looking up the AD object is done like this:

. . .
string adPath = "LDAP://DC=myco,DC=local"
DirectoryEntry adRoot = new DirectoryEntry(adPath);
Object adsiObj = adRoot.NativeObject; //Bind to the native AdsObject to force authentication


DirectorySearcher adSearch = new DirectorySearcher(adRoot);
string queryGuid = Guid2OctetString(objectGuid);
adSearch.Filter = "(&(objectClass=" + objectClass + ")(objectGUID=" + queryGuid + "))";


SearchResult adResults = adSearch.FindOne();
adObject = adResults.GetDirectoryEntry();
. . .


Applying a GUID filter clause to the objectGUID attribute in this example is just for illustration purposes.

To lookup the AD object identified by the GUID simply use the octet string (.NativeGuid) in the DirectoryEntry constructor (see Binding Using GUID in the DirectoryService SDK):

DirectoryEntry adObject= new DirectoryEntry("LDAP://<" + "GUID=" + nativeGuid + ">")

Please let me know if the .NET Guid .ToString() has a format argument I have missed that produces the octet string format.