Skip to content
May 18 / Michaël Hompus

Migrating a catch-all maildir to Office 365

I’ve been running my own mail server at home for years using Postfix, dovecot, amavisd-new, ClamAV and SpamAssassin. But it requires a reliable connection and some maintenance once in a while. And of course it always breaks when I‘m on the other side of the world.

To free myself of that burden I decided to make the move to Office 365. I got myself a P1 subscription and started to browse through the configuration screens. The migration of an account from IMAP to Exchange Online was very fast and easy.

Happy with how everything looked, felt and connected I was ready to make the switch.

Just before I wanted to change the MX record to point to Office 365 I double checked the configuration of my account. I discovered I couldn’t find a way to set my account as a catch-all account. After some research I found out this is not possible at all!

“Catch-all Mailbox

A catch-all mailbox receives messages sent to email addresses in a domain that do not exist. Exchange Online anti-spam filters use recipient filtering to reject messages sent to mailboxes that don’t exist, so catch-all mailboxes are not supported.”

That left me 2 options:

  1. Stop the migration to Office 365, and leave things as they were.
  2. Make every email address I used in the past an alias.

I started searching if anyone has done this before. It looks like this is not the case, so seeing this as a challenge, I started working on my own solution.

Extracting all used addresses

First you need to get every email address ever used as a recipient.

As my bash scripting is a bit rusty, I found this script to convert mdir to mbox format by Joerg Reinhardt which I used as base for my own script: getting email recipient addresses from maildir.

The script needs 2 parameters, the maildir directory and the email domain you want to get the aliases for.

$ ./maildir-dump.sh <Maildir directory> <Email domain>

So for me:

$ ./maildir-dump.sh Maildir hompus.nl

This results in a long file with a lot of email addresses. To aggregate this list into a CSV file you can use the following command:

sort Maildir.dump | uniq -c | sort -k1nr | awk ‘BEGIN {OFS = “;”;} {print $1,$2}’ > Maildir.csv

This allows you to open the file using Excel and remove all entries you don’t want to be an alias.
I counted 385 unique email addresses, too many to add manually.

Adding the aliases to a mailbox

First I configured and connected to Exchange Online using the “Windows PowerShell to the Service” article.

First we will read the CSV file we have generated and authored.

$csv = Import-Csv D:\Maildir.csv -Delimiter ‘;’ -Header Count, Email

Then you need to get a reference to the mailbox you want to add the aliases to.

$temp = Get-Mailbox -Identity michael

You need to add all email addresses to the EmailAddress property, but I discovered the last email address to be added will become the default address. So make sure the primary SMTP address is added last.

$temp.EmailAddresses = $temp.EmailAddresses | ? { $_ -ne “SMTP:” + $temp.PrimarySmtpAddress }

$csv | % { $temp.EmailAddresses += (“SMTP:” + $_.Email) }

$temp.EmailAddresses += “SMTP:” + $temp.PrimarySmtpAddress

Now you only need to set the EmailAddress property on the actual mailbox

Set-Mailbox -Identity michael -EmailAddresses $temp.EmailAddresses

And done!

Mar 11 / Michaël Hompus

Make your browser cache the output of an HttpHandler

Recently I worked on an HttpHandler implementation that is serving images from a backend system. Although everything seemed to work as expected it was discovered images were requested by the browser on every page refresh instead of caching them locally. Together with my colleague Bert-Jan I investigated and solved the problem which will be explained in this post.

The problem

Let’s start with the original (simplified) code. This code gets the image from the backend system (in this case Content Management Server 2002) and serves it to the browser, or in case the resource is not available it will return "404 Not Found".

public class ResourceHandler : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
    context.Response.Cache.SetCacheability(HttpCacheability.Public);
    context.Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0));

    string imagePath = "some path";

    Resource resource = CmsHttpContext.Current.RootResourceGallery
                                    .GetByRelativePath(imagePath) as Resource;

    if (resource == null)
    {
      // Resource not found
      context.Response.StatusCode = 404;
      return;
    }

    using (Stream stream = resource.OpenReadStream())
    {
      byte[] buffer = new byte[32];
      while (stream.Read(buffer, 0, 32) > 0)
      {
        context.Response.BinaryWrite(buffer);
      }
    }
  }

  public bool IsReusable
  {
    get { return true; }
  }
}

