Tuesday, March 31, 2009

How to Run WSS 3.0 or MOSS on Vista or XP64

Microsoft designed WSS and MOSS to run on Windows Server 2003 or Windows Sever 2008.  This restriction is enforced by the installer.  The creative people at Bamboo Solutions have created a installer wrapper for WSS and MOSS which runs on Windows Vista x32 and x64 or Windows XP x64. The full instructions for this install are found here.

The limitations appear to be:

1)      It is not supported by Microsoft.  This means some things may not actually work correctly.  As I pointed out in my article about WindowsIdentity impersonation, some operating system API calls are only supported in Windows Server 2003 and Windows Server 2008. So spurious errors may occur.

2)      IIS on Windows Vista Home Premium only supports Basic Authentication. 

3)      The installer supports only install in farm (not single server) mode as web front end.

4)      Because it is in farm mode, an external SQLServer must be provided.

I tried this out at home and it works.  There are some curious features to my home installation.  First I do not have a domain controller so the entire system runs on local users and basic authentication.  I am an administrator in both SQLServer and on my machine so I just used myself as the Farm Administrator process users.  The result is that everything I do in SharePoint is shown as being done by System Account (SHAREPOINT/system).  I was able to add other local users to my Document Sharing site though. I would suggest that anyone considering doing this for either development or a home office, create separate local users as suggested in the Office SharePoint Server security account requirements.

The other obvious problem is using SQLExpress for the database.  Since SharePoint stores document blobs, after a while the Document Sharing site is likely to be full.

Overall though, I think I prefer my Windows Server 2008 x32 box for development.  With MSDN membership there is no reason not to go with Windows Server 2008, SQLServer 2008, MOSS 2007 SP1, VS 2008, and VSeWSS 1.3.  But if you have a zero budget operation, this looks like it might be a good way to enter SharePoint development.

 

Monday, March 30, 2009

Activate-Once Hidden Farm Features

To create a dependent farm feature that is hidden and will activate only once, use a resource-hidden feature.  I explained Friday that hidden features automatically deactivate when the last feature dependent upon the hidden feature deactivates and that resource-hidden features do not.  So using a resource-hidden feature the feature does not deactivate.

The problem is that resource-hidden features do not automatically activate in the activation tree.  The solution for farm level features is to set the attribute ActivateOnDefault=”true” in the feature element API.  This will activate your farm level feature on deployment. Here is the feature.xml for the SPFeatureDefinition:

<?xml version="1.0" encoding="utf-8"?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="C595997F-F3DD-4677-82CF-02AF9FB73451"
         Title="$Resources:Resource_hidden_feature_DO_NOT_LOCALIZE"
         Description="$Resources:Resource_hidden_feature_DO_NOT_LOCALIZE"
         RequireResources="true"
         Hidden="false"
         Scope="Farm"
         ActivateOnDefault="true"
         ReceiverAssembly="My.Receiver.Assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8d63fadacc3f6a0e"
         ReceiverClass="My.Receiver.RunOnceFarmFeatureReceiver">
</Feature>

In this example the code in the SPFeatureReceiver is run only once at initial SPSolution.Deploy.  The feature is resource-hidden so users cannot see it and will not deactivate it from the Central Administration UI.  The feature also will not be deactivated by any features which depend on this feature.   The feature will only be deactivated prior to uninstall.

Thursday, March 26, 2009

Resource-Hidden Features can have ActivationDependencies

What is a resource-hidden feature?  It is a feature that is hidden from the user using a loophole in the SPFeatureDefinition API that is intended to show locale specific features.

Basically the feature element API says that the attribute RequireResources=”true” will prevent a feature from appearing in the GUI feature list if a resource file for the locale cannot be found.  A resource-hidden feature exploits this by providing no resources for any locales. Here is what the feature.xml looks like:

<?xml version="1.0" encoding="utf-8"?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="C595997F-F3DD-4677-82CF-02AF9FB73451"
         Title="$Resources:Resource_hidden_feature_DO_NOT_LOCALIZE"
         Description="$Resources:Resource_hidden_feature_DO_NOT_LOCALIZE"
         RequireResources="true"
         Hidden="false"
         Scope="Site">
  <ActivationDependencies>
    <ActivationDependency FeatureId="0D7EE02B-F92F-4ed2-97F7-349AEC1E0517" />
    <ActivationDependency FeatureId="5730DE72-D669-4fc7-9EA1-BC50826EF575" />
    <ActivationDependency FeatureId="C3BAA93D-B3DE-424c-850A-E19253068473" />
  </ActivationDependencies>
</Feature>

You might ask why would you use a resource-hidden feature instead of a regular hidden feature with the attribute Hidden=”true”?  Simply because the two have different behaviors:

·         Activation – Hidden features auto-activate in the feature dependency chain. Resource-hidden features do not.

·         Deactivation – Hidden features which are dependent features, deactivate when the last feature depending upon them deactivates. Resource-hidden features do not.

·         Dependency  Tree – Hidden features cannot have any ActivationDependencies. Resource-hidden features can.

Remember resource-hidden features cannot be seen in the UI and do not auto-activate so they must be programmatically activated by adding them to a SPFeatureCollection.

Tuesday, March 24, 2009

10 Best Practices

If you have not already done so.  Read “10 Best Practices For Building SharePoint Solutions” by E. Wilansky, T. Stojecki, P. Olszewski and S. Kowalewski that just came out in this month’s MSDN Magazine.  This is a great article that covers a lot of basic ground on designing SharePoint applications and how to partition them.  It has many great references.

Monday, March 23, 2009

Using WindowsIdentity.Impersonate in SharePoint applicatons

Linking legacy code to your SharePoint application can pose several issues.  One of these issues is how to run the code as the correct user.  When threads enter your business logic through IIS and SharePoint they already impersonate the user connecting to the Web Application.  Threads in the SPTimerService run as the timer service process user. Threads in your custom SPWindowService run as the service process user that you have specified.  Getting your thread to assume the right WindowsIdentity can be a challenge.

WindowsIdentity.Impersonate should not be used as a method to access SPSite objects in pure non-legacy SharePoint applications.  That should be done by passing an SPUserToken object into the SPSite constructor.  Still legacy code may require the change in WindowsIdentity and glue code may access the SPSite object to communicate data back into SharePoint.  There are two steps to accomplishing this. 

First convert the SPUser to a WindowsIdentity. To accomplish this, create the user principal name (UPN) for the user.  Then instantiate a new WindowsIdentity using the UPN.

private static string GetUpn(SPUser spUser)
{
    string[] tmp = spUser.LoginName.Split('\\');
    System.DirectoryServices.ActiveDirectory.Domain domain =
        System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
    return tmp[1] + "@" + domain.Name;
}
.
.
.
SPUser spUser = GetMySPUser();
WindowsIdentity spUserIdentity = new WindowsIdentity(GetUpn(spUser));

Restrictions: This approach will only work when the user is actually a member of the Domain.GetCurrentDomain.  Otherwise you need to search for the domain name through the active directory service. Also this approach will only work if the process user has the local security policy “Act As Part of the Operating System” = “true”.  By default the NT Authority\Network Service user has this privilege. Additionally the WindowsIdentity instantiation is also restricted to the Server 2003 or Sever 2008 operating systems, but so is SharePoint.

Next perform the impersonation.

