Saturday, March 29, 2008

Custom Actions With .Net Installer Class Yields Weird Error

The Error

The other day I was writing a custom installer for some SharePoint stuff. I ended up customizing the UI a little and mapping those user inputs to properties that I would refer to some custom actions class that I wrote.

I was following a tutorial on the web and everything seemed to be going fine until I added all my properties, ran the installer and then bam!

Low to my surprise, when I ran the installer I ended up getting an error of the form:

Error 1001. Exception occurred while initializing the installation: System.IO.FileNotFound: Could not load file or assembly 'file:///[path]' or one of it's dependencies. The system cannot find the file specified.

I ended up digging around on the web for a bit and couldn't really find anything, so after I fixed it I decided that this gotcha might deserve to be immortalized in a blog entry.

The Solution (at least for me)

The fix was of course something silly. I'm almost embarrassed. When you pass custom properties that are file paths that include spaces in them (ie. 'C:\Program Files\...') you need to wrap the whole path in quotes, this goes for custom properties too.

Further more if you're passing in the path of the installation directory (TARGETDIR) you need to not only wrap it in quotes but also put a back slash on the end of it.

/name="[TARGETDIR]\"

There's more information on the MSDN.

So I fixed my custom action expressions by wrapping them in quotes (and putting a trailing backslash on [TARGETDIR] AND I'm good to go! No more angry error.Putting quotes (

Maybe next time I'll RTFM.

Best,
Tyler

Friday, March 28, 2008

Adding SharePoint Intellisense to Visual Studio 2008

Where's My Intellisense?

By default even after installing WSS on your development machine you won't have any intellisense for XML files in Visual Studio 2008. This will probably change when they release the WSS extensions for VS 2008 but until then you have to get your hands a little dirty.

Enabling Intellisense in Visual Studio 2008

You have a couple of options so here they are:

  • If you already have WSS installed on your development machine then you simply need to create a new file (call it sharepoint.xml) and place it in the Program Files\Microsoft Visual Studio 9.0\Xml\Schemas directory and then place the following XML in it. Now Restart Visual Studio.
<SchemaCatalog xmlns="http://schemas.microsoft.com/xsd/catalog">
  <Schema href="%CommonProgramFiles%/Microsoft Shared/web server extensions/12/TEMPLATE/XML/wss.xsd" targetNamespace="http://schemas.microsoft.com/sharepoint/" /> 
</SchemaCatalog>
  • Your Second option (if you don't have WSS installed) is do the option above, AND THEN to download this file (the missing wss.xsd) and place it in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML directory (create it if you don't have it or modify the path in the first step to point to wherever you want to put it. Now Restart Visual Studio.

Done! The next time you open up an XML file and specify the xmlns you should see SharePoint as an option and after selecting it you should have full intellisense (as soon as the xmlns gets specified).

Specifying the sharepoint xmlns in an XML file to get xml intellisense. 

WSS Intellisense in Visual Studio 2008

Hope that helps
Tyler

Walkthrough: Creating a SharePoint Feature Receiver and Custom Link with WSS Extensions

Why You Should Start Putting Everything in Features

If you're just getting into .wsp development with SharePoint these days you're in luck, you've managed to skip a couple years of pain when it comes to getting retractable functionality into SharePoint. With the WSS Extensions for Visual Studio a lot this stuff has never been easier.

It's relatively "easy" to get content INTO SharePoint, it seems like anyone with a copy of SharePoint Designer can embed files, lists, .aspx's in a WSS instance.

The REAL problem is getting your stuff out cleanly, and to allow yourself a managed upgrade when it comes time to maintain this content. Hopefully some plan that DOESN'T rely on you moving files around in SPD or visiting a series of web.config's and bin folders on all the Web Front End machines in the farm. This is the beauty of features and solutions and this walkthrough will show you how to create a simple feature that does the following:

  1. Copy a local file into the WSS instance.
  2. Add a link to the Site Actions menu.
  3. Modify the web.config programmatically on all machines in the farm.

Before You Start

If you get lost you can download a copy of a working solution here. Before you can open it though you'll need a copy of Visual Studio .NET 2005 and a copy of WSS Extensions for VS 2005 v1.1. Unfortunately there isn't support for VS 2008 yet word on the street has it that it'll be out in June of this year (2008).

What You Should Know about Solutions

Solutions (.wsp) are .cab files. You can take any .wsp rename it to a cab and open it up in WinRar or unpack it with WinZip (useful for debugging). Solutions pretty much all look the same. They all:

  • Have a manifest.xml in the root. This lists all the Features, Web Parts and assemblies in the solution. The schema of valid tags for a solution can be found here.
  • Underneath the root of the cab is usually a directory for each feature that is in the solution.
  • Inside each feature folder is usually a file named feature.xml which describes a the title, description, a feature receiver if one exists, the scope of the feature (Web, Site, Web Application or Farm), the location of dependent files, and the a series of element manifests (elements.xml) which usually sit in their own folder underneath the feature folder.
  • Element manifests (elements.xml) describes pretty much all the functionality that this feature will have. It's in these elements.xml that we can specify custom links, new list instances, content types, workflows etc... What can be described in an elements.xml (which is quite a bit) is listed here.
  • Below is a sample solution which contains a Solution (FeatureDemo) which has a manifest.xml, one feature that has one elements.xml. The sample.txt is an example of a dependent file (like a .aspx) that we plan on deploying as part of this feature.Anatomy of SharePoint Solution

Walkthrough

  1. After the extensions have been installed, open up VS and create a New Project. Choose an Empty project, name it and click OK.Create new empty project with Visual Studio WSS Extensions
  2. Right click on your new project->Add->New Item, choose a Module and name it Install Content. Rename the Module.xml file that gets created to elements.xml (this isn't a requirement, but it's more in line with the typical anatomy of a solution). It's of note that whenever we add items to our project this way (using Right Click->Add) we're really setting up a new feature. Every folder that gets added by you adding WSS items to the project will for the most part end up equating to a Feature in our .wsp. Renaming Module.xml to elements.xml
  3. The WSS Extensions v1.1 add a new window that you can view WSP anatomy with called the WSP View. You can get to it via View->Other Windows->WSP View. When you do this you can see the anatomy of your solution. It should now look something like:Anatomy of WSP
  4. See the sample.txt? Right now that will get deployed to the root of our site as dictated by the elements.xml.
  5. Now we're going to add a link to Google.com on the site actions menu. In the Solution Explorer, open up elements.xml and add the following xml just below the <Elements> tag.
    <CustomAction 
        GroupId="SiteActions" 
        Location="Microsoft.SharePoint.StandardMenu" 
        Sequence="1000" 
        Title="Google Search" 
        ImageUrl="/_layouts/images/ActionsSettings.gif" 
        Description="A public search engine." 
        > 
        <UrlAction Url="http://google.com"/
    </CustomAction>
    If you want to dig more into what you can do with custom link actions there's more details on custom action attributes here. Essentially you can add custom links almost anywhere in SharePoint, from Lists, to Items to Context Menus...it's quite extensive.
  6. Now the feature receiver, in the Solution Explorer add a new class called FeatureReceiver.cs under the InstallContent folder.Adding a Feature Receiver
  7. Add a reference to Windows SharePoint Services (or Microsoft.SharePoint.dll) and then copy/paste in the following code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace FeatureDemo
{
    public class FeatureReceiver : SPFeatureReceiver
    {
        //All the modifications we will be doing to the Web.Config when we activate/deactivate this feature.
        private ModificationEntry[] entries =
            {
                //Ensure there's a connectionStrings section.
                new ModificationEntry(
                    "connectionStrings"
                    ,"configuration"
                    ,"<connectionStrings/>"
                    ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
                    ,true)
                //Create the connectionstring.
                ,new ModificationEntry(
                    "add[@name='ConnectionString'][@connectionString='Data Source=serverName;Initial Catalog=DBName;User Id=UserId;Password=Pass']"
                    ,"configuration/connectionStrings"
                    ,"<add name='ConnectionString' connectionString='Data Source=serverName;Initial Catalog=DBName;User Id=UserId;Password=Pass'/>"
                    ,SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode
                    ,false)
            };

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            //Get a reference to the web application and then remove entries for the blorum.
            SPSite site = properties.Feature.Parent as SPSite;
            SPWebApplication webApplication = site.WebApplication;

            site.RootWeb.Title = "Set from activating code at " + DateTime.Now.ToString();
            site.RootWeb.Update();

            foreach (ModificationEntry entry in entries)
                webApplication.WebConfigModifications.Add(CreateModification(entry));

            webApplication.WebService.ApplyWebConfigModifications();
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            //Get a reference to the web application and then remove entries for the blorum.
            SPSite site = properties.Feature.Parent as SPSite;
            SPWebApplication webApplication = site.WebApplication;

            site.RootWeb.Title = "Set from deactivating code at " + DateTime.Now.ToString();
            site.RootWeb.Update();

            foreach (ModificationEntry entry in entries)
            {
                //Some entries we create but do not remove
                if (!entry.CreateOnly)
                    webApplication.WebConfigModifications.Remove(CreateModification(entry));
            }

            webApplication.WebService.ApplyWebConfigModifications();
        }

        /// <summary>
        /// Event that fires after this feature is installed.
        /// </summary>
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
        }

        /// <summary>
        /// Event taht fires before this feature is installing.
        /// </summary>
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
        }

        /// <summary>
        /// Creates a SPWebModificaion object based on our parameters.
        /// </summary>
        private SPWebConfigModification CreateModification(ModificationEntry entry)
        {
            SPWebConfigModification modification = new SPWebConfigModification(entry.Name, entry.XPath);
            modification.Owner = "OurApplicationName";
            modification.Sequence = 0;
            modification.Type = entry.ModificationType;
            modification.Value = entry.Value;

            return modification;
        }

        /// <summary>
        /// Container to hold info about our modifications to the web.config.
        /// </summary>
        private struct ModificationEntry
        {
            public string Name; //Name of the node
            public string XPath;//Where the node is located
            public string Value;//The value of the attribute/node
            public SPWebConfigModification.SPWebConfigModificationType ModificationType;
            public bool CreateOnly;//Whether we should create and not remove the node when deactivating the feature.

            public ModificationEntry(string name, string xPath, string value,
                SPWebConfigModification.SPWebConfigModificationType modificationType, bool createOnly)
            {
                Name = name;
                XPath = xPath;
                Value = value;
                ModificationType = modificationType;
                CreateOnly = createOnly;
            }
        }
    }
}