Every time the browser requested a resource it responded with the following headers and included the full image.

HTTP/1.1 200 OK
Cache-Control: public
Content-Length: 3488
Content-Type: image/gif
Server: Microsoft-IIS/6.0
X-AspNet-Version: 2.0.50727
COMMERCE-SERVER-SOFTWARE: Microsoft Commerce Server, Enterprise Edition
X-Powered-By: ASP.NET
Date: Fri, 11 Mar 2011 10:51:08 GMT

GIF89a... (the raw image)

The CAUSE

For some reason the local browser cache was omitted. We fired up Fiddler and started comparing the headers to an other source where an image was getting cached locally.

On the first request we discovered an additional header "Last Modified":

Last-Modified: Tue, 05 Jun 2007 15:19:48 GMT

The second response we got to the same image resulted not in a "200 OK" but a "304 Not Modified" message.

HTTP/1.1 304 Not Modified
Connection: close
Date: Fri, 11 Mar 2011 12:21:55 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Cache-Control: public
Last-Modified: Tue, 05 Jun 2007 15:19:48 GMT

The Solution

So the first thing missing was the "Last Modified" entry in our first response. We added code to include this property.

context.Response.Cache.SetLastModified(resource.LastModifiedDate);

By adding the "Last Modified" date the browser added a new entry to the second request of the image:

If-Modified-Since: Tue, 05 Jun 2007 15:19:48 GMT

But the response was still the same "200 OK" with the complete image. As it turned out you need to handle the "If-Modified-Since" yourself. We added the following code to handle this.

string rawIfModifiedSince = context.Request.Headers.Get("If-Modified-Since");
if (string.IsNullOrEmpty(rawIfModifiedSince))
{
  // Set Last Modified time
  context.Response.Cache.SetLastModified(res.LastModifiedDate);
}
else
{
  DateTime ifModifiedSince = DateTime.Parse(rawIfModifiedSince);

  if (resource.LastModifiedDate == ifModifiedSince)
  {
    // The requested file has not changed
    context.Response.StatusCode = 304;
    return;
  }
}

After testing this again the image was still transmitted every time it was requested. A quick debug of the date compare revealed that the HTTP request date time does not contain milliseconds. The following fix was applied.

if (resource.LastModifiedDate.AddMilliseconds(
                   -resource.LastModifiedDate.Millisecond) == ifModifiedSince)

Now every following request returned a "304 Not Modified" and saves us a lot of traffic and loading time!

Summary

To conclude this post I give you the complete code:

using System;

public class ResourceHandler : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
    context.Response.Cache.SetCacheability(HttpCacheability.Public);
    context.Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0));

    string imageName = "some path"

    Resource resource = CmsHttpContext.Current.RootResourceGallery
                                    .GetByRelativePath(imageName) as Resource;

    if (resource == null)
    {
      // Resource not found
      context.Response.StatusCode = 404;
      return;
    }

    string rawIfModifiedSince = context.Request.Headers
                                             .Get("If-Modified-Since");
    if (string.IsNullOrEmpty(rawIfModifiedSince))
    {
      // Set Last Modified time
      context.Response.Cache.SetLastModified(resource.LastModifiedDate);
    }
    else
    {
      DateTime ifModifiedSince = DateTime.Parse(rawIfModifiedSince);

      // HTTP does not provide milliseconds, so remove it from the comparison
      if (resource.LastModifiedDate.AddMilliseconds(
                  -resource.LastModifiedDate.Millisecond) == ifModifiedSince)
      {
          // The requested file has not changed
          context.Response.StatusCode = 304;
          return;
      }
    }

    using (Stream stream = resource.OpenReadStream())
    {
      byte[] buffer = new byte[32];
      while (stream.Read(buffer, 0, 32) > 0)
      {
          context.Response.BinaryWrite(buffer);
      }
    }
  }

  public bool IsReusable
  {
    get { return true; }
  }
}
Feb 23 / Michaël Hompus

Translating URLs using Alternate Access Mappings from code

With SharePoint it’s easy to configure multiple zones for your SharePoint Web Application. For example you have a Publishing Web Site with two zones.

  • The authenticated CMS where editors can mange content: http://cms.int
  • The anonymous website where everybody can view the content: http://www.ext

When the editors link to sites, pages, documents and images the URL will start with http://cms.int. After the content is published it’ll also be available on the anonymous site. Now most of the URLs will be automatically translated to corresponding zone URL and start with http://www.ext.

There are however some place this is not the case. You could try to use relative URLs but even that won’t fix every scenario.

Translate the URL using code

Facing this issue I had to translate the URLs myself. But I wanted to write minimal code. Lucky Microsoft has done most of the work for me.

On the SPFarm Class you will find the AlternateUrlCollections Property. This “collection” is actually an instance of the SPAlternateUrlCollectionManager Class and provides the RebaseUriWithAlternateUri Method. And this is where the magic happens.

This method has an overload where you supply a Uri and a SPUrlZone. You can provide one of the values of the SPUrlZone Enumeration or you can provide the current zone.

To get your current zone you can use the static Lookup Method of the SPAlternateUrl Class. This method requires a Uri so we provide the current one using the ContextUri Property from the same class.

To wrap it all up I give you the code:

var originalUri = new Uri("http://cms.int/pages/default.aspx");

var zone = SPAlternateUrl.Lookup(SPAlternateUrl.ContextUri).UrlZone;

var translateUri = SPFarm.Local.AlternateUrlCollections
                                .RebaseUriWithAlternateUri(originalUri, zone));

// When accessing from the authenticated zone
// translateUri == "http://cms.int/pages/default.aspx"

// When accessing from the anonymous zone
// translateUri == http://www.ext/pages/default.aspx

“Other” URLs

If you pass a URL which is not listed as an Alternate Access Mapping the method will return the original URL.

Jan 17 / Michaël Hompus

Configure people picker over a one-way trust using PowerShell

In a previous post I have written about Using the people picker over a one-way trust. In that post I use STSADM commands as there are no other ways to configure this. A downside of the STSADM command is your domain password being visible on the command prompt in plain text for everybody to read.

With SharePoint 2010 Microsoft introduces several cmdlets to replace the “old” STSADM commands. But looking at the STSADM to Windows PowerShell mapping you will see the commands for configuring the people picker are not present.

Creating my own script

PowerShell contains a nice cmdlet called Get-Credential which uses a popup to request credentials from the user and stores the password in a SecureString. This triggered me to write a PowerShell script which will work the same as “STSADM -o setproperty -pn peoplepicker-searchadforests”, but instead of typing the credentials on the command line it will use the credential dialog for every trusted domain.

As written in my previous post the configuration is done in two steps.

SetAppPassword

First you need to create a secure store for the credentials. This is done by executing the SetAppPassword command on every server in your SharePoint Farm with the same password.

STSADM:

stsadm -o setapppassword -password <password>

PowerShell:

SetAppPassword <password>

function SetAppPassword([String]$password) {
  $type = [Microsoft.SharePoint.Utilities.SPPropertyBag].Assembly
                     .GetType("Microsoft.SharePoint.Utilities.SPSecureString")
  $method = $type.GetMethod("FromString", "Static, NonPublic", $null,
                                                           @([String]), $null)
  $secureString = $method.Invoke($null, @($password))
  [Microsoft.SharePoint.SPSecurity]::SetApplicationCredentialKey($secureString)
}

PeoplePickerSearchADForests

The second step is to register the (trusted) domains to be visible in the people picker. Remember the setting is per web application and zone.

STSADM:

stsadm -o setproperty -url <url> -pn “peoplepicker-searchadforests” -pv “forest:<source forest>;domain:<trusted domain>,<trusted domain>\<account>,<password>

PowerShell:

PeoplePickerSearchADForests <url> “forest:<source forest>;domain:<trusted domain>

function PeoplePickerSearchADForests(
                                 [String]$webApplicationUrl, [String]$value) {
  $webApplication = Get-SPWebApplication $webApplicationUrl

  $searchActiveDirectoryDomains = $webApplication.PeoplePickerSettings
                                                 .SearchActiveDirectoryDomains
  $searchActiveDirectoryDomains.Clear()

  $currentDomain = (Get-WmiObject -Class Win32_ComputerSystem).Domain

  if (![String]::IsNullOrEmpty($value)) {
    $value.Split(@(';'), "RemoveEmptyEntries") | ForEach {
        $strArray = $_.Split(@(';'))

        $item = New-Object Microsoft.SharePoint.Administration
                                  .SPPeoplePickerSearchActiveDirectoryDomain

        [String]$value = $strArray[0]

        $index = $value.IndexOf(':');
        if ($index -ge 0) {
            $item.DomainName = $value.Substring($index + 1);
        } else {
            $item.DomainName = $value;
        }

        if ([System.Globalization.CultureInfo]::InvariantCulture.CompareInfo
                                .IsPrefix($value, "domain:","IgnoreCase")) {
            $item.IsForest = $false;
        } else {
            $item.IsForest = $true;
        }

        if ($item.DomainName -ne $currentDomain) {
            $credentials = $host.ui.PromptForCredential("Foreign domain trust"
            + " credentials", "Please enter the trust credentials to connect "
            + "to the " + $item.DomainName + " domain", "", "")

            $item.LoginName = $credentials.UserName;
            $item.SetPassword($credentials.Password);
        }

        $searchActiveDirectoryDomains.Add($item);
    }

    $webApplication.Update()
  }
}

Using the script

I have attached the script so you can use it in any way you want. You can put the commands in you own .ps1 file, or load the script in your current session using the following syntax:

. .\<path to file>\PeoplePickerSearchADForests.ps1

(yes, that’s a dot, then a space, then the path to the script)

PeoplePickerSearchADForests.zip

Aug 26 / Michaël Hompus

Joining an IQueryable with an IEnumerable

With the introduction of LINQ the difference between writing code for accessing a lists of objects in memory and accessing a list of data in an external data source like SQL is vanishing. Combining a in memory with a external list in a single query was not yet possible. With the introduction of .NET Framework 4.0 this has changed.

In this post I want to filter my SQL data using a list of integers I have stored in memory.

Data model

I have created a small data model as illustration. This will be used for the examples. Follow the link on the image below for a larger version. The model is part of the sample code.

Visual representation of the datamodel as described above.
Data model

IQueryable.Join

The first option you can use is to use the Queryable.Join Method.

var entities = new DemoModelContainer();
var countryIds = Enumerable.Range(1, 3);

var query = from i in entities.Items
            from c in i.Countries
            join cid in countryIds on c.Id equals cid
            select i;

var items = query.ToList();

When you look at the generated SQL statement (I love IntelliTrace!) you can see a temporary table is created by using the UNION ALL statement.

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name]
FROM
    [dbo].[Items] AS [Extent1]
    INNER JOIN [dbo].[CountryItem] AS [Extent2]
                               ON [Extent1].[Id] = [Extent2].[Items_Id]
    INNER JOIN (
        SELECT
            [UnionAll1].[C1] AS [C1]
        FROM (
            SELECT 1 AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable1]
            UNION ALL
            SELECT 2 AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable2]
        ) AS [UnionAll1]
        UNION ALL
        SELECT 3 AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable3]
    ) AS [UnionAll2] ON [Extent2].[Countries_Id] = [UnionAll2].[C1]

What happens is that 2 values are combined in a set, this set is extended with the next value, this set is extended with another value, and so on, and so on, until all values are present.

This works nicely, but as you can imagine, the SQL query will grow rapidly when the IEnumerable list gets larger.

When you change:

var countryIds = Enumerable.Range(1, 3);

into:

var countryIds = Enumerable.Range(1, 50);

you will run into a SqlException:

Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries.

SQL Server has a hard-coded limit of nesting queries, it doesn’t matter which version or edition of SQL server you use.

IEnumerable Contains

Joining tables is one way of filtering data, an alternative is to use the WHERE statement. This can be done by using the Queryable.Where Method.

var entities = new DemoModelContainer();
var countryIds = Enumerable.Range(1, 3);

var query = from i in entities.Items
            from c in i.Countries
            where countryIds.Contains(c.Id)
            select i;

var items = query.ToList();

When you look at the generated SQL statement (Still loving IntelliTrace!) you can see now the IN statement is used.

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name]
FROM
    [dbo].[Items] AS [Extent1]
    INNER JOIN [dbo].[CountryItem] AS [Extent2]
                               ON [Extent1].[Id] = [Extent2].[Items_Id]
WHERE
    [Extent2].[Countries_Id] IN (1,2,3)

This SQL query is much smaller and filtering against a larger set will not give you an exception.

This is how the WHERE statement looks when filtering on 50 values:

WHERE [Extent2].[Countries_Id] IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
                                   16,17,18,19,20,21,22,23,24,25,26,27,
                                   28,29,30,31,32,33,34,35,36,37,38,39,
                                   40,41,42,43,44,45,46,47,48,49,50)

Conclusion

You can use both methods to filter SQL data with a list from memory. But when choosing for the join method you should be really sure your query will not reach the maximum level of nesting.

I prefer to use Contains method.

Although I used a many-to-many relation in this example, the same applies for a one-to-many scenario.

Sample code

I’ve added a small Visual Studio 2010 project so you can see the SQL statements for yourself.

You will need SQL Server Express 2008 R2 installed on your machine to run the code.

Jun 22 / Michaël Hompus

Keeping your SharePoint 2010 development databases small

With SharePoint 2010 the amount of databases on your SQL server has grown quite a bit. By default most of these databases have their recovery model set to ‘FULL’. After some time you will discover you’re running out of space.

The problem

Most likely the problem lies with the transaction logs of  your databases. With the recovery model set to ‘FULL’ the will keep storing every transaction until you make a backup. Chances are you don’t configure a backup plan for you development environment as most development databases don’t need a backup as your sources will be stored in a source control system.

The (manual) solution

To solve this problem you can change the recovery model of each database by hand. For this you can open SQL Server Management Studio (SMSS), open the properties screen for a database and navigate to the options tab. There you will find the recovery model option.

image 
Database Properties screen with the Recovery model set to ‘Simple’.

Saving this change will empty your transaction log. But it will not shrink the physical file on disk. To shrink this file you can look at the "Shrink" task.

image 
The context menu’s to shrink the size of the log files.

The (automated) solution

Executing this step for every database manually is quite some work. So you want the easy solution. :)

The following TSQL script will change the recovery model for every database to ‘simple’ and shrinks the database.

USE [master]
GO

DECLARE @dbname SYSNAME
DECLARE @altercmd NVARCHAR(1000)
DECLARE @shrinkcmd NVARCHAR(1000)

DECLARE [dbcursor] CURSOR FOR SELECT [name] FROM sysdatabases

OPEN [dbcursor]
FETCH NEXT FROM [dbcursor] INTO @dbname

WHILE
    @@FETCH_STATUS = 0
BEGIN
    IF
        (SELECT DATABASEPROPERTYEX(@dbname, 'RECOVERY')) != 'SIMPLE'
        AND
        @dbname != 'tempdb'
    BEGIN
        SET @altercmd = 'ALTER DATABASE "' + @dbname
                                               + '" SET RECOVERY SIMPLE'
        EXEC (@altercmd)

        SET @shrinkcmd = 'DBCC SHRINKDATABASE ("' + @dbname + '")'
        EXEC (@shrinkcmd)

        PRINT @dbname
    END

    FETCH NEXT FROM [dbcursor] INTO @dbname
END

CLOSE [dbcursor]
DEALLOCATE [dbcursor]
Jun 1 / Michaël Hompus

Using the people picker over a one-way trust

When you have a SharePoint farm and you want to use accounts from another domain you need a partial (one-way) or a full (two-way) trust between those domain.

A full trust is not always desirable and there your problem begins. After setting up the one-way trust you can authenticate with an account from the trusted domain, but the SharePoint People Picker doesn’t show any accounts from this domain.

It has been documented by others before, but as I ran into this recently I’ll give my summary how I fixed this.

This solution is the same for WSS 3.0/SharePoint 2007 as SharePoint 2010.

The problem

When using a one-way trust you don’t see any accounts from the other domain in the people picker.

SharePoint People Picker not showing any accounts. 
People picker not showing accounts from the other domain.

The reason

This is an example of how you could use a partial trust.

Architecture with a company and a development domain setup with a partial trust.
Example of a one-way trust architecture. 

You want to allow employees to authenticate in a development farm, but you don’t want to allow any test or service account from the development domain to authenticate in the company domain.

As the application pool account is based in the development domain it doesn’t have the right to query the company domain.

The solution

Using STSADM we can configure which forests and domains are searched for accounts by setting the peoplepicker-searchadforests property. The best part is that we can supply a username and password for a trusted domain.

SharePoint doesn’t allow you to store this username and password in plain text on the server. So you will have to configure a secure store. If you skip this step, configuring the search account for trusted domains will always fail with the following message.

Cannot retrieve the information for application credential key.

To create a credential key you will have to use the following command.

stsadm -o setapppassword -password <password>

This command has to be executed on every server in the farm.

Now you can configure the forests and domains you want to search using the following command.

stsadm -o setproperty -url <web application url> -pn peoplepicker-searchadforests -pv forest:<source forest>;domain:<trusted domain>,<trusted domain>\<account>,<password>

You can combine any number of forests and domains, but you need to specify at least one. You also need to include all forests and domains in one statement because every time you execute this command it will reset the current settings.

Also note this setting is per web application, and even per zone.

SharePoint People Picker showing an account from the one-way trusted domain. 
People picker showing accounts from the other domain.

Apr 16 / Michaël Hompus

Make your PowerPoint Presentation look good on a wide screen projector

The other day I attended a meeting where the presenter switched from a PowerPoint slide to demonstrate an application. When he made the switch it was quite obvious the beamer was setup to only display the 4:3 slides to the maximum of the white screen. Since his desktop was in a 16:10 resolution the application was falling off the screen on both sides. Which was quite a distraction.

While I was preparing a presentation myself I wanted to be sure my presentation would be in the same resolution as my desktop as I would be switching between my slides and Visual Studio.

Choosing your Aspect Ratio

For presentations on a beamer or screen there are 3 common aspect ratio’s at the moment: 4:3, 16:9 and 16:10. If you don’t know the ratio you can use the maximum resolution to determine the required ratio.

4:3

This is the classic resolution for computer screens. This is also the default for PowerPoint. 800×600, 1024×768, 1152×864 and 1280×960 are common resolutions used with the ratio.

If you show a slide on wide screen you will get black bars on the side.

A PowerPoint slide with a 4:3 ratio shows black bars on the side using a 16:10 screen. 
A 4:3 slide on a 16:10 screen. Showing black bars on the side.

16:9

This ratio is commonly used by HDTV screens and beamers. 1280×720 and 1920×1080 are common resolutions used with this ratio.

If you show a slide in this ratio on a 4:3 or 16:10 screen you will get black bars on the top and bottom.

A PowerPoint slide with a 16:9 ratio shows black bars on the top and bottom using a 16:10 screen. 
A 16:9 slide on a 16:10 screen. Showing black bars on the top and bottom.

16:10

This ratio is commonly used by modern screen and beamers. 1280×800, 1440×900 and 1920×1200 are common resolutions used with the ratio.

If you show a slide in this ratio on a 4:3 screen you will get black bars on the top and bottom. On a 16:9 screen you will get black bars on the side.

A PowerPoint slide with a 16:10 ratio shows no black bars on a 16:10 screen.
A 16:10 slide on a 16:10 screen. No black bars showing.

Setting the aspect ratio in PowerPoint

It’s easy to set the required aspect ratio:

In PowerPoint go the “Design” tab on the ribbon.

Click on “Page Setup“.

The PowerPoint application with the "Design" tab selected on the ribbon.

At “Slides sized for” set the desired ratio.

The "Page Setup" dialog with the ratio dropdown box displaying several ratio's including 4:3, 16:9 and 16:10.

Press the “OK” button and you’re set.

Apr 14 / Michaël Hompus

Using the Surface SDK with Visual Studio 2010

With the launch of Visual Studio 2010 this week a lot of people will start upgrading to the new version. After the installation was complete I noticed the Surface project and item templates were not available. In this post I explain how to get the entries in Visual Studio 2010.

First of all you need to realize that the templates are part of the Microsoft Surface SDK. If you have it installed, skip the next paragraph.

INSTALLING the SURFACE SDK

If you are running Windows 7 or a Windows Vista x64, you need to apply the changes described in a previous post: "Installing the Microsoft Surface SDK on Windows 7 x64".

If you don’t have Visual Studio 2008 on your system and want to start with Visual Studio 2010 from scratch some additional steps are needed patching the Surface SDK Installer. For details on the patch process see the earlier referenced post.

Patching the installer

When you’re patching the MSI with the Orca tool remove also the following conditions:

Select the row with "Installed OR (VS2008SPLEVEL AND VS2008CSPROJSUPPORT) OR VCSEXP2008SPLEVEL" and choose "Drop row"

Select the row with "Installed OR        (VS2008SPLEVEL AND VS2008SPLEVEL >= "#0") OR        (VCSEXP2008SPLEVEL AND VCSEXP2008SPLEVEL >= "#0")" and choose "Drop row"

Select the row with "Installed OR DEXPLORE" and choose "Drop row"

Select the row with "Installed OR VS90DEVENV OR NOT VS2008SPLEVEL" and choose "Drop row"

Select the row with "Installed OR VCSHARP90EXPRESS OR NOT VCSEXP2008SPLEVEL" and choose "Drop row"

Copying the Templates from the SDK

Now that you have installed the Surface SDK the subsequent steps are quite simple.

The following lines are for x64 systems. If you’re running on x86 change "Program Files (x86)" into "Program Files".

xcopy /s "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\Surface\v1.0" "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Surface\1033\"

xcopy /s "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\CSharp\Surface\v1.0" "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ProjectTemplates\CSharp\Surface\1033\"

cd "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE"

devenv /setup

After this is finished launch Visual Studio 2010.

Screenshot with the "New Project" dialog displaying the Surface Application templates.
The "New Project" dialog with the Surface Application templates listed.

(Remember to select .NET Framework 3.5)

Screenshot with the "Add New Item" dialog displaying the Surface Control templates.
The "Add New Item" dialog with the Surface Control templates listed.

Apr 1 / Michaël Hompus

Microsoft releases Linux Integration Services for Hyper-V 2.1 Beta

Today the Microsoft Virtualization Team announced the availability of the new beta version of the Linux Integration Services for Hyper-V. There are three big changes in this version:

  • Virtual machines will be able to use up to 4 virtual CPU’s.
  • Virtual machines will be able to synchronize their time with the parent partition.
  • Virtual machines will be able to shutdown gracefully from the Hyper-V manager.

In this post I will try the new features.

Linux Integration Services for Hyper-V 2.0

First I got a Virtual machine (VM) installed as described in my previous post “Running CentOS 5.x on Hyper-V“. I used the current released stable version of the Linux Integration Services (LIS): Version 2.0.

Screenshot displaying the VMBUS information on booting the virtual machine. Build Date=Jun 29 2009 and Build Description=Version 2.0. 
On boot time VMBUS displays Version 2.0

My Hyper-V host only has a dual-core CPU. So it’s impossible for me to test the 4 CPU support. I couldn’t find any differences with 2 CPU’s.

Shutdown from Hyper-V Console

With the current version of the LIS when I press the shutdown button I get the following error:

Screenshot displaying the Hyper-V console showing the error text: "The application encountered an error while attempting to change the state of 'BlogDemo'. Failed to shut down the virtual machine.".
Hyper-V Console shows the error “Failed to shut down the virtual machine.”

Time synchronization

With the current version of the LIS I had a lot of trouble with the clock of the VM getting out of sync very fast. I did a post to fix this: “Correcting time drift with CentOS on Hyper-V“.

I did not implement the fix on the VM I created for this post to demonstrate the problem:

Screenshot displaying the Hyper-V console showing the VM gets a time difference of multiple=
The VM gets an offset of multiple seconds within minutes.

Linux Integration Services for Hyper-V 2.1 Beta

To get the beta drivers you need to download them from the Microsoft Connect website. (Look for the Linux Integration Services for Hyper-V project site).

I installed the new drivers in exact the same way as the 2.0.

Screenshot displaying the VMBUS information on booting the virtual machine. Build Date=Mar 23 2010 and Build Description=Version 2.1.2.
On boot time VMBUS displays Version 2.1.2

Not only the new version number is displayed, also the new Shutdown and Timesync channels are mentioned!

Shutdown from Hyper-V Console

Pressing the shutdown button now gives a more expected result:

Screenshot displaying the virtual machine has received the shutdown command and starts the poweroff sequence.
The VM receives the signal to shutdown and calls /sbin/poweroff

Screenshot displaying the Hyper-V console with the message: "The virtual machine is truned off".
The VM is gracefully turned off.

Time synchronization

With the new LIS the time is pretty much stable, nothing NTP can handle. There is no need to change the boot command in grub anymore.

Screenshot displaying the Hyper-V console showing the VM shows minimal time difference over the course of minutes.
The VM only shows a minimal time difference over the course of minutes.