Olav Aukan Getting information off the Internet is like taking a drink from a fire hydrant…

25Feb/10

Using ActiveX to launch desktop applications from SharePoint

A client wanted to have a list of applications on their intranet with additional metadata so that these applications could be targeted to different users based on their Active Directory account and SharePoint memberships. This could be done easily for web applications where the Applications list would simply have a URL pointing to the application and open that URL in a new window when the user clicked it. The targeting would be achieved with the built in Audience Targeting features of SharePoint. However, for desktop applications this approach would not work.

Back in the days this would probably be done with a file:// link, but it seems that this feature is not widely used any more due to security concerns. I had no luck getting a file:// link to work in IE inside the client's environment, so I decided to look for other options. Ideally I would have liked to be able to do this with plain JavaScript, but then again allowing JavaScript to start local processed on your machine is not really secure either. So in desperation I turned to the much hated ActiveX... ActiveX will only work in IE, but because this is a corporate intranet and they've standardized on IE7 anyway, it's not really an issue.

So, how to implement this? I decided to create a document library to store a HTML file for each desktop application. This HTML file launches the application by using an ActiveX object and then closes the browser window when done. Each desktop application in the Applications list would then point to one of these files and open it in a new window. Let's see what this HTML looks like when launching Notepad.

<html>
  <head>
    <script language="javascript">
      function LaunchApp(appPath)
      {
        try
        {
          <!-- Launch application -->
          WSH = new ActiveXObject("WScript.Shell");
          WSH.run(appPath);
        }
        catch (ex)
        {
           errMsg = "An error occured while lauching the application.\n\n";
           alert(errMsg);
        }
        <!-- Close window -->
        window.open('', '_self', '');
        window.close();
      }
    </script>
</head>
  <body onLoad="LaunchApp('C:\\windows\\system32\\notepad.exe')">
  </body>
</html>

It's quite straight forward really. There's a JavaScript method to launch the application and the onLoad event of the <body> element is used to call this method. Notice the rather peculiar window.open() method though. This is a workaround I found on another blog that gets rid of the confirmation prompt you get in IE when trying to close a window.

So now you've saved the HTML file to a document library in SharePoint and you click on it to confirm that it works as expected, but much to your disappointment nothing happens... You'll notice that the IE status bar now says "Error or page" or "Done" with the error icon. Double clicking the icon will bring up a description of the error where it says "Automation server can't create object". This is because by default IE will not trust an ActiveX control that is not marked as safe. The workaround for this is to modify the security settings for the Local Intranet zone to allow it to run the ActiveX control. Go to Tools -> Internet Options -> Security -> Local Intranet -> Custom Level and enable the option "Initialize and script ActiveX controls not marked as safe for scripting".

If you hit refresh in the browser now it will launch Notepad and close the browser window without any prompts.

Some thoughts:

  • Be sure to understand the consequences of enabling this option! It should be safe for the Local Intranet zone, but i wouldn't want it on for the Internet zone.
  • You probably want to push this option to all users with a Group Policy.
  • Applications that you want to launch this way must be installed under the same path on all computers.
  • The appPath parameter expects a DOS style path were "Program Files" become "PROGRA~1".
  • For some reason ex.message and ex.description are both empty inside the catch block.

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

Any sample code on this blog is provided "AS IS”, without warranty of any kind. The author takes no responsibility for problems arising from the use of this code.

19Feb/10

Improvements in Web Content Management in SharePoint 2010

I just found these three posts from Andrew Connell about what's new and improved in SharePoint 2010 when it comes to Web Content Management. Great summary of the "highlights".

Part 1 - Improvements to the Core SharePoint Platform & How the Benefit SharePoint 2010 Web Content Management

Part 2 – What’s Improved with SharePoint Server 2010 Web Content Management

Part 3 – What’s New with SharePoint Server 2010 Web Content Management

19Feb/10

How to automatically set the master page when a site is created

So you've created an awesome master page for your team sites but you are a bit annoyed that you have to manually configure every new site to use this master page after they are created. Perhaps some of the users forget to do this and then you end up with different branding on different sites leading to more confusion. Wouldn't it be nice if we could apply our custom master page automatically upon creation of a team site? With the wonders of feature stapling this can be done quite easily.

Feature stapling is basically a way of attaching a (custom) feature to a site template without modifying the site template itself so that when a site is created from that template the stapled feature is automatically activated. In this example we will staple our feature to the Team Site template. The steps involved are:

  1. Create a new Visual Studio project for our features.
  2. Create the Team Site Master Page Activator feature that sets our custom master page for new sites.
  3. Create the Team Site Master Page Stapler feature that staples our activator feature to the Team Site template.
  4. Deploy WSP

First of all, get WSPBuilder if you are not already using it. WSPBuilder integrates right into Visual Studio and lets you build WSP packages from your solution with just a few clicks. If you are developing locally on a SharePoint server it can handle the deployment for you as well. I'm not gonna go into details on how wonderful this tool is so you're just gonna have to take my word for it.

1. Create a new Visual Studio project

WSPBuilder will add new project types to Visual Studio, so fire it up and create a "WSPBuilder Project". I'm using VS2008 in this example.

2. Create the Team Site Master Page Activator feature

After the project is created, we can start adding features to the project. We'll start with the activator feature which handles activating and deactivating our custom master page. Add the feature to the project by clicking Add -> New Item and selecting the WSPBuilder item "Feature with receiver".

Give the feature the name "TeamSiteMasterPageActivator" and click Add. This brings up the Feature Settings window.

Make sure the scope is set to Web, give the feature a description and click OK.

