WebKMS Toronto SharePoint Consulting

Friday, February 19, 2010

Case Study - Law Firm Intranet Portal

WebKMS recently completed development on a Portal for a leading Canadian Law Firm. The Portal design combined an extremely robust knowledge management system with a feature rich user interface. The Portal has been rolled out to over 1500 employees and the feedback on the system has been tremendous. For more information on this project email info@webkms.com and request our Case Study - Law Firm Portal.

Thursday, February 18, 2010

Migrating from 32-bit MOSS server to 64-bit MOSS server.

Migrating an application (both content and code) from an existing environment to a newer environment is always a challenging and at times a daunting task. SharePoint application is not an exception. I would like to share my experiences at WebKMS where I had to migrate the content database and deploy the code from our existing 32-bit MOSS solution in Windows Server 2003 environment onto a newly built 64-bit MOSS environment in Windows Server 2008 R2.

First, let’s start with migrating the content database:

Everything looked good until I tried to attach the content db from within the Central Admin. Taking a full backup of the existing content from the 32-bit SQL box and restoring it on the 64-bit SQL box was smooth. The problem occurred while actually attaching the content db from the Central admin interface on the 64-bit MOSS environment. This is resolved the by backing up the site collection(s) we had and restoring it on the MOSS server using the stsadm commands.

Here is the script that did the trick:

---Backing up the site collection---
set newUrl=http://mymossServer/sites/mySiteCollection
stsadm -o backup -url %newUrl % -filename c:\mySiteCollection.bkp

---Restoring the site collection---
set newUrl=http://mymossServer/sites/mySiteCollection
stsadm -o restore -url %newUrl% -filename c:\mySiteCollection.bkp -overwrite

Please note that in order to restore the site collection, the content database on the server must be brand new! (One can add a blank content database from the Central Admin interface).

Some of the other minor things one would face working on the Windows 2008 server is that the security on the server is more tighter and normally running scripts and other commands would not run. My deployment script didn’t work the first time (if everything works smoothly the first time, we should all pack our bags for good :-). Even though I had logged into the server as an administrator, I had to run my deployment batch file as an administrator! One can either right-click on the batch file and select run as administrator or open the DOS command window itself as an administrator and execute the script in the normal way. I prefer this option as even executing an iisreset needs to be run as an administrator! Imagine having a separate batch file just with iisreset and yet run it as an administrator! We all know how many times during the development process one needs to do iisreset.

An other tweak I had to do with my deployment script is that I had to specify the complete path of my wsp package file in the addsolution operation of the stsadm command syntax. Please note that the batch file is executed from C:\Windows\system32 directory whereas my script and the .wsp files are in C:\Deployment directory.

C:\Windows\system32> stsadm -o addsolution -filename myDeployment.wsp
myDeployment.wsp: The specified file was not found.

Either I modify the batch file to include the complete path like this:
stsadm -o addsolution -filename C:\Deployment\myDeployment.wsp

or

execute the batch file in Command prompt from the correct location (C:\Deployment in my case) like below:

C:\Deployment >stsadm -o addsolution -filename myDeployment.wsp

We also had a set of Web services that were built with WSE 3.0. There have been some discussions that WSE 3.0 won’t be supported in 64-bit MOSS installation and the web services might not be working. This didn’t seem to be a problem in our case though.

Wednesday, January 20, 2010

Creating custom SharePoint Timer Job

Background:

In one of my recent tasks I had to synchronize the data between two lists that resided on different site collections. The synchronization must occur on an ongoing basis (every 30 minutes interval). There are two ways to implement this; either as a console application or as a SharePoint Timer Job. The second option is more ideal as it doesn’t involve user action (though a console application can be scheduled, it would be more intuitive to run it as a SharePoint Timer Job on a SharePoint Web Application).

Introduction:

SharePoint Timer Job is a scheduled task that should be installed on the Web Application. There could be many ways to install the job (create a custom stsadm command, console application, via a SharePoint feature, etc...). The most ideal candidate to install SharePoint Timer Job is through a SharePoint feature. This feature will be hidden as no one can activate it through SharePoint Interface due to security issues. This will be activated by the administrator using the appropriate stsadm command.

Note: You can neither edit, nor reschedule the existing job (once you’ve installed it) through SharePoint Interface, but you can download some utilities like zevenseas Timerjob Overview” or develop your own that can do it.

Development Process:

1. Custom SharePoint Job.

The custom job must be inherited from SPJobDefinition class that implements the virtual Execute method which Windows SharePoint Services Timer calls when the job is executed. In addition the lock type of the Job must be considered from the following options:

  • SPJobLockType.ContentDatabase - Locks the content database. A timer job runs one time for each content database that is associated with the Web Application; therefore your job will run as many times for each content database associated with the Web Application that exists.
  • SPJobLockType.Job - Locks the timer job so that it runs on only one machine in the Farm.
  • SPJobLockType.None - No locks. The timer job runs on every machine on which the parent service is provisioned.

Please refer to ZevenSeas Post in order to get a clear picture of the subject.

public class PeopleSynchronizerJob : SPJobDefinition

{

public PeopleSynchronizerJob(): base(){ }

public PeopleSynchronizerJob(string jobName, SPService service, SPServer server, SPJobLockType targetType)

: base(jobName, service, server, targetType)

{

}

public PeopleSynchronizerJob(string jobName, SPWebApplication webApplication)

: base(jobName, webApplication, null, SPJobLockType.ContentDatabase)

{

this.Title = " People Synchronizer";

}

public override void Execute(Guid contentDbId)

{

// get a reference to the current site collection's content database

SPWebApplication webApplication = this.Parent as SPWebApplication;

SPContentDatabase contentDb = webApplication.ContentDatabases[contentDbId];

// Do the execute stuff

}

2. SharePoint Feature for the job activation:

  1. Declare the SharePoint Feature as following: Do not forget to set Hidden to be True

xml version="1.0" encoding="utf-8"?>

<Feature Id="4FAAC725-FF19-4084-8248-F72E1F628E39"

Title="People Data Synchronization"

Description="PeopleSynchronizerJob activation"

Version="1.0.0.0"

Scope="Site"

Hidden="True"

DefaultResourceFile="core"

ReceiverAssembly="’Your assembly Name’, Version=1.0.0.0, Culture=neutral, PublicKeyToken=’Your assembly public key’"

ReceiverClass="’Your assembly Name’.PeopleSynchronization.EventReceiver"

xmlns="http://schemas.microsoft.com/sharepoint/">

<ElementManifests/>

Feature>

2. Create another class that inherits from the Microsoft.SharePoint.SPFeatureReceiver class and implement the FeatureActivated & FeatureDeactivated event handlers like so:

public class EventReceiver : SPFeatureReceiver

{

const string PEOPLE_SYNCHRONIZER_JOB_NAME = "People Synchronizer";

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

string msg = "";

try

{

SPSite site = properties.Feature.Parent as SPSite;

msg = "People Data Synchronization Feature Activation on site " + site.Url + Environment.NewLine;

if (UnregisterJob(site))

msg += string.Format("The job {0} was successfully unregistered {1}", Consts.PEOPLE_SYNCHRONIZER_JOB_NAME, Environment.NewLine);

PeopleSynchronizerJob job = new PeopleSynchronizerJob(Consts.PEOPLE_SYNCHRONIZER_JOB_NAME, site.WebApplication);

SPMinuteSchedule schedule = new SPMinuteSchedule();

schedule.BeginSecond = 0;

schedule.EndSecond = 59;

schedule.Interval = 30;

job.Schedule = schedule;

job.Update();

msg += string.Format("The job {0} was registered successfully {1}", Consts.PEOPLE_SYNCHRONIZER_JOB_NAME, Environment.NewLine);

Utils.LogMessage(msg + "Activation performed successfully.", EventLogEntryType.Information);

}

catch (Exception e)

{

msg += string.Format("Failed to register {0} job {1}", Consts.PEOPLE_SYNCHRONIZER_JOB_NAME, Environment.NewLine + e.ToString());

Utils.LogMessage(msg, EventLogEntryType.Error);

}

}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

string msg = "";

try

{

SPSite site = properties.Feature.Parent as SPSite;

msg = "People Data Synchronization Feature Deactivation on site " + site.Url + Environment.NewLine;

if (UnregisterJob(site))

msg += string.Format("The job {0} was successfully unregistered", Consts.PEOPLE_SYNCHRONIZER_JOB_NAME + Environment.NewLine);

Utils.LogMessage(msg, EventLogEntryType.Information);

}

catch (Exception e)

{

msg += string.Format("Failed to unregister {0} job {1}", Consts.PEOPLE_SYNCHRONIZER_JOB_NAME, Environment.NewLine + e.ToString());

Utils.LogMessage(msg, EventLogEntryType.Error);

}

}

public override void FeatureInstalled(SPFeatureReceiverProperties properties)

{

}

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)

{

}

private bool UnregisterJob(SPSite site)

{

SPJobDefinitionCollection jobs = site.WebApplication.JobDefinitions;

foreach (SPJobDefinition job in jobs)

if (job.Name == Consts.PEOPLE_SYNCHRONIZER_JOB_NAME)

{

job.Delete();

return true;

}

return false;

}

}

Deployment process:

In order to deploy this job, all you need to do is:

1. Install the strongly named assembly of the custom job (PeopleSynchronizerJob.dll)on the GAC.

2. Do an iisreset /noforce.

3. Install and activate the feature (through stsadm command)

Once you activate the feature, it should show up on the Timer Job Definitions page in Central Administration / Operations. It will appear in the Timer Job Status page after the first time it is executed.

Debugging SharePoint Timer Job (Visual Studio):

SharePoint Jobs do not have a current context, they are executed by another windows process called “Windows SharePoint Services Timer”. The executable name of this process is “OWSTIMER.EXE”. This is the process that should be attached to the code within Execute() method to be able to debug the job. Yes, I know - it can be tedious to wait for all 30 minutes until the break point color turns into yellow… But the job can be executed job instantly, by creating your small windows application utility that will pass through all jobs in the environment that populates a list of those jobs; pick the custom job from this job to run the same instantly. But then attach this running application to start debugging the job.

private void PopulateJobNames()

{

msgStatus = txtStatus.Text = "";

ddlJobs.Items.Clear();

using (SPSite site = new SPSite(txtURL.Text))

{

SPJobDefinitionCollection jobs = site.WebApplication.JobDefinitions;

SPContentDatabase cntDB = site.ContentDatabase;

foreach (SPJobDefinition job in jobs)

ddlJobs.Items.Add(job.Name);

}

}

private void ExecuteJob()

{

msgStatus = Environment.NewLine + DateTime.Now.ToString();

using (SPSite site = new SPSite(txtURL.Text))

{

SPJobDefinitionCollection jobs = site.WebApplication.JobDefinitions;

SPContentDatabase cntDB = site.ContentDatabase;

foreach (SPJobDefinition job in jobs)

if (job.Name == ddlJobs.Text)

{

job.Execute(cntDB.Id);

msgStatus += string.Format("{0}The Job {1}, was executed.", Environment.NewLine, job.Name);

break;

}

} }

Note:

1. This debug process may not cover all cases.

2. SharePoint Timer job is executed under the account that runs “Windows SharePoint Services Timer”; therefore one should be aware of security issues in production. Refer to the StackOverFlow Post regarding this subject.

Followers