When I first heard about TECH Cocktail, I was surprised to see that such an organization even existed in Chicago - I didn't realize that there was such a vibrant technology community here!
TECH Cocktail has done a bunch of mixers in Chicago and has recently expanded to other cities. They're putting on the inaugural TECH Cocktail Conference on May 29th here in Chicago.
There's a nice lineup of speakers, and Jason Fried from 37 Signals is giving the keynote. I'm looking forward to that one, I'm a big fan of what 37 Signals is doing and really enjoyed reading about their take on software development in Getting Real.
In my continuing efforts to bring you useful nuggets from the Office Communicator SDK, I bring you IMessengerContactResolution.
I realized that I needed something like this when I wasn't able to resolve contacts given their SMTP address by using the IMessenger::GetContact method. The IMessenger::FindContact methods wasn't helpful because it actually invoked the Communicator's UI to find a contact.
I came across IMessengerContactResolution::ResolveName in the SDK, but saw that the ResolveName method was marked as Not Implemented. Well, it is ... I didn't find out until someone on the UC team sent me a code snippet that used it. Moral of the story: give it a shot even if the SDK says it's not implemented.
Scenario
The scenario I was working with is that a user could drag a contact out of Communicator into my application. What you get in the drag arguments happens to be the contact's primary SMTP address, which may or may not match their Sip Uri, e.g. john.doe@contoso.com vs. jdoe@contoso.com. A lot of organizations do this, and there's really no standard way to "discover" a user's Sip given their SMTP and vice versa.
My ResolveContact method tries to resolve its input string a couple of different ways. As usual, when programming against the Office Communicator SDK, you unfortunately have to control logic flow using try/catch statements. I feel dirty every time I do that, but hey, my hands are tied :)
ResolveContact
In my application, I carry around a class-level variable called _resolver to perform contact resolution:
private IMessengerContactResolution _resolver;
When I handle the Communicator sign-in event, I set up _resolver by casting my main communicator object to IMessengerContactResolution:
_resolver = _communicator as IMessengerContactResolution;
This is pretty common when writing applications that use the Office Communicator SDK, you of course are responsible for cleaning up those objects which I do when I handle the Communicator sign-out event.
Here's the code for my ResolveContact method:
void ResolveContact(string dropString, out string sipUri, out string displayName)
{
try
{
sipUri = null;
displayName = null;
try
{
// Scenario 1: User enters a Sip Uri
// If so, GetContactDetails will return a display name
//
// Code for this method not included,
// It simply called IMessenger::GetContact
GetContactDetails(dropString, out displayName);
}
catch
{
sipUri = null;
}
if (String.IsNullOrEmpty(displayName)) // Not a match based on Sip Uri
{
try
{
// Scenario 2: User enters an SMTP address
// Try to resolve the SMTP address into a Sip Uri
//
sipUri = _resolver.ResolveContact(ADDRESS_TYPE.ADDRESS_TYPE_SMTP,
CONTACT_RESOLUTION_TYPE.CONTACT_RESOLUTION_CACHED_ONLY,
dropString);
}
catch
{
sipUri = null;
}
// Not a match based on SMTP Address
if (String.IsNullOrEmpty(sipUri))
{
try
{
// Scenario 3: User enters a contact's display name
// Try to resolve the Display Name into a Sip Uri
//
sipUri = _resolver.ResolveContact(
ADDRESS_TYPE.ADDRESS_TYPE_DISPLAY_NAME,
CONTACT_RESOLUTION_TYPE.CONTACT_RESOLUTION_CACHED_ONLY,
dropString);
}
catch
{
sipUri = null;
}
}
// Success - get the contact's Display Name
if (!String.IsNullOrEmpty(sipUri))
{
GetContactDetails(sipUri, out displayName);
}
}
else
{
sipUri = dropString; // Scenario 1
}
}
catch (Exception exception)
{
throw exception;
}
}
I'm gonna chalk this up to one of things that's painfully obvious after the fact... I'm using the IMessengerAdvanced::StartConversation method of the Office Communicator automation SDK to dial ad-hoc phone numbers, e.g. simply dialing Clarity's front desk at +13128633100 as opposed to selecting a contact in Communicator and dialing one of its listed phone numbers.
Communicator would attempt to dial the number but the call to StartConversation would throw a not-so-helpful COMException.
vConversationData
The SDK documentation describes the StartConversation method, but is vague on its parameters. The trick here is to correctly populate the vConversationData parameter.
Here's what the SDK says about the vConversationData parameter:
A VARIANT value to hold a XML binary large object (BLOB) used to send data dependent on the chosen conversation type. For focus-based conference call, this parameter is used to pass in the conference URI as the content of a <ConfURI> element. For PSTN calls, the parameter contains an array of TEL URIs, as a <TelURIs> element.
Pretty misleading, because we won't be BLOB'ing anything!
Formatting Phone Numbers
Before being passed to the StartConversation method, phone numbers have to be prefixed with tel: to explicitly specify that this is a phone number.
As part of configuration Office Communications Server for voice, you can add regular expressions to normalize the way Communicator dials phone numbers, e.g. when I simply dial 39XX, Communicator knows that this is my Clarity extension and dials it as +131286339XX.
What this means is that if you have these rules in place, you don't need to worry much about normalizing the phone number before passing it to the StartConversation method.
Of course, if you specify a phone number that doesn't match any of your normalizing regular expressions, Communicator won't be able to make the phone call. Communicator handles this gracefully, you don't need to handle an exception in your code.
IMessengerAdvanced::StartConversation
Here's the obvious part:

