Apr 23 2008

CS2008

Category: IT RelatedRory Primrose @ 19:21

I have upgraded the blog to CS2008 tonight. It mostly went well, but I did waste an hour trying to get my media files to be served. Turned out that the issue was that the AppPool in IIS now needs to be run under Integrated rather than Classic. See here.

Tags:

Apr 20 2008

Give me the media and the Ultimate Boot CD

Category: Applications | IT RelatedRory Primrose @ 05:43

My dad bought a laptop that came with the typical factory image full of junk. It crashed constantly, didn't detect the anti-virus that was installed and wouldn't allow me to activate Windows Defender. It also only had the system partition for data. All good reasons for a rebuild. HP kindly don't offer media for recovery though. Not being familiar with this, I wiped the system drive for a rebuild without first burning the recovery partition to a DVD. After lots of attempts at recovery and installation, one of my actions was to set the recovery partition as the active partition so I could boot straight into it. That worked until the recovery image wanted to reboot, then I couldn't set the active partition back.

I miss the days when manufacturers would just include a couple of DVD's. Are they really that expensive to press that they can't include the disks?

Any way, Ultimate Boot CD became a lifeline.

Tags:

Apr 15 2008

Lying to WCF

Category: IT Related | .NetRory Primrose @ 15:16

There are cases when you need to transmit username/password credentials to WCF without transport security. The times that you should do this are rare because of the obvious security implications of sending credentials over the wire without encryption. One case where this is required is where hardware acceleration is used in a load balancer. The traffic between the load balancer and the client is encrypted, but the traffic between the load balancer and the service host is not. The issue here is that the service still needs the credentials passed to the load balancer from the client.

Drew Marsh has a great write up about how to lie to WCF about the security of the binding that it is using for a service. Nicholas Allen has also posted on the topic here and here.

Tags: ,

Apr 7 2008

WCF Security: Getting the password of the user

Category: .Net | My SoftwareRory Primrose @ 15:41

A common problem with service security is that username/password security is needed for authentication and authorization at the service boundary, but those same credentials are also required to consume other resources such as a database or underlying service. By default, username/password security will run the authentication and authorization of the credentials but only the username is available to the executing service code. This is typically made available through Thread.CurrentPrincipal.Identity.Name.

Storing username password credentials in a custom principal and identity against Thread.CurrentPrincipal is a really nice way of going. Thread.CurrentPrincipal returns IPrincipal which is a common framework type that will be available to all layers of a service executed by the thread. If Thread.CurrentPrincipal.Identity can return a custom IIdentity, then this is where the password can be made available. Using Thread.CurrentPrincipal frees up business and data access layers from relying on any security design that is tied up with a specific service implementation. The trick is how to get username password information into the thread that executes the service code.

One place that both the username and password is available is in the UserNamePasswordValidator.Validate() method. You could write a custom validator and hook it up in your service configuration, but this doesn't help you. There is no native facility to store the password from the validator. You can't store any credentials against Thread.CurrentPrincipal as the validator gets evaluated by WCF on a different thread than the thread that executes the service code. You could manually put it somewhere like in a static collection, but this would have potential security risks as your code will be handling the safety and security of multiple sets of credentials for users. This also means that somewhere else in the service implementation would require the credentials to be pulled out of the static and put into Thread.CurrentPrincipal. Using validators for this purpose is not the answer. Validators are for validating credentials, not storing them for later use.

Thankfully, there is a much more elegant way of passing these credentials around compared to the validator based solution. There is another place in WCF where there is access to both the username and the password and the security context of the service. Leveraging WCF extensibility is the answer.

Setting up the security context of the service is done using IAuthorizationPolicy implementations. The place in WCF where there is access to the username, password and IAuthorizationPolicy configuration is CustomUserNameSecurityTokenAuthenticator.ValidateUserNamePasswordCore. The ValidateUserNamePasswordCore method is passed the username and password parameters and returns a readonly collection of IAuthorizationPolicy objects. SecurityTokenAuthenticator, from which CustomUserNameSecurityTokenAuthenticator ultimately inherits from, is not configurable in WCF itself. Using Reflector to follow the calls against SecurityTokenAuthenticator, the place that is extensible in WCF such that a custom SecurityTokenAuthenticator can be used is ServiceCredentials. Creating a custom ServiceCredentials object, using custom objects between the ServiceCredentials and SecurityTokenAuthenticator call stack is the answer.