It's worth mentioning that this is an adaptation of a post by Ted Pattison, a SharePoint consultant and training. Lets talk about this code for a second. We essentially create a list of modifications that we're going to do to the web.config. We do these on FeatureActivated and clean up after ourselves on FeatureDeactivating. This forms the basis of cleaner SharePoint development. For demo purposes we also rename the site title to the current time stamp so that we know the code ran.

8. Now open up the feature.xml in the WSP View and change the Scope to "Site" and register our event receiver. Depending on what you named your project your feature.xml file should should look something like this:

<?xml version="1.0" encoding="utf-8"?>
<Feature
    Id="beb48f0a-9c42-40b9-a4b5-9d01d23106a8"
    Title="InstallContent"
    Scope="Site"
    Version="1.0.0.0"
    Hidden="FALSE"
    DefaultResourceFile="core"
    xmlns="http://schemas.microsoft.com/sharepoint/"
    ReceiverAssembly="FeatureDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5"
    ReceiverClass="FeatureDemo.FeatureReceiver"
    >
  <ElementManifests>
    <ElementManifest Location="InstallContent\elements.xml" />
    <ElementFile Location="InstallContent\sample.txt" />
  </ElementManifests>
</Feature>

9. We're almost there! Finally open up your project properties and under the Debug section set the URL to the SharePoint site you want to deploy to. Now press F5 and watch it go!Setting the deployment URL for the WSS Extensions

10. That's it. If you now visit the site you'll see that our feature was activated under Site Actions->Site Settings->Site Collection Features. You can deactivate if you want and all our modifications through this feature will be gracefully removed. You can see our link to Google in the Site Actions Menu, our changed website title telling us that the activation code ran, our sample.txt document has been deployed to the site root, AND if you dig up the web.config you'll see our added <ConnectionStrings/> section and a connection string that we set. These changes will also disappear if you deactivate the feature.InstallContent demo feature deployed and activated. Changed site title and link in Site Actions menu. Modification to web.config done through SPWebConfigModification 

There's also now a FeatureDemo.wsp file in the \bin\Debug\ folder. We could take this and deploy it to another farm using the following commands.

stsadm -o addsolution -filename FeatureDemo.wsp

stsadm -o deploysolution -name FeatureDemo.wsp -immediate -allowgacdeployment -url http://w2k3-tyler-virt:83">http://w2k3-tyler-virt:83

stsadm -o activatefeature -id {featureGuid} -url http://w2k3-tyler-virt:83

In doing so we've now deployed content that is easily retractable AND upgradable. I dream of a day where all SharePoint content is delivered with this level of thoughtfulness!

There's a good chance that some of those steps were confusing, and because of that you can download the source project here.

There's a lot available when it comes to SharePoint development these days. I think the biggest paradigm shift most ASP.NET developers are going through is realizing that we are now participating in a large shared platform with SharePoint. We are no longer writing stand alone apps where the ASP.NET developer is boss. With WSS/MOSS development a lot more consideration to Farm Administrators and other WSS Developers is in order when rolling out your content.

Take Care,
Tyler

Wednesday, March 19, 2008

Restoring a Shared Service Provider Admin Site

Caveat

Note, this blog entry demonstrates how to restore a shared service provider administration site. Before you get your hands dirty it would be a good idea to backup up your SharePoint databases, and to ensure you still have all the necessary databases for the SSP that is throwing the error below.

Background

The other day I found a pretty strange error in the event log on one of our shared development machines. It sang a little to the tune of:

Event Type: Error
Event Source: Office SharePoint Server
Event Category: Office Server Shared Services
Event ID: 5290
Date: 3/18/2008
Time: 9:02:01 PM
User: N/A
Computer: [ComputerName]
Description:
There is no administration site associated with the Shared Services Provider SharedServices1.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

This is a pretty unfortunate error. I usually happens when someone accidentally deletes a Shared Service Provider administration site. People usually do it because they think it's a rogue site that no ones using and often the content database (for the admin site, not the SSP) gets deleted too.

What's a Shared Service Provider?

When you install a MOSS as a stand alone farm (all on one machine) a couple of web applications are created for you.

  • A default site stubbed out with dummy content.
  • The Central Administration Site (to administrate the farm).
  • A Shared Service Provider which is hosted in a web application.
  • An administration site to administer the default Shared Service Provider.

If you did an advanced install you probably ended up creating these items individually at some point during your more lengthy install process. But then again if you're savvy enough to set up a WSS/MOSS farm you're probably not the kind of character to accidentally delete an administration site.

Shared Service Providers (SSP) provide a lot of the extended functionality that is WSS/MOSS. SSP's are responsible for things like Search/indexing, My Site hosting, Profiles, Audiences (for content targeting), Portal Usage Reporting (enhanced Usage reports), Excel Services, and the Business Data Catalog. Most of that list is implemented only in MOSS.

Any ways you normally administer all these Shared Services with a series of SSP Administration Sites...but someone's gone and deleted one! Now before you go fire someone understand that it's kind of an understandable mistake, plus you can create a new one and hopefully not loose any data from your SSP. The SSP Admin Site looks like a random SharePoint site, something like http://ServerName:#####. The numbers are pseudo random, the one I went and deleted for these screen shots was at http://w2k3-tyler-virt:44778. It's of note that this may be more complicated for a farm scenario since services are usually spread over multiple SSPs which will have many content databases spread over (potentially) many machines. I would not recommend following the steps below to repair trouble in a farm. Stand alone installs only.

Recreating a Shared Service Provider and Admin Site