WSPBuilder will generate a Feature.xml file similar to this:

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="83f0030d-3921-485c-99d9-49d205d86d79"
          Title="Team Site Master Page Activator"
          Description="This feature set's the master page of a team site to our 'CustomTeamSite.master'."
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          DefaultResourceFile="core"
          ReceiverAssembly="MasterPageActivator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=978fbee28dd41158"
          ReceiverClass="MasterPageActivator.TeamSiteMasterPageActivator"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

In this post I am assuming you've already deployed you custom master page with a different solution. In the future I'll post a guide on how to package and deploy master pages with WSPBuilder, but for now let's just assume there's a 'CustomTeamSite.master' in the site collection master page gallery. We can now edit the feature receiver code to make it set our custom master page when the feature is activated. This is done in the FeatureActivated method. Our end result should look something like this:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    try
    {
        using (SPWeb currentWeb = properties.Feature.Parent as SPWeb)
        {
            using (SPSite currentSite = currentWeb.Site)
            {
                string masterPageUrl = string.Format("{0}/_catalogs/masterpage/CustomTeamSite.master", currentSite.ServerRelativeUrl).Replace("//", "/");
                currentWeb.CustomMasterUrl = masterPageUrl;
                currentWeb.MasterUrl = masterPageUrl;
                currentWeb.Update();
            }
        }
    }
    catch (Exception ex)
    {
        string errorMessage = string.Format("An exception occured while trying to activate the feature. Message: '{0}'.", ex.Message);
        throw new SPException(errorMessage);
    }
}

We probably want our feature to clean up after itself when it's deactivated, so we'll go ahead and add some code to the FeatureDeactivating method that sets the site's master page back to 'default.master'. The code looks something like this:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    try
    {
        using (SPWeb currentWeb = properties.Feature.Parent as SPWeb)
        {
            using (SPSite currentSite = currentWeb.Site)
            {
                string masterPageUrl = string.Format("{0}/_catalogs/masterpage/default.master", currentSite.ServerRelativeUrl).Replace("//", "/");
                currentWeb.CustomMasterUrl = masterPageUrl;
                currentWeb.MasterUrl = masterPageUrl;
                currentWeb.Update();
            }
        }
    }
    catch (Exception ex)
    {
        string errorMessage = string.Format("An exception occured while trying to deactivate the feature. Message: '{0}'.", ex.Message);
        throw new SPException(errorMessage);
    }
}

3. Create the Team Site Master Page Stapler feature

The activator feature is now done and we can move on to the stapler feature. Add the feature to the project by clicking Add -> New Item and selecting the WSPBuilder item "Blank Feature".

Give the feature the name "TeamSiteMasterPageStapler" and click Add. This brings up the Feature Settings window.

Make sure the scope is set to Farm, give the feature a description and click OK.

WSPBuilder will generate a Feature.xml file similar to this:

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="1cd9b76d-1915-44ef-b704-3307d53fab65"
          Title="Team Site Master Page Stapler"
          Description="This feature staples the 'Team Site Master Page Activator' feature to the Team Site template so that it's activated when a new team site is created."
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Farm"
          DefaultResourceFile="core"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

Next we need to create a FeatureSiteTemplateAssociation node in the Elements.xml file that staples this feature to the built-in Team Site template. For this we need the GUID of our custom feature and the TemplateName of the Team Site template. For a list of valid TemplateNames check out this blog. We want to staple our feature to the Team Site template, so we will use the TemplateName 'STS#0'. The modified Elements.xml looks like this.

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <FeatureSiteTemplateAssociation Id="83f0030d-3921-485c-99d9-49d205d86d79" TemplateName="STS#0"/>
</Elements>

The Visual Studio project should now look something like this.

4. Deploy WSP

The stapler feature is now complete and we can deploy the solution. If you are working on a SharePoint server all you have to do is right-click on the solution and select WSPBuilder -> Build WSP and then WSPBuilder -> Deploy. If you are not working on a SharePoint server you will have to copy the WSP file and installation script generated by WSPBuilder to the SharePoint server and deploy it using the script. Once the solution has been deployed you can test that it works by creating a new site from the Team Site template. The new site should now have your 'CustomTeamSite.master' applied.

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

Any sample code on this blog is provided "AS IS”, without warranty of any kind. The author takes no responsibility for problems arising from the use of this code.

13Feb/10

Do you have a disaster recovery plan?

Do you have a disaster recovery plan?
There is a Creative Commons license attached to this image.

I did not...

A while back my Western Digital 500GB MyBook had a breakdown during the transfer of a batch of large files. This had happened to me a coupe of times before where Windows would display a Delay Write Failed error message, and some times it would have problems detecting the disk. Running scandisk and/or rebooting always seemed to fix the problem, so I didn't give it much attention, but this time it was different. The disk could no longer be detected by Windows, SpinRite or Western Digital's own maintenance tools. I tried the disk in several different computers with the same result.

When the system tried to boot the disk it would spin up and emit a series of rather loud clicking sounds before spinning down again. Many hours of googling led me to the conclusion that the disk had in fact suffered mechanical failure, and that I should have picked up the previous Delay Write Failed errors as a warning that the disk was about to fail. As it is now, the only option I have to recover the data is to have it sent to a data recovery specialist where they remove the data plates from the faulty drive housing and place them in a new one. This has to be done in a Class 100 Clean Room because even a microscopic spec of dust could lead to permanent loss of data when the disk is powered up again. Needless to say this service is not cheap.

This disk contained all my original RAW files from the last 2-3 years, and since I had never bothered to burn a backup DVD, these pictures are now most likely lost forever! The disk sits in my bookshelf as a grim reminder of my own stupidity awaiting the day I decide to fork out the big bucks and send it off for recovery. So the moral of the story is, always backup your files!