PasswordServiceCredentials

The custom ServiceCredentials class implementation returns a custom SecurityTokenManager if custom username password validation is enabled.

public class PasswordServiceCredentials : ServiceCredentials
{
    public PasswordServiceCredentials()
    {
    }

    private PasswordServiceCredentials(PasswordServiceCredentials clone)
        : base(clone)
    {
    }

    protected override ServiceCredentials CloneCore()
    {
        return new PasswordServiceCredentials(this);
    }

    public override SecurityTokenManager CreateSecurityTokenManager()
    {
        // Check if the current validation mode is for custom username password validation
        if (UserNameAuthentication.UserNamePasswordValidationMode == UserNamePasswordValidationMode.Custom)
        {
            return new PasswordSecurityTokenManager(this);
        }

        Trace.TraceWarning(Resources.CustomUserNamePasswordValidationNotEnabled);

        return base.CreateSecurityTokenManager();
    }
}

PasswordSecurityTokenManager

The custom SecurityTokenManager returns a custom SecurityTokenAuthenticator when it finds a SecurityTokenRequirement for username security. It also ensures that a default validator is available if one is not configured.

internal class PasswordSecurityTokenManager : ServiceCredentialsSecurityTokenManager
{
    public PasswordSecurityTokenManager(PasswordServiceCredentials credentials)
        : base(credentials)
    {
    }

    public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
    {
        if (tokenRequirement.TokenType == SecurityTokenTypes.UserName)
        {
            outOfBandTokenResolver = null;

            // Get the current validator
            UserNamePasswordValidator validator =
                ServiceCredentials.UserNameAuthentication.CustomUserNamePasswordValidator;

            // Ensure that a validator exists
            if (validator == null)
            {
                Trace.TraceWarning(Resources.NoCustomUserNamePasswordValidatorConfigured);
                validator = new DefaultPasswordValidator();
            }

            return new PasswordSecurityTokenAuthenticator(validator);
        }

        return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
    }
}

PasswordSecurityTokenAuthenticator

The custom SecurityTokenAuthenticator is where the half of the magic happens. Here there is the opportunity to return custom a IAuthorizationPolicy implementation that will allow us to inject a custom IPrincipal and IIdentity on the thread the executes the service code.

internal class PasswordSecurityTokenAuthenticator : CustomUserNameSecurityTokenAuthenticator
{
    public PasswordSecurityTokenAuthenticator(UserNamePasswordValidator validator)
        : base(validator)
    {
    }

    protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateUserNamePasswordCore(String userName, String password)
    {
        ReadOnlyCollection<IAuthorizationPolicy> currentPolicies = base.ValidateUserNamePasswordCore(
            userName, password);
        List<IAuthorizationPolicy> newPolicies = new List<IAuthorizationPolicy>(currentPolicies);

        newPolicies.Add(new PasswordAuthorizationPolicy(userName, password));

        return newPolicies.AsReadOnly();
    }
}

PasswordAuthorizationPolicy

The IAuthorizationPolicy implementation is where the other half of the magic happens. This policy gets passed the username and password so that it can store the password in the security context. The policy will return false until it finds a GenericIdentity in the evaluation context that has the same username as the one provided to the policy. It then creates a custom IIdentity that exposes both the username and password and stores it back into the collection of identities in the evaluation context and also stores it against the PrimaryIdentity property. PrimaryIdentity is then exposed through ServiceSecurityContext.PrimaryIdentity in the service implementation. A custom principal is then created (without roles) and stores it against the Principal property of the context. Principal is then injected into Thread.CurrentPrincipal by WCF (depending on configuration).

using System; 
using System.Collections.Generic; 
using System.IdentityModel.Claims; 
using System.IdentityModel.Policy; 
using System.Security.Principal; 
using System.ServiceModel; 
using System.Threading; 