WindowsImpersonationContext context = spUserIdentity.Impersonate();
try {
    MyLegacyData data = ExecuteLegacyLogic();
    using (SPSite site = new SPSite(mySiteId))
    {
        using (SPWeb web = site.OpenWeb(myWeb)
        {
            AddLegacyDataResults( web, data );
        }
    }
}
finally
{
    context.Undo();
}

Notes: In the above block the SPSite object access is the same as if were performed using new SPSite(mySiteId, spUser.UserToken).

Restrictions: Never run this code block inside a SPSecurity.RunWithElevatedPrivileges delegate.  This appears to be unsupported by SharePoint.  The resulting SPSite and SPWeb objects will be unusable.  In this condition, a StackOverflowExcetion will be thrown if you attempt to access an SPUser in the SPWeb.Users collection and will bring down the entire process.

Outside of the above restriction, the code appears to work as expected.

Friday, March 20, 2009

How to create a new user for an SPWeb

It seems that if a user needs to be added to a SPWeb object one should just call SPWeb.Users.Add. Unfortunately, this does not work and you will get an exception.  To add a user correctly you need to define the users access rights. This is most easily done using the predefined roles.  Here is a small foreign method that does the job correctly.

private void AddUser(SPWeb web, SPRoleType roleType, string login, string email, string name, string notes)
{
    SPRoleDefinition roleDefinition = web.RoleDefinitions.GetByType(roleType);
    SPRoleAssignment roleAssignment = new SPRoleAssignment(login, email, name, notes);
    roleAssignment.RoleDefinitionBindings.Add(roleDefinition);
    web.RoleAssignments.Add(roleAssignment);
}

Remember to always get SPRoleDefinition objects using the SPRoleDefinitionCollection.GetByType method and never using the [string] indexer.  The indexer takes the localized name of the role. Using a string like “Viewer” will work in English but will not in any other language. SPRoleType is an enumeration that is locale independent.

Monday, March 9, 2009

SharePoint uses action states in SPObjectStatus

An action state is a state that indicates that an action is being performed.  The SPObjectStatus used by the SPPersistedObject.Status has three such states Provisioning, Unprovisioning, and Upgrading this allows SPPersistedObjects to undergo long running operations.  Here is a quick summary of all the states:

Name

Meaning

Details

Disabled

OFF

New or fully removed object

Online

ON

Object fully setup

Provisioning

Action State

Moving to ON

Unprovisioning

Action State

Moving to OFF

Upgrading

Action State

Changing object versions

Offline

FAILED

Your dead

One of the major misconceptions with new SharePoint developers is that ON and OFF are really Online and Offline, but they are not. ON and OFF are represented by Online and Disabled.  Offline has the same meaning it does in SQLServer. Your object is dead beyond use.  This is data corruption, hardware failure, network failure, etc.

SharePoint maps the Provisioning and Unprovisioning states differently depending on the object.  Here are some examples:

Object

Provisioning

Unprovisioning

SPWindowsServerInstance

Starting

Stopping

SPSolution

Deploying

Retracting

SPDatabase

Creating

Dropping

It is best practice to code any custom SPPersistedObjects to set the Provisioning and Unprovisioning action states correctly.  Particularly if your Provision and Unprovision methods are long running.

Friday, March 6, 2009

SPPersistedObject.Properties is a great place for storing miscellaneous data

The SPPersistedObject.Properties  property is a great place to store miscellaneous data and place flags on administrative objects.  There are two very important considerations when using this data store.

1. Although it is implemented by a System.Collections.Hashtable which can take any object as a key.  The only acceptable key in SharePoint is a string key.  Using any other object class will cause SharePoint to throw spurious class cast exceptions. The Microsoft.SharePoint.Administration.SPBackwardCompatibilityPropertyMapper.GetClusterProperties() is one such internal method that will throw System.InvalidCastException if your SPFarm.Properties contain non-string keys.

2. Remember to call Update after adding or removing a key/value pair in the Hashtable.  It is easy to miss this and I have found that it is often not detected in unit tests.  The following are SharePoint singleton objects within a given process: SPAdministrationWebApplication.Local, SPDiagnosticService.Local, SPFarm.Local, SPFarm.Local.TimerService, SPServer.Local, SPWebService.ContentService, SPWebService.AdministrationService,  SPWebServiceInstance.LocalAdministration, and SPWebServiceInstance.LocalContent.  In most unit tests both the operations performed and the asserts called occur in the same process.  If you are setting properties on these singleton objects within your unit test process, there is no way to tell whether those properties were persisted back to the database.  The only way to detect it would be to use a two process test harness.

Thursday, March 5, 2009

Dealing with SPDeletedConcurrencyException

SPDeletedConcurrencyException is an exception thrown by the SPPersistedObject Update() method.  It indicates that another processes has deleted the SPPersistedObject you are trying to update.

The questions you need to answer are:

·         Is this unexpected behavior for your application (is class not expected to be deleted)?

·         Does the removal of my object negatively impact the current operation?

If the answer to both these questions is “no”, then simply suppress this exception.

Building on yesterdays code snip:

int count = 0;
while (true)
{
    try
    {
        MySPPersistedObject obj = FindMyObject(id);
        obj.SomeOperation();
        obj.Update();
        break;
    }
    catch (SPDeletedConcurrencyException){
        // Done. This object is gone so we are finished.
        // Optional logging of condition
        break;
    }
    catch (SPUpdatedConcurrencyException)
    {
        count++;
        if (count > 10)
        {
            throw;
        }
    }
}

Wednesday, March 4, 2009

Dealing with SPUpdatedConcurrencyException

SPUpdatedConcurrencyException is an exception thrown by the SPPersistedObject Update() method. It indicates that another processes has updated the SPPersistedObject you have just modified and are trying to update.

I have found that best practice is to wrap all calls to Update on any SPPersistedObject to iterate until successful. Of course it is folly to iterate forever, so I have my code give up after 10 tires. Here is a snip:


int count = 0;
while (true)
{
    try
    {
        MySPPersistedObject obj = FindMyObject(id);
        obj.SomeOperation();
        obj.Update();
        break;
    }
    catch (SPUpdatedConcurrencyException)
    {
        count++;
        if (count > 10)
        {
            throw;
        }
    }
}

Email Forwarding

I have setup a group to email forward the tips to via Email.  It can be found at: http://groups.google.com/group/sharepoint-developer-tip-of-the-day/topics

Dave

New micro-blog started.

I created this new micro-blog to share small tips about SharePoint programming with my coworkers and the development community as a whole.

I call this a micro-blog because the tips are very small and short and you may need to read several tips to get the larger picture.

Hope you enjoy it.

-          Dave