The way the vConversation data document was phrased, I was trying stuff like:
<TelURIs><TelURI>tel:+13128633100</TelUri></TelURIs>
XML Blob, I think not ...
If you haven't seen Tafiti Visual Search, wander on over to CodePlex and check it out. Kevin Marshall and I contributed to the project by extending Tafiti to search against SharePoint. The Windows Live team recently made significant updates to the Tafiti project, and we just checked in a change set with the updated SharePoint search code. You can download the change set and configure it to search against your SharePoint portal.
The Microsoft Office Communicator 2007 SDK allows you to automate a running instance of Office Communicator 2007 to integrate Communicator functionality into your applications.
There are a bunch of other SDKs that the Unified Communication team puts out. The difference between them can get confusing, let's clear some things up for this specific SDK.
- You need to have Office Communicator 2007 installed and running
- The SDK works by automating functions of Communicator, e.g. when you start an IM conversation through code, a Communicator window actually launches
Check out the "what others are downloading" section of the download link above for links to some of the other SDKs.
The SDK comes with a help collection, but no sample applications. There are some good snippets of code in there, but it's pretty dry reading.
I put together a sample WinForms application that showcases some of the functionality that the SDK provides, it beats reading the help file to figure out what the difference between the IMessenger, IMessenger2, and IMessenger3 interfaces is.
The Visual Studio 2008 project includes some presence controls which you can download separately if you'd like.
I've worked on a couple of projects that involved using this SDK, here are some useful tips from stuff I've come across:
- Communicator allows you to add distribution lists as contacts, e.g. I add the Clarity "All Employees" group and automatically get everyone in Clarity on my buddy list. If you do this, you don't get the individual members of the group as contacts - they won't be in Messenger.MyContacts
- Clean up after yourself. This a COM automation API, use Marshal.ReleaseComObject to properly dispose of Communicator objects
- Various interfaces inherit from each other to provide more functionality, e.g. IMessenger3 inherits from IMessenger2 which inherits from IMessenger1
For a while now, I've been looking for a solution to sync my Google Calendar with Outlook. My wife and I have a Google Calendar on which we have "family events", she subscribes to it from iCal on her MacBook, and I subscribe to it from within Outlook and overlay it on top of my main calendar.
The only problem for me with this setup is that I don't get the items from the Google Calendar in my Outlook Calendar. I don't get reminders for them, and can't see them on my smart phone.
There's a few tools out there that do this, but after reading Jeff Atwood's post - A Question of Programming Ethics - about some unscrupulous applications, I'm hesitant to fork out my username and password to anybody.
Google recently released Google Calendar Sync to provide two-way or one-way sync between Outlook and Google Calendar and vice versa.
Configuration
Once installed, GCS puts an icon in your system tray which you can use to configure it.
One-way Sync from Google Calendar to Outlook
I was specifically interested in one-way sync from Google Calendar to Outlook, and set it to sync every 60 minutes (probably overkill).
The one-way sync works pretty well, but I found an annoying side-effect. Every time GSC performs a sync, I find a bunch of empty meeting requests in my Deleted Items folder in Outlook. They are all dated 12/31/1979 and are scheduled with random people from my company's Global Address Book.
Wonder what these are all for and why GSC is reaching into my Global Address Book ...
One-way Sync from Outlook to Google Calendar
Uneventful, same thing with the phantom meeting requests in the Deleted Items folder though. One of my colleagues mentioned that not all of his meeting requests in Outlook were being copied over to Google Calendar - I didn't verify this (too much crap in my Calendar to go through it one item at a time).
Two-way Sync
Nothing profound to add here.
Verdict
Google Calendar does a great job of inheriting the properties of Outlook meetings, e.g. the attendee list and each individual's status, your response to the meeting request, and the text of the meeting. It even puts a handy "maps" link to map whatever is in the Location field of the meeting request.
You might cause some confusion if you edit an Outlook meeting from Google Calendar, you probably should only edit meeting requests that originate in Outlook within Outlook itself.
Other than the strange issue with the meeting requests in the Deleted Items folder in Outlook, I have no complaints. I hope to see the issue resolved in future releases, I've come across some annoyed mentions of it on the web.
I would also like to see Google consolidate what it puts in the System Tray, there's no reason this can't be a part of the Gmail Notifier for example.
Update (03/31/2008)
I downloaded an update and it seems to have fixed the issue with the Calendar items in the Outlook Deleted Items folder.
Craig Shoemaker, my good friend and former co-worker, recently joined Infragistics as a new media evangelist. Today, he launched a new video podcast series called pixel8 which is going to focus on the convergence of user experience development with traditional software development.
The days of graphic designers mocking up pretty stuff in PhotoShop and tossing it over the fence are numbered! New technologies such as WPF and Silverlight are forcing UX and traditional developers to work together more closely than ever.
Craig's already got a few great shows lined up with Scott Guthrie, Josh Smith, and other new media ninjas - be sure to check it out.
The Office Communicator 2007 SDK is one of a bunch of SDKs available for the Office Communications Server 2007 product suite. This particular one is used to automate a running instance of Communicator 2007. For example, you can interact with Communicator to start text, audio, or video conversations. You can also respond to certain Communicator events in code, such as the creation of a new conversation window.
If you've tried to extract the contents of an IM conversation window using IMessengerConversationWnd::History, you might have noticed that the History property always seems to return null.
The trick here is that the conversation text isn't injected into the IM window at the exact moment that it is created; you have to use a timer to check for when the History property returns something other than null.
Here's some code that shows how to use System.Threading.Timer to poll the conversation window. This code doesn't go into how to use the CommunicatorAPI COM objects to hook into Communicator from within your application and wire up the necessary events - the SDK I linked to above has plenty of examples on how to do that.
If you have any questions about that though, hit me up and I'll try to answer them.
We'll start by declaring a Timer object in our class, and also a plain object to hold a reference to a specific IM conversation window:
private Timer timer = null;
private object convWindow;
Communicator WindowCreated Event Handler
In the Communicator WindowCreated event handler, we can tell whether this is an incoming or outgoing conversation window by comparing the window's HWND property to a local variable called windowHandle.
void communicator_OnIMWindowCreated(object pIMWindow)
{
try
{
IMessengerConversationWndAdvanced imWindow =
(IMessengerConversationWndAdvanced)pIMWindow;
if (((IMessengerConversationWndAdvanced)pIMWindow).HWND == windowHandle)
{
// outgoing
}
else
{
// incoming
windowHandle = imWindow.HWND;
HandleIncomingMessage(pIMWindow);
}
}
}
Setting up the Timer
For this example, I'll use a function called HandleIncomingMessage to handle an incoming conversation. I'll pass the conversation window as a parameter to the function.
void HandleIncomingMessage(object pIMWindow)
{
IMessengerConversationWndAdvanced imWindow =
(IMessengerConversationWndAdvanced)pIMWindow;
if (((IMessengerConversationWndAdvanced)pIMWindow).HWND == windowHandle)
{
convWindow = pIMWindow;
timer = new Timer(new TimerCallback(timer_Tick), null,
TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1));
}
}
Note that we check for matching window handles before doing anything - there could be multiple instances of Communicator windows in existence at the same time; we need to make sure that we are dealing with the right window.
Also note that we set our class variable convWindow to the pIMWindow object. This way, the Timer's tick event can access the window to check its History property.
Tick
private void timer_Tick(object state)
{
IMessengerConversationWndAdvanced imWindow =
(IMessengerConversationWndAdvanced)convWindow;
if (imWindow.History != null)
{
string history = imWindow.History; // Do what you need to do here ...
imWindow.Close();
timer.Change(Timeout.Infinite, Timeout.Infinite);
timer.Dispose();
}
}
When the window's History property is finally not null, you can can run whatever logic you need to. In my scenario, I closed the window, set the timer to never fire again, and then disposed it. This way, the next time this happens, the timer will be reinitialized cleanly.
Tick Durations
You should set up your timer to tick at an interval that's convenient for your needs; for example it shouldn't tick again while the timer_Tick handler is still executing.
Cleaning Up
Remember that these are all COM objects that need to be disposed of property. You should use Marshal.ReleaseComObject(... to clean up when you're done.
I just got back from the Bill Gates keynote at ODC 2008 and wanted to share some tidbits.
This is the second time I've heard Bill speak, and both times I found the Q&A session to be a very valuable part of the keynote. Some conference attendees got a chance to ask Bill some questions and express some frustrations with developing around Office and SharePoint.
Here are some of the more interesting point that came out of the Q&A:
- Office 14
- Microsoft is hard at work on Office 14 (looks like we're skipping v13)
- Office 14 will contain a major "Office in the cloud" piece where there will be web versions of programs such as Word, Excel, etc.
- Bill acknowledged that these wouldn't be as feature-rich, but would contain enough functionality for the most common tasks, e.g. OWA vs. Outlook
- SharePoint
- He acknowledged that SharePoint 2007's success caught Microsoft a little by surprise
- They're doing their best to catch up on documentation and developer tool support
- Increased staffing in SharePoint product support
- Future support for enabling Lists to use their own SQL tables for storage
- Apple
- He pointed out that a lot of effort was made to "Apple'ify" Office 2008 for the Mac
- As a result, there are no plans to support things such as VBA and Ribbon extensibility in Office 2008 for the Mac
- This comment came as a result of someone asking if Microsoft would eventually provide support for programming against the object model in Office 2008 for the Mac
Overall, I was impressed by how everybody who asked a question got a straight and informed answer. Well, except the host who asked: "What's up with Yahoo?"
If you're going to be attending ODC 2008 in San Jose this coming week, drop by and visit us at the Clarity Consulting booth!
Jon Rauschenberger (Clarity's CTO) and I will be presenting a demo application that that our team built for the keynote speech that Gurdeep Singh Pall - Corporate Vice President, Unified Communications Group will be giving on the second day of the conference.
Can't share too much right now about the contents of the demo, but it's good stuff. After ODC is over, I'll post a screencast and some more information on the demo itself.
The application would not have looked as amazing as it does without the help of Clarity's WPF Ninja, Erik Klimczak (that really should be his title). Many thanks to Erik for his help, as our colleague Kevin Marshall likes to say, it was a mERIKle ...
Jon will also be leading a session in the Server track: SER 312 - Business Process Communications with UCMA (Unified Communications Managed API). UCMA allows you to build sip applications using managed code. For example, you could implement an IM bot using the UCMA.
I'm personally looking forward to a bunch of sessions in the server track. If you're into SharePoint development, there's a lot of good material there.
Hope to see you at ODC 2008!
I recently presented on the topic of Designing for Extensibility at a FASS (Friday Afternoon Seminar Series) at Clarity. The presentation was inspired by an article by Miguel A. Castro in CoDe Magazine. In this article, Miguel demonstrated the use of Providers, Plug-Ins, and Extensibility Modules to demonstrate best practices for building an extensible application.
While I was already familiar with the design patterns presented in the article, I thought Miguel did the best job I've seen in demonstrating how to use them. I had also previously heard Miguel discuss the topic as a guest on my buddy Craig Shoemaker's podcast - The Polymorphic Podcast.
The accompanying code is a Visual Studio 2008 project that contains everything you need to to explore Providers, Plug-Ins, and Extensibility Modules. I included all the necessary plumbing code that was excluded from the CoDe Magazine article due to space constraints, e.g. dealing with custom web.config sections.
The source code for the Visual Studio 2008 project has been RAR'd and is available here.
You can start the screencast by clicking on the image below.
P.S. It's 0 degrees right now in Chicago, and I turned off my furnace so I could record this screencast without the sound of it behind me. I hope you appreciate that I froze for you :)

UPDATE: This code is now in the Tafiti project in CodePlex and should be going into the main branch soon.
In this excellent post, my colleague Kevin Marshall build a prototype for integrating Tafiti search into SharePoint. I've been working with Kevin to add some functionality to the prototype and get it working as a tab on the Search Center on the Clarity intranet.
The first thing I wanted to do was to move away from having all the SharePoint search code in the search.aspx code-behind. I created a very simple provider to encapsulate the search functionality:
public abstract class SearchProvider : ProviderBase
{
public abstract LiveXmlSearchResults ExecuteSearch();
}
It's worth noting at this point that I didn't want to put any effort into refactoring any existing Tafiti code so it would work within the provider model. So when you see some cheesy branching logic a little further down, that's why :)
To support retrieving the specified provider from the application's web.config, I implemented SearchProviderCollection and SearchProviderSection classes to describe a collection of search providers, and a web.config section to describe the search provider.
public class SearchProviderCollection : ProviderCollection
{
public new SearchProvider this[string name]
{
get { return (SearchProvider)base[name]; }
}
public override void Add(ProviderBase provider)
{
if (provider == null)
throw new ArgumentNullException("provider");
if (!(provider is SearchProvider))
throw new ArgumentException
("Invalid provider type", "provider");
base.Add(provider);
}
}
public class SearchProviderSection : ConfigurationSection
{
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get { return
(ProviderSettingsCollection)base["providers"];}
}
[StringValidator(MinLength = 1)]
[ConfigurationProperty("defaultProvider",
DefaultValue = "SearchProviderSharePoint")]
public string DefaultProvider
{
get { return (string)base["defaultProvider"]; }
set { base["defaultProvider"] = value; }
}
}
This allows me to configure the application directly in the web.config to use a specific search provider. This is supported by first defining a new sectionGroup:
<sectionGroup name="system.web">
<section name="searchService"
type="SearchProviderSection"
allowDefinition="MachineToApplication"
restartOnExternalChanges="true" />
</sectionGroup>
and then by specifying the search provider in a searchService element within :
<system.web>
<searchService defaultProvider="SearchProviderSharePoint">
<providers>
<add name="SearchProviderSharePoint"
type="SearchProviderSharePoint"/>
</providers>
</searchService>
Let's now take a look at the implementation of the SearchProviderSharePoint class:
public string QueryText { get; set; }
private string _queryPacket = string.Empty;
private WindowsImpersonationContext
_impersonationContext = null;
public override void Initialize(string name,
System.Collections.Specialized.NameValueCollection config)
{
// Left out some code, nothing fancy happening here
base.Initialize(name, config);
}
public SearchProviderSharePoint() {}
In an overloaded constructor, we format the QueryPacket that will be sent to the search service and do a simple check to see if the service is actually available. You would obviously want to do something a little more than simply throwing an exception, but you get the point:
public SearchProviderSharePoint(string queryText)
{
QueryText = queryText;
_queryPacket = String.Concat(
"<QueryPacket xmlns='urn:Microsoft.Search.Query'>",
"<Query>",
"<SupportedFormats>",
"<Format revision='1'>
urn:Microsoft.Search.Response.Document:Document</Format>",
"</SupportedFormats>",
"<Context>",
"<QueryText language='en-US' type='STRING'>",
QueryText,
"</QueryText>",
"</Context>",
"<Range>",
"<StartAt>1</StartAt>",
"<Count>100</Count>",
"</Range>",
"</Query>",
"</QueryPacket>");
if (IsSearchServiceAvailable() == false)
throw new Exception("Service unavailable");
}
You can add some more information to the QueryPacket, very similar to how you can tweak the search request in the object model before executing it, for example:
<EnableStemming>true</EnableStemming>
<TrimDuplicates>true</TrimDuplicates>
<IgnoreAllNoiseQuery>true</IgnoreAllNoiseQuery>
<ImplicitAndBehavior>true</ImplicitAndBehavior>
<IncludeRelevanceResults>true</IncludeRelevanceResults>
<IncludeSpecialTermResults>true</IncludeSpecialTermResults>
<IncludeHighConfidenceResults>true</>IncludeHighConfidenceResults>
The code to check if the search service is online is:
private bool IsSearchServiceAvailable()
{
_impersonationContext = ((WindowsIdentity)
HttpContext.Current.User.Identity).Impersonate();
SPSearch.QueryService queryService =
new SPSearch.QueryService();
queryService.Credentials =
CredentialCache.DefaultNetworkCredentials;
string serviceStatus = queryService.Status();
_impersonationContext.Undo();
return serviceStatus.ToUpper() == "ONLINE" ? true : false;
}
The search is then executed and formatted into LiveXml format before being displayed in the browser:
public override LiveXmlSearchResults ExecuteSearch()
{
_impersonationContext = ((WindowsIdentity)
HttpContext.Current.User.Identity).Impersonate();
SPSearch.QueryService queryService =
new SPSearch.QueryService();
queryService.Credentials =
CredentialCache.DefaultNetworkCredentials;
DataSet queryResults =
queryService.QueryEx(_queryPacket);
_impersonationContext.Undo();
return CreateLiveXmlSearchResults(queryResults);
}
private LiveXmlSearchResults CreateLiveXmlSearchResults
(DataSet queryResults)
{
DataTable relevantResults =
queryResults.Tables["RelevantResults"];
LiveXmlSearchResults returnResults =
new LiveXmlSearchResults();
returnResults.searchresult.documentset._source =
"FEDERATOR_MONARCH";
returnResults.searchresult.documentset._count =
relevantResults.Rows.Count.ToString();
returnResults.searchresult.documentset._start = "0";
returnResults.searchresult.documentset._total =
relevantResults.Rows.Count.ToString();
LiveXmlWebResult[] results =
new LiveXmlWebResult[relevantResults.Rows.Count];
for (int i = 0; i < relevantResults.Rows.Count; i++)
{
LiveXmlWebResult webResult =
new LiveXmlWebResult();
webResult.title =
relevantResults.Rows[ i]["Title"].ToString();
webResult.desc = GetDescription(
relevantResults.Rows[ i]["Description"],
relevantResults.Rows[ i]["HitHighlightedSummary"]);
webResult.url =
relevantResults.Rows[ i]["Path"].ToString();
results[ i] = webResult;
}
if (results != null)
returnResults.searchresult.documentset.document =
(results.Length > 1)
? (object)results : (object)results[0];
return returnResults;
}
The GetDescription method is the exact same in Kevin's original post, so for the sake of brevity, I'll leave it out.
As promised, here's the cheesy part where I avoided refactoring the original code to fit the search logic into the new search provider. In the SoapSearch method in search.aspx.cs, we first load the SearchProviderSection from web.config. I basically leave everything intact if there is no provider specified in web.config. Otherwise, we just call the ExecuteSearch method of the provider to search against SharePoint.
private void SoapSearch(...
{
LiveXmlSearchResults result = null;
if (_provider == null)
{
SearchProviderSection section = (SearchProviderSection)
WebConfigurationManager.
GetSection("system.web/searchService");
_providers = new SearchProviderCollection();
ProvidersHelper.InstantiateProviders
(section.Providers, _providers, typeof(SearchProvider));
_provider = _providers[section.DefaultProvider];
}
try
{
if (_provider == null)
{
// Original LiveSearch prep code goes here - left out
}
else if (_provider is SearchProviderSharePoint)
{
SearchProviderSharePoint sharePointProvider =
new SearchProviderSharePoint(query);
result = sharePointProvider.ExecuteSearch();
}
// Continue ...
}
And finally, we want to get Tafiti set up as a tab on our portal's Search Center. I chose to simply create a new web site on my SharePoint front-end, I had to install .Net 3.5 first though for Tafiti to run.
You can create a new Search Page in your Search Center, stick a good old Page Viewer Web Part on it and point it to your Tafiti site.
The only thing that's acting strange now is actually signing in to Tafiti in order to be able to save your search results. When you register an application with Windows Live, you have to specify a url for it. In this case, I want this to be the address of the search page in SharePoint, which contains a Page Viewer web part to display the actual Tafiti web application. I'm prompted to sign in, but it doesn't look I really ever do get signed in - my shelved results aren't being saved to the database.
Windows 2003 Rights Management Services is a technology that allows you to apply usage policies that are permanently attached to content such as Office documents. For example, instead of restricting permissions on a folder on a network share, a usage policy may be applied to a Word document so that only certain people can open the document. The usage policy may also dictate that readers not be able to print the document, or copy its contents to the clipboard. The policy is attached to the document (or email) regardless of its physical location, e.g. a network share or a SharePoint document library.
We recently deployed WRMS at Clarity and realized that it can integrate nicely into SharePoint; allowing you to apply usage policies at the document library level. Microsoft provides an excellent
guide for configuring WRMS to integrate with SharePoint.
Our SharePoint and WRMS deployments at Clarity are on the same server, so that simplified things a lot. However, this is usually not the case, so it is important to point out that for the integration to work, the Windows Rights Management Client w/ SP2 needs to be installed on all SharePoint web front ends.
WRMS creates a Service Connection Point on the domain, so that all rights management enabled applications can automatically discover the WRMS instance to authenticate against. In the Central Administration site, there is a section that allows you to configure your portal to use WRMS. You can specify that SharePoint use the default WRMS instance on the domain to implement Information Rights Management features.
However, after selecting this option we got the following error:
The required Windows Rights Management client is present but could not be configured properly. IRM will not work until the client is configured properly.
It turns out that to configure the rights management client "properly", you have to trigger it by opening a document that has been restricted by WRMS. The only way we could accomplish this was by installing Word 2007 on the web front end server and opening a Word document which we had restricted using WRMS.
After that, we were able to configure Information Rights Management in the SharePoint Central Administration site. The solution is very frustrating in my opinion because you would never install Office programs like Word on a SharePoint server.
I'm going to uninstall Word 2007 from the server this week and see if the integration between WRMS and SharePoint still works. My hunch that it will because it looks like the WRMS client just needed to run that one time. I'll post my findings here in an update.
I'm up in Milwaukee this week at a client site, and was bummed about having to miss the Visual Studio 2008 Install Fest that was happening as part of the Dev Cares event at Clarity on Tuesday.
I figured that since I would be in Milwaukee, I would give Larry Clarkin a call to grab a bite/brew. Larry let me know that the WI .Net user group was having their holiday party and Visual Studio 2008 install fest that night. Guess where I would be going?
My first thought was, "Wow, how did they score a space this big?" The event was hosted at a company called Direct Supply who was generous enough to allow the group to use the space. The room we were in could have easily fit more than 500 people! All I can say is that I wish I had access to a facility like that to host meetings ...
In conjunction with the install fest, the user group was sponsoring a toy drive. Anybody who donated a toy would get a special raffle ticket for an Xbox 360 Elite edition. Larry must have a closet stashed with goodies. No, I didn't win :(
All registrants received a trial version of VS2K8 which they could later redeem for a fully licensed version. People got busy installing it on their machines, after all this was an install fest. A couple of people even brought their desktop machines, that's what I call hardcore!
It was funny watching the laptop users hunt down power slots as their laptops ate up their batteries during the install.
Some brave volunteers got up and talked about their favorite new features in VS2K8. A lot of what we saw were actually new features of the .Net Framework itself, but many people were seeing them for the first time.
I'm easy to please; I was won over by the Split View functionality of the HTML Designer. It allows you to edit a web page in the HTML view and see the changes immediately in the Design view. This has got to be awesome when you're running a nice monitor at a pretty high resolution.
Last but not least, the organizers thankfully decided to forgo the traditional developer meal of pizza. Scott Isaacs' wife put together a fantastic spread! Kudos to her, I ate like a champ!
The SharePoint Products and Technologies Team quietly announced the release of Service Pack 1 for SharePoint 2007 yesterday. My first impression is that the release of the service pack is extremely well documented. Both the product team and the SharePoint Developer Documentation Team have already provided some great resources for upgrading to SP1.
The SharePoint SDK already has some how-to’s for creating ASP.NET AJAX-enabled web parts. The What’s New in SharePoint Server 2007 Service Pack 1 page on the Office Developer Center site also highlights some of the new features provided by SP1 such as:
-
Windows Server 2008 compatibility
-
ASP.NET AJAX support and compatibility
-
Support for repartitioning databases via stsadm
Whitepaper: Planning and Deploying SP1
I also downloaded a white paper titled Planning and Deploying Service Pack 1 for Microsoft Office SharePoint Server 2007 in a Multi-server Environment.
I couldn’t help but chuckle to myself as I read through the first ten pages of this document. I’m sure there are some butchered implementations of WSS and MOSS out there, so it seems like the team took the chance to again preach the value of a cleanly deployed and maintained SharePoint implementation.
The document got pretty prescriptive as far as SQL Server setup, maximum recommended size for content databases, the physical setup of the hardware for SQL Server, even the types of hard disks (and how they are connected) recommended for optimal performance.
I’m sure that a lot of this is common sense to an experienced DBA, but for some reason I get the feeling that experienced DBAs weren’t really consulted on most SharePoint installations :)
Microsoft also seems to be really pushing using 64-bit hardware for this and other server products. A couple of times in the document, I saw: “If you must run 32-bit servers, …”. Seems a little presumptuous to me, there are some serious factors to consider before moving to 64-bit hardware, especially licensing costs!
Installing SP1 for MOSS (Single-server install on a VPC)
To upgrade a MOSS installation to SP1, you first have to install SP1 for WSS 3.0 before installing SP1 for MOSS 2007.
Remember to back up any customizations that you might have made to any files, e.g. modifying a default site template directly. Your changes might get overwritten by the service pack install.
It is also strongly recommended that you do a full backup (directly in SQL Server, or through Central Administration) of your farm.
I performed the install when logged in using the SharePoint service account. The account is a local administrator and also has all the necessary permissions to the SharePoint databases in SQL.
Install WSS 3.0 SP1
After firing up the WSS 3.0 SP1 install, you are first greeted by the good old EULA:

When the service pack is done installing, the SharePoint Products and Technologies Configuration Wizard is immediately launched:
PRESS CANCEL!!!
You only want the SharePoint Products and Technologies Configuration Wizard to run after the MOSS SP1 is installed.
Install MOSS 2007 SP1
The sequence of events is exactly the same as when you install WSS 3.0 SP1. Once the service pack is installed, the Configuration Wizard runs again.

Important: if you are installing the service pack in a multi-server environment, you are prompted to stop here, install the service pack on all your servers, let the Configuration Wizard do its thing on each server, and then click OK. You should do this on computer at a time, not simultaneously.

The wizard does its thing and automatically launches Central Administration when you close it.
Once the Configuration Wizard is done, the Central Administration site is launched. Browse to your portal and check everything out, e.g. make sure the pages are displaying, and your services are up and running.
Verifying Installation
The fastest way to verify that the service pack was applied successfully is to check the version displayed on the Servers in Farm page in Central Administration. Prior to applying the service pack, the version was 12.0.0.4518. After applying the service pack, it's 12.0.0.6219.
Conclusion
A single server installation of the service pack is obviously going to be pretty straightforward. However, when installing this in a multi-server environment, you have a lot more to consider.
Make sure you read the white paper Planning and Deploying Service Pack 1 for Microsoft Office SharePoint Server 2007 in a Multi-server Environment, there's some excellent information there about preparing your server farm for the service pack.