namespace Neovolve.Framework.Communication.Security 
{ 
    internal class PasswordAuthorizationPolicy : IAuthorizationPolicy 
    { 
        public PasswordAuthorizationPolicy(String userName, String password) 
        { 
            const String UserNameParameterName = "userName"; 

            if (String.IsNullOrEmpty(userName)) 
            { 
                throw new ArgumentNullException(UserNameParameterName); 
            } 
  
            Id = Guid.NewGuid().ToString(); 
            Issuer = ClaimSet.System; 
            UserName = userName; 
            Password = password; 
        } 
  
        public bool Evaluate(EvaluationContext evaluationContext, ref object state) 
        { 
            const String IdentitiesKey = "Identities"; 
  
            // Check if the properties of the context has the identities list 
            if (evaluationContext.Properties.Count == 0 
                || evaluationContext.Properties.ContainsKey(IdentitiesKey) == false 
                || evaluationContext.Properties[IdentitiesKey] == null) 
            { 
                return false; 
            } 
  
            // Get the identities list 
            List<IIdentity> identities = evaluationContext.Properties[IdentitiesKey] as List<IIdentity>; 
  
            // Validate that the identities list is valid 
            if (identities == null) 
            { 
                return false; 
            } 
  
            // Get the current identity 
            IIdentity currentIdentity = 
                identities.Find( 
                    identityMatch => 
                    identityMatch is GenericIdentity 
                    && String.Equals(identityMatch.Name, UserName, StringComparison.OrdinalIgnoreCase)); 
  
            // Check if an identity was found 
            if (currentIdentity == null) 
            { 
                return false; 
            } 
  
            // Create new identity 
            PasswordIdentity newIdentity = new PasswordIdentity( 
                UserName, Password, currentIdentity.IsAuthenticated, currentIdentity.AuthenticationType); 
            const String PrimaryIdentityKey = "PrimaryIdentity"; 
  
            // Update the list and the context with the new identity 
            identities.Remove(currentIdentity); 
            identities.Add(newIdentity); 
            evaluationContext.Properties[PrimaryIdentityKey] = newIdentity; 
  
            // Create a new principal for this identity 
            PasswordPrincipal newPrincipal = new PasswordPrincipal(newIdentity, null); 
            const String PrincipalKey = "Principal"; 
  
            // Store the new principal in the context 
            evaluationContext.Properties[PrincipalKey] = newPrincipal; 
  
            // This policy has successfully been evaluated and doesn't need to be called again 
            return true; 
        } 

        public String Id 
        {
            get;
            private set;
        } 


        public ClaimSet Issuer 
        { 
            get; 
            private set; 
        } 
  
        private String Password 
        { 
            get; 
            set; 
        } 
  
        private String UserName 
        { 
            get; 
            set; 
        } 
    } 
} 

Configuration

As mentioned above, the custom ServiceCredentials needs to be configured so that the custom IAuthorizationPolicy is evaluated. Under serviceBehaviors, the serviceCredentials element allows a custom type to be defined. userNameAuthentication needs to be set to Custom, otherwise Windows authentication of the username password credentials will be used by default. Lastly, serviceAuthorization needs to set userNamePasswordValidationMode to custom. Without the validation mode being custom, the custom IPrincipal will not be assigned against Thread.CurrentPrincipal.

The other thing to note is that because username password credentials are being passed over to the wire to the service, transport security is required to protect the credentials. The security mode should be set to TransportWIthMessageCredentials with a message client credential type as UserName.

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
  <system.web> 
    <compilation debug="true" /> 
  </system.web> 
  <system.serviceModel> 
    <bindings> 
      <netTcpBinding> 
        <binding name="netTcpBindingConfig"> 
          <security mode="TransportWithMessageCredential"> 
            <message clientCredentialType="UserName" /> 
          </security> 
        </binding> 
      </netTcpBinding> 
    </bindings> 
    <services> 
      <service behaviorConfiguration="Neovolve.Framework.Communication.SecurityTest.Service1Behavior" 
        name="Neovolve.Framework.Communication.SecurityHost.Service1"> 
        <endpoint address="net.tcp://localhost:8792/PasswordSecurityTest" 
          binding="netTcpBinding" bindingConfiguration="netTcpBindingConfig" 
          contract="Neovolve.Framework.Communication.SecurityHost.IService1" /> 
      </service> 
    </services> 
    <behaviors> 
      <serviceBehaviors> 
        <behavior name="Neovolve.Framework.Communication.SecurityTest.Service1Behavior"> 
          <serviceDebug includeExceptionDetailInFaults="true" /> 
          <serviceCredentials type="Neovolve.Framework.Communication.Security.PasswordServiceCredentials, Neovolve.Framework.Communication.Security"> 
            <serviceCertificate findValue="localhost" x509FindType="FindBySubjectName" /> 
            <userNameAuthentication userNamePasswordValidationMode="Custom" /> 
          </serviceCredentials> 
          <serviceAuthorization principalPermissionMode="Custom" /> 
        </behavior> 
      </serviceBehaviors> 
    </behaviors> 
  </system.serviceModel> 