The easiest way out of this is to create a new admin Site. I'm going to naively assume that this is on a stand alone install, if it's in an enterprise farm you probably need a little more instruction than a blog entry given that you could have a lot more complexity in play.

  1. Open up the database instance for your WSS/MOSS instance. If you don't know what I'm talking about instructions can be found here. Ensure that you still have databases for the SSP that you deleted. If your Shared Service Provider was called SharedServices1 then you're looking for a database of the form (default names at least) SharedServices1_DB_[GUID] and SharedServices1_Search_DB_[GUID]. For example my databases were called: SharedServices1_DB_6c907a40-1cea-4599-bf83-13c3157f08d0 and SharedServices1_Search_DB_3de60ba5-022e-494b-8042-d6df471167b3. Rolls off the tongue huh?
  2. Open up the Central Administration Site and click on "Shared Services Administration". If you really have deleted the site the name of the Shared Services Provider (ie. SharedServices1) is probably a dead link.
  3. Click on New SSP to create a new one. We're going to do this so that we can set the new SSP to be the default and delete the old SSP (without deleting it's databases). Then we're going to restore the old SSP from it's content databases, make it the default SSP and finally assign the web applications back to it.Adding a new Shared Service Provider.
  4. Fill out all the fields (I named mine SharedServices2 in honor of it's fallen brother). Create a new Web Application to house this site, put it in it's own Application Pool. For some of the more background information on configuring an SSP and what the fields do there's this technet article. For a stand alone install I ended up using NT AUTHORITY\LOCAL SERVICE as a credential which I'm still not sure if it's the appropriate credential. I was a little scared that Network Service might not have enough mustard to get at all the content it needed. A lot of this won't matter until you need to do the restore and you'll have to put in values that you really care about. When creating the SSP at this step ignore the warnings and just familiarize yourself with the options.
  5. If this worked out we should see something like the picture below, two Shared Service Providers. Our now make SharedServices2 (or whatever you called it) the default SSP and delete SharedServices1 so we can restore it as a new SSP (that has an Administration site).New Shared Service Provider created.
  6. Click on Shared Services Administration->Change Default SSP (from the Shared Services Administration) and choose your new SSP (SharedServices2).
  7. Delete the original SSP (SharedServices1) by choosing delete from it's context menu. DO NOT DELETE IT'S ASSOCIATED DATABASES! This might take a second to complete.Deleting a Shared Service Provider.
  8. Now there's just one SSP (SharedServices2), notice how SharePoint automatically assigns the web applications in the farm to use this SSP. Now we're going to click on Restore SSP and restore the original SSP with a brand new Administration site. Name it SharedServices1 and fill out fields similar to what you did in step 4. Create a new web application and when it comes time to fill out the fields for Search Database and but this time fill out the two databases for the SSP. This will include the databases that we identified in step 1. IE. SharedServices1_DB_6c907a40-1cea-4599-bf83-13c3157f08d0 and SharedServices1_Search_DB_3de60ba5-022e-494b-8042-d6df471167b3. This will ensure that you don't lose all your data from the SSP like Search Content Sources, Excel Services Settings, Audiences, BDC etc...
  9. No we should have two working SSPs, SharedServices1 and SharedServices2. Click Change Default SSP and change it back to SharedServices1. After that delete SharedServices2 (this might take a while). Don't feel guilty about deleting associated content databases either, there's no real important data in them anyways. SharePoint should make all your web applications use your newly restored SSP (SharedServices1). You should almost be good to go.Two shared service providers
  10. When you're all done it should look like below. If you wait long enough everything should propagate but if you want immediate results I'd suggest rebooting the machine and making sure that the error is no longer in the event log AND all your settings are still kosher in the SSP (ShareServices1) admin site. I know there were quite a few steps but this was hard to break down and still provide any measure of detail.Working Shared Service Provider and administration site.

That's it, hopefully that made a little bit of sense. I get the feeling that for anything more than 5 steps a screen cast is probably in order. I may start to get into that I heard that MS offers free Silverlight hosting for videos.

Good luck,
Tyler

Tuesday, March 18, 2008

Windows Stuff: Adding Custom Actions to Context Menus

This post is pretty simple, it's really to immortalize some pretty simple stuff that I seem to keep forgetting in my head. Blogging something has a tendency to sear things into my head.

Larry Wall once listed the three chief virtues of the programmer as: Laziness, Impatience and Hubris. This is really about the first quality. I downloaded Paint.NET the other day and wanted to be able to open certain types of content with Paint.NET with the context menu (see below). On some of my machines this either isn't available or doesn't work very well. This is how you add commands to the context menu for a given file type. To be clear we want the following behavior.

Custom right click context menu action.

To pull this off is quite simple you first:

  1. Open Windows Explorer, click Tools->Folder Options->File Types(tab) scroll down to the file type you want to add the action for.
  2. After you have your type of file selected (ie. GIF) click Advanced.
  3. Click New to add a new Custom Action, give it a name and then an application to run. You can specify parameters using "%1" "%2" etc...Adding custom action to right click context menu.
  4. It's important to put quotes (") around the parameters in case they happen to be in paths that involve white space. I won't really take the time to get in to DDE because I've never really gotten it to work consistently.
  5. Finally click OK and you should be off to the races.

Short but sweet,
Tyler

Wednesday, March 12, 2008

SharePoint: Checking User Permissions On a Web/List/Item

When writing any kind of application that has more than one role it's often the case that you end up showing/hiding page elements based on the users security. This holds true for applications and Web Parts that are in SharePoint as well. Thankfully this is taken care of for us in WSS/MOSS, at least when it comes to Webs (SPWeb), Lists (SPList) and List items (SPListItem). All these objects (and potentially more) implement the ISecurableObject interface which among other useful methods and properties has the DoesUserHavePermissions() method.

This method takes one or more SPBasePermissions and will tell you if the current SPUser (or another one if you're checking SPList/Item/Web/Site) has the given permissions. There's a ton of permissions that are available, and they're analogous to what you would set in the permission screen in the SharePoint UI. You can find a list of them here.

It's important to note that the SPBasePermission enum uses the [Flags] attribute which means you can check many at the same time by using the bit wise OR (|) operator. You can check to see if a user has permissions on a list (or any ISecurableObject) this with code like:

if (list.DoesUserHavePermissions(SPBasePermissions.ViewListItems)
{
//do something clever...
}

Because they all implement ISecurableObject you can use similar code on SPWeb, SPList and SPListItem.

To programmatically check to see if there is anonymous access allowed on a list you can do something like:

if ((list.AnonymousPermMask64 & SPBasePermissions.ViewListItems) == SPBasePermissions.ViewListItems)
{
// do something clever...
}

Finally I'd like to share a wonderful control that's saved my bacon a couple of times. Because our changes to SharePoint should always be subtle and involve as little code as possible, it's often convenient to show hide content by declaratively setting down some syntax in SharePoint Designer. The SPSecurityTrimmedControl allows us to do just that. This control will hide content for any user who doesn't have the appropriate permission. I often use this control to wrap the Site Actions Menu to hide the "View All Site Content" when that's the only permission users have. For example:

<Sharepoint:SPSecurityTrimmedControl runat="server" PermissionsString="AddAndCustomizePages" PermissionContext="CurrentSite">
Any HTML or controls between these tags will only show for users who have the 'AddAndCustomizePages' permission.
</SharePoint:SPSecurityTrimmedControl>

Note that the permissions that appear in the PermissionsString are analogous to those listed above, so strings like 'ViewListItems' apply. You can also set an optional PermissionContext which can be any of the contexts listed in the enum.

Hope some of that's useful.

Best,
Tyler

Friday, March 7, 2008

And Then Win XP SP2 Wouldn't Give Me 4 GB of Memory

The other day I was firing up a virtual machine and got an error message that looked a lot like:

The virtual machine could not be started because there was not enough memory available on the host.

This confused me because I am supposed to have a sizable 4 GB of memory on this computer. Sure enough I fire up the System Properties and find out I only have 3 GB (2.98) of memory!? What happened to oh say...that other gigabyte that I paid for and then laboriously installed?

2008-03-07 17.59.07

I wanted to make sure that the memory wasn't bad so I booted into OS X (this is an iMac) and saw all 4 GB gloriously presented to the other OS. So I had to wonder, is this Boot Camp or Windows XP SP2 making my life harder than it needs to be?

Picture 6

I dug around on the information super highway and found a bunch of info in forums, but a lot of it was misleading (which is why I'm writing this in the first place). To set the record straight, you're screwed. Yes it IS possible for a 32 bit operating system like Windows XP to address 4 GB of memory, and technically it does, just not the way you'd like it too. It's of note that some people get 3.5 GB to show, others get 3.7GB it kind of depends what kind of other devices you have in your computer.

Before I continue let me first say that changing the boot.ini and adding /3GB and /PAE will NOT help Windows XP recognize any more memory. This was probably the biggest source of confusion that I found while bouncing around in the forums. If you value your time, don't throw it at these pointless flags.

The Windows Server Performance Team as a blog post that describes why /3GB and /PAE will not help you here. Essentially (and I'm paraphrasing here from a bright guy named Dan) Windows 32 DOES support 4 GB of memory. But this includes the memory for ALL the devices in your computer. Some of that addressable range is eaten up by other devices. If you have a 512MB video card you will never see more that 3.5 GB of memory, less any other devices (some of which are system reserved blocks). For more info see Dan's post.

The real moral of the story is you're hosed. Stop wasting your time. If you want all 4 GB to show run a 64 bit OS you old timer!

1 GB Smaller,
Tyler

Wednesday, March 5, 2008

Work Around When Impersonating, Authenticating Across Non Trusted Domains

The Problem

The other day I was taxed with impersonating a credential that was in another domain! Let me explain. I have two machines, one in DomainA and the other in DomainB. We have code that needs to run on DomainA\Machine1 and copy files to a share on DomainB\Machine2.

It's of note that because there's no trust between DomainA and DomainB there is no credential that exists that we can use to copy these files that both domains will know about.

For example, you can set up a share on DomainB\Machine2, but who's credentials do you give rights to? Even if you give rights to 'Everybody' that still just means everybody in the domain (DomainB) and on the local machine, no one in DomainA will be able to get access to that share. Further more you can't run code on DomainA\Machine1 and try to impersonate a credential in DomainB, you'll get the following exception.

System.ComponentModel.Win32Exception: Logon failure: unknown user name or bad password.

This makes sense, DomainA has no idea what the credentials are like in DomainB and has no real means of checking.

The Workaround

I remember reading an article about Remote Debugging not too long ago and a similar problem I faced at a client location; trying to remotely debug an machine in a foreign domain when you don't have a domain account! It's complicated because the machine running the remote debugger needs to know that you're a safe user before it allows you to block a whole w3p.exe process. As it turns out you can create a credential with the same name and password on both computers and they'll honor each others credentials and their associated rights. The table below is from said article. It describes what accounts you can use to debug remotely given two different machines. Notice that the 'Local accounts with same user name and password on both computers' column is Yes ever time. We can apply this same technique to solve our problem. We create two local accounts with the same username and password on both machines and we're in business.

Computer setup Local System account Domain account Local accounts with the same user name and password on both computers
Both computers on the same domain Yes Yes Yes
Both computers on domains with two-way trust No No Yes
One or both computers on a workgroup No No Yes
Computers on different domains No No Yes

The Icing

Impersonation is usually a headache. It often involves calling a P/Invokes into the Win32 API and is often as messy as it comes. Enter the Impersonator, a class written by Uwe Keim and posted on Code Project. I tell you this little class has saved my bacon a ton of times. What I like the most about it (besides the fact that it works well) is the clear demarcation that Uwe creates between codes running on different contexts by implementing IDisposable. It's very clear which code is running on credential A, and which code is running on credential B. It's used like yo:

Console.WriteLine("User: {0}", WindowsIdentity.GetCurrent().Name); 
//Change your credentials 
using (new Impersonator("userName", "domain", "password")) 

  //now running as user "domain\userName". 
  Console.WriteLine("User: {0}", WindowsIdentity.GetCurrent().Name); 
  //Conduct your elivated privilege business. 

//Continue on as your previous credential set. 
Console.WriteLine("User: {0}", WindowsIdentity.GetCurrent().Name);

So that's pretty much it. I think that's gotta be one of the best things about being a developer, sometimes someone does all the hard work for you.

Best,
Tyler

Sunday, March 2, 2008

ConnectionStrings: What's the Difference between (local) vs. LocalHost vs. "."

There's a lot of things in this world I don't really think about. Until not too long ago one of these things was how many different there are of specifying the name of a local machine when connecting.

For instance when connecting to a machine what's the difference if I connect to "localhost", "(local)" or simply "."?

The answer ends up being it depends on the provider you're using to connect. If you're using the SQL Server Native Client or the .NET Data Provider for .NET 2.0 the behavior is the same regardless of which name you use (localhost/(local)/or "."). These providers will first try to connect using Shared Memory, then try to connect using TCP/IP and then finally Named Pipes. So for these providers it doesn't really matter what you use to specify the server name, they both behave the same.

Developers who work with other providers though know that these aren't all equivalent. Someone who's intimately familiar with the Microsoft OLE DB Provider for SQL Server have probably had occasions where they could connect with "(local)" but not with "localhost" or ".". This is because for the Microsoft OLE DB Provider for SQL Server provider will do a little more work for (local) than it will for "localhost" or ".". If you specify (local) it will behave as mentioned above, it will first try to connect via Shared Memory, then TCP/IP and then finally via named pipes. If you specify "localhost", or "." however these are protocol specific for this provider. That is, they will only try to connect via the protocol that you specify.

I guess as a rule of thumb if you just want to connect and don't care for details you might want to consider always connecting to "(local)". Chances are that (depending on your provider) it will do a little more leg work for you. If you're neurotic enough to want to know the exact protocol that you're using to enter the data store (could be useful to know when it comes time to deploy) then use either "localhost" or ".".

Hope that sheds a little light.

Regards,
Tyler