</configuration> 

Download: Neovolve.Framework.Communication.Security.zip (78.91 kb)

Note: the unit test in the solution must be run in debug rather than normal unit test running because of the use of the WcfServiceHost.exe. With the configuration defined, an x509 certificate is required on the machine with localhost as the subject name.

Tags: , , ,

Apr 7 2008

Implementing IErrorHandler

Category: .NetRory Primrose @ 11:08

As soon as I read about IErrorHandler in Juval Lowy's book, I was sold. This interface in WCF is excellent to use for error handling and shielding at service boundaries. The interface comes with two methods.

  1. ProvideFault allows you to handle the error and determine what is returned to the client. This is where exception shielding comes in. The service implementation or any of the layers in the service can throw an exception. The error handler is the opportunity to determine whether the exception thrown is something that the service understands and is happy for the client to receive or whether the exception needs to be shielded into another exception/fault. A shielded exception would typically be a generic service exception that says that the service encountered an error.
  2. HandleError allows you to process some error specific logic asynchronous to the service call. This means that you could do some tracing/instrumentation or some expensive operation without blocking the client call.

There issue that I have with IErrorHandler is that the documentation is not detailed enough to allow you to hook up an error handler so that it is actually used. My recent experience has resulted in me defining an IErrorHandler, but not correctly configuring it so that it gets invoked. This highlights a disadvantage of the IErrorHandler design (assuming a configuration based setup). The service will still work without the handler being invoked, you will just get different exceptions on the client. This has a potential security risk if exception shielding has been written to shield sensitive information from clients.

The MSDN documentation (see here) provides examples about how to to create an IErrorHandler implementation including the behavior extension, but doesn't provide the full class examples. Hence my misunderstanding that resulted in my handler not being invoked. I suggest that two classes get created. One is the error handler, the other is the error handler element for configuration.

ErrorHandler

The error handler implements IErrorHandler, but also implements IServiceBehavior. This interface allows the error handler to be hooked up by configuration.

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
 
namespace WcfServiceLibrary1
{
    public class ErrorHandler : IErrorHandler, IServiceBehavior
    {
        public void AddBindingParameters(
            ServiceDescription serviceDescription,
            ServiceHostBase serviceHostBase,
            Collection<ServiceEndpoint> endpoints,
            BindingParameterCollection bindingParameters)
        {
        }
 
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler = new ErrorHandler();
 
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
 
                if (channelDispatcher != null)
                {
                    channelDispatcher.ErrorHandlers.Add(errorHandler);
                }
            }
        }
 
        public bool HandleError(Exception error)
        {
            Trace.TraceError(error.ToString());
 
            // Returning true indicates you performed your behavior.
            return true;
        }
 
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
        }
 
        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            // Shield the unknown exception
            FaultException faultException = new FaultException(
                "Server error encountered. All details have been logged.");
            MessageFault messageFault = faultException.CreateMessageFault();
 
            fault = Message.CreateMessage(version, messageFault, faultException.Action);
        }
    }
}

ErrorHandlerElement

The error handler element defines the extension behavior such that ErrorHandler can be defined against a service behavior.

using System;
using System.ServiceModel.Configuration;
 
namespace WcfServiceLibrary1
{
    public class ErrorHandlerElement : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new ErrorHandler();
        }
 
        public override Type BehaviorType
        {
            get
            {
                return typeof(ErrorHandler);
            }
        }
    }
}

Configuration

This configuration identifies the ErrorHandlerElement as a behavior extension, which then allows errorHandler (the name of the configured extension) to be defined against the service behavior. This is how the error handler gets hooked up for the service.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <system.web>
    <compilation debug="true" />
  </system.web>
 
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MyServiceBehavior">
          <serviceDebug includeExceptionDetailInFaults="true" />
          <errorHandler />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="errorHandler"
            type="WcfServiceLibrary1.ErrorHandlerElement, WcfServiceLibrary1" />
      </behaviorExtensions>
    </extensions>
    <services>
      <service behaviorConfiguration="MyServiceBehavior"
              name="WcfServiceLibrary1.Service1">
        <endpoint binding="wsHttpBinding"
                  contract="WcfServiceLibrary1.IService1" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

This will hook up the error handler to cover any exceptions thrown by the service. Error handlers can also be debugged via the IDE.

Tags: , , ,

Apr 4 2008

Code coverage doesn't like foreach loops

Category: .NetRory Primrose @ 12:03

I have an interesting scenario that I have just come across in my code. I have a foreach loop that is not getting 100% code coverage in unit tests. Prior to this, I really liked foreach for its ease of use and readability even though there is a minor performance penalty compared to using a for loop.

Here is the situation. I have a flush method that looks like this:

public void Flush()
{
    // Loop through each listener
    foreach (TraceListener listener in Source.Listeners)
    {
        // Flush the listener
        listener.Flush();
    }
}

Code coverage for this method says that 2 blocks not covered, 12.5% not covered, 14 blocks covered, 87.5% covered. Code metrics for this method are maintainability index is 80, cyclomatic complexity is 3, class coupling is 5 and lines of code is 2.

The IL for this method is:

.method public hidebysig instance void Flush() cil managed

{

    .maxstack 2

    .locals init (

        [0] class [System]System.Diagnostics.TraceListener listener,

        [1] class [mscorlib]System.Collections.IEnumerator CS$5$0000,

        [2] bool CS$4$0001,

        [3] class [mscorlib]System.IDisposable CS$0$0002)

    L_0000: nop

    L_0001: nop

    L_0002: ldarg.0

    L_0003: call instance class [System]System.Diagnostics.TraceSource MyNamespace.MyClass::get_Source()

    L_0008: callvirt instance class [System]System.Diagnostics.TraceListenerCollection [System]System.Diagnostics.TraceSource::get_Listeners()

    L_000d: callvirt instance class [mscorlib]System.Collections.IEnumerator [System]System.Diagnostics.TraceListenerCollection::GetEnumerator()

    L_0012: stloc.1

    L_0013: br.s L_002a

    L_0015: ldloc.1

    L_0016: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()

    L_001b: castclass [System]System.Diagnostics.TraceListener

    L_0020: stloc.0

    L_0021: nop

    L_0022: ldloc.0

    L_0023: callvirt instance void [System]System.Diagnostics.TraceListener::Flush()

    L_0028: nop

    L_0029: nop

    L_002a: ldloc.1

    L_002b: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()

    L_0030: stloc.2

    L_0031: ldloc.2

    L_0032: brtrue.s L_0015

    L_0034: leave.s L_004d

    L_0036: ldloc.1

    L_0037: isinst [mscorlib]System.IDisposable

    L_003c: stloc.3

    L_003d: ldloc.3

    L_003e: ldnull

    L_003f: ceq

    L_0041: stloc.2

    L_0042: ldloc.2

    L_0043: brtrue.s L_004c

    L_0045: ldloc.3

    L_0046: callvirt instance void [mscorlib]System.IDisposable::Dispose()

    L_004b: nop

    L_004c: endfinally

    L_004d: nop

    L_004e: ret

    .try L_0013 to L_0036 finally handler L_0036 to L_004d

}

The UI for code coverage indicates that each line of code is hit. My guess is that there is something to do with the IEnumerator that is called when foreach is compiled.

I changed the code to this:

public void Flush()
{
    // Loop through each listener
    for (Int32 index = 0; index < Source.Listeners.Count; index++ )
    {
        TraceListener listener = Source.Listeners[index];

        // Flush the listener
        listener.Flush();
    }
}

Code coverage now says that 0 blocks not covered, 0% not covered, 11 blocks covered, 100% covered. Code metrics for this method now say that maintainability index is 75, cyclomatic complexity is2, class coupling is 3 and lines of code is 3.

The IL for this method is now:

.method public hidebysig instance void Flush() cil managed

{

    .maxstack 2

    .locals init (

        [0] int32 index,

        [1] class [System]System.Diagnostics.TraceListener listener,

        [2] bool CS$4$0000)

    L_0000: nop

    L_0001: ldc.i4.0

    L_0002: stloc.0

    L_0003: br.s L_0024

    L_0005: nop

    L_0006: ldarg.0

    L_0007: call instance class [System]System.Diagnostics.TraceSource MyNamespace.MyClass::get_Source()

    L_000c: callvirt instance class [System]System.Diagnostics.TraceListenerCollection [System]System.Diagnostics.TraceSource::get_Listeners()

    L_0011: ldloc.0

    L_0012: callvirt instance class [System]System.Diagnostics.TraceListener [System]System.Diagnostics.TraceListenerCollection::get_Item(int32)

    L_0017: stloc.1

    L_0018: ldloc.1

    L_0019: callvirt instance void [System]System.Diagnostics.TraceListener::Flush()

    L_001e: nop

    L_001f: nop

    L_0020: ldloc.0

    L_0021: ldc.i4.1

    L_0022: add

    L_0023: stloc.0

    L_0024: ldloc.0

    L_0025: ldarg.0

    L_0026: call instance class [System]System.Diagnostics.TraceSource MyNamespace.MyClass::get_Source()

    L_002b: callvirt instance class [System]System.Diagnostics.TraceListenerCollection [System]System.Diagnostics.TraceSource::get_Listeners()

    L_0030: callvirt instance int32 [System]System.Diagnostics.TraceListenerCollection::get_Count()

    L_0035: clt

    L_0037: stloc.2

    L_0038: ldloc.2

    L_0039: brtrue.s L_0005

    L_003b: ret

}

There are several posts around that talk about the performance difference of foreach vs for, but no-one seems to have actually posted metrics to base their stance on. One post that was in interesting read was How to Write High-Performance C# Code by Jeff Varszegi. As far as performance goes, the collection in this situation is always going to be very small so it is perhaps not that much of an issue.

I think that for loops would be faster after looking at the IL and understanding what foreach does under the covers. I don't think however that the performance difference is significant in itself. However, if foreach causes issues with code coverage, perhaps both these issues combined is enough of a reason to change coding practices.

Updated: Reformatted the IL code to avoid PRE tags that don't wrap.

Tags: ,

Apr 1 2008

Outlook has a dialog open, but it doesn't

Category: IT RelatedRory Primrose @ 03:40

I was playing with a new profile on my laptop last night as I was wanting to work with Outlook data syncing to my phone without changing the data on my normal profile. After I logged in, I found that Outlook wasn't responsive. I could use the mouse to do actions, but not the keyboard. I also couldn't close it. When I tried to add a SharePoint list Outlook would give me a message saying "A dialog is open. Close it and try again." The great thing was that there was no dialog open.

This frustrated me insanely for 30 minutes. I then remembered something from working at a small IT company about eight years ago. Back then, we had an issue with Word automation from VB6 on a server where the account running the service hadn't yet logged into the machine. Word was locking up on the dialog that appears asking for your name and initials. We had to log into the machine running the service with the service account, open Word, click Ok, log off and then the service would run the Word automation without a problem.

It then occurred to me that Outlook uses Word by default as its email editor. So when Outlook runs, it also spins up Word. As this is a new profile and I am only interested in Outlook, I hadn't run Word yet.

Sure enough, after opening Word, clicking Ok on the user details dialog and closing Word, Outlook worked like a charm.

I am so surprised that Microsoft haven't covered this scenario. It's crazy to render an application useless because the user hasn't dealt with a dialog that isn't displayed.

Tags: