Apr 12 2012

Updating claims within an RP session with WIF

Category: .NetRory Primrose @ 17:17

I have a scenario where a web application is using WIF to manage federated security. The system will get a SAML token from an STS for the authenticated user. The token is only going to contain the NameIdentifier claim (a typical Windows Live token for example). This means that the application itself needs to manage the account information related to an authenticated user.

The application will store the first name, last name and email address of the user. These values will be populated into the IClaimsPrincipal for an existing account using a custom ClaimsAuthenticationManager implementation.

public override IClaimsPrincipal Authenticate(String resourceName, IClaimsPrincipal incomingPrincipal)
{
    IClaimsPrincipal principal = base.Authenticate(resourceName, incomingPrincipal);

    if (principal == null)
    {
        return null;
    }

    if (principal.Identity.IsAuthenticated == false)
    {
        return principal;
    }

    IClaimsIdentity identity = principal.Identity as IClaimsIdentity;

    if (identity == null)
    {
        return principal;
    }

    if (identity.Claims == null)
    {
        return principal;
    }

    IUnityContainer container = DomainContainer.Current;
    IAccountManager accountManager = container.Resolve<IAccountManager>();

    try
    {
        String userName = identity.GetClaimValue<String>(ClaimTypes.NameIdentifier);
        Account account = accountManager.Accounts.FirstOrDefault(x => x.UserName == userName);

        if (account != null)
        {
            identity.UpdateFromAccount(account);
        }
        else
        {
            // For anti forgery in MVC posts, there must be a name and we want it to be unique
            identity.UpsertClaim(ClaimTypes.Name, Guid.NewGuid().ToString());
        }
    }
    finally
    {
        container.Teardown(accountManager);
    }

    return principal;
}

This code will ensure that the principal executing against the web requests will contain the claims that match the users account. There are two scenarios that this does not cater for.

  1. New users (there isn’t an account yet)
  2. Existing users that update their account

The user will use an online form to submit changes to their account in either of these scenarios. The issue here is that the FedAuth security token stored in the request/response cookie is now out of date as it contains the serialized claims from when the authentication session was created, but these claims have now changed. The cookie is not updated to reflect any changes to the principal.

On a side note, I did find that simply updating the claims on the principal did cause the updated claims to be persisted for that web instance. I don’t understand where this logic sits as the FedAuth cookie is supposed to contain the serialized claims when IsSessionMode = false. Regardless, this doesn’t cover the case of NLB servers or when the application is hosted across multiple Azure web roles.

The stale token cookie meant that the application used the old claims when the web request was processed by a different web role instance (in the Azure dev fabric). The only two options to fix the stale claims when an account is updated appear to be:

  1. Expire the authentication session
  2. Force a new FedAuth cookie in the response

I don’t like option #1 because the application is essentially logging the user out of the system, causing a redirect to a protected resource to bounce them back out to the STS so that they will then be provided a new principal that has the current set of claims when they log back in (as populated by the ClaimsAuthenticationManager). My main issue with this is that the UX is inconsistent and confusing. The scenario is worse when the user has not told the STS to persist their authentication information therefore forcing them to manually log in to the application again.

This leaves me with having to update the FedAuth cookie mid-session from within the RP. The main reason that this is not ideal is because it is creating a coupling between the RP and the security implementation. It really is the lesser of two evils however and I think that a better UX wins over a purist architecture.

The next issue is that there is no clean API to use to write a new FedAuth cookie to the HttpResponse using the available FederatedAuthentication information. I came up with this extension method with the help of Reflector.

using System;
using System.Diagnostics.Contracts;
using Microsoft.IdentityModel.Claims;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Web;

/// <summary>
/// The <see cref="ClaimsPrincipalExtensions"/> class is used to provide extension methods for the <see cref="IClaimsPrincipal"/> interface.
/// </summary>
public static class ClaimsPrincipalExtensions
{
    /// <summary>
    /// Updates the session security token.
    /// </summary>
    /// <param name="principal">
    /// The principal. 
    /// </param>
    /// <param name="fam">
    /// The federation authentication module. 
    /// </param>
    /// <param name="sam">
    /// The session authentication module. 
    /// </param>
    public static void UpdateSessionSecurityToken(
        this IClaimsPrincipal principal, WSFederationAuthenticationModule fam, SessionAuthenticationModule sam)
    {
        Contract.Requires<ArgumentNullException>(principal != null);
        Contract.Requires<ArgumentNullException>(fam != null);
        Contract.Requires<ArgumentNullException>(sam != null);

        String cookieContext = GetSessionTokenContext(fam);
        TimeSpan configuredSessionTokenLifeTime = ConfiguredSessionTokenLifeTime(fam);
        DateTime validFrom = DateTime.UtcNow;
        DateTime validTo = validFrom.Add(configuredSessionTokenLifeTime);
        SessionSecurityToken securityToken = sam.CreateSessionSecurityToken(
            principal, cookieContext, validFrom, validTo, fam.PersistentCookiesOnPassiveRedirects);

        sam.WriteSessionTokenToCookie(securityToken);
    }

    /// <summary>
    /// Configureds the session token life time.
    /// </summary>
    /// <param name="fam">
    /// The fam. 
    /// </param>
    /// <returns>
    /// A <see cref="TimeSpan"/> instance. 
    /// </returns>
    private static TimeSpan ConfiguredSessionTokenLifeTime(WSFederationAuthenticationModule fam)
    {
        TimeSpan defaultTokenLifetime = SessionSecurityTokenHandler.DefaultTokenLifetime;

        if (fam.ServiceConfiguration == null)
        {
            return defaultTokenLifetime;
        }

        if (fam.ServiceConfiguration.SecurityTokenHandlers == null)
        {
            return defaultTokenLifetime;
        }

        SessionSecurityTokenHandler handler =
            fam.ServiceConfiguration.SecurityTokenHandlers[typeof(SessionSecurityToken)] as
            SessionSecurityTokenHandler;

        if (handler != null)
        {
            defaultTokenLifetime = handler.TokenLifetime;
        }

        return defaultTokenLifetime;
    }

    /// <summary>
    /// Gets the session token context.
    /// </summary>
    /// <param name="fam">
    /// The fam. 
    /// </param>
    /// <returns>
    /// A <see cref="String"/> instance. 
    /// </returns>
    private static String GetSessionTokenContext(WSFederationAuthenticationModule fam)
    {
        String sessionTokenContextPrefix = "(" + fam.GetType().Name + ")";
        String signOutUrl = WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(
            fam.Issuer, fam.SignOutReply, fam.SignOutQueryString);

        return sessionTokenContextPrefix + signOutUrl;
    }
}

This extension method will write a new FedAuth cookie to the response stream using the same implementation that WIF uses to create the cookie in the first place. This now allows the principal to provide the current set of claims to a web farm regardless of which web instance processes the request.

Tags: ,

Mar 20 2012

Should code contracts be tested?

Category: .Net | Software DesignRory Primrose @ 16:26

I have been writing unit tests for my classes that use code contracts (Contract.Requires<T>) just like I did with the old style guard clauses basically since code contracts were released. My reasoning for testing them has always been that the unit test code should not make any assumptions about the implementation of the SUT and ideally should have no understanding about how it is implemented. Instead it should just test the behaviour.

If a method that takes a reference type as a parameter and a value must be supplied, then the expectation is that the method will throw an exception if null is provided. I have always believed that this behaviour should be tested regardless of whether this is implemented as a traditional guard clause, a Contract.Requires on the method implementation or a Contract.Requires on the interface or base class.

I saw someone mention recently that they didn’t think that code contracts should be tested. This has lead me to question the value of the practice. A crisis of testing faith perhaps.

My two thoughts about this so far are:

  • Code contracts are a totally different beast than traditional guard clauses
    • Compiler support
    • Design time analysis
    • Compile time analysis
    • Intellisense support
    • Runtime enforcement (assuming that the contract has been written correctly and is therefore not a source of a bug itself)
  • Testing all these contracts is starting to look more like infrastructure code

Regarding the last point, I am referring to infrastructure code in that it is important for running the application but is repetitive, is not the meaty part of the application and doesn’t really have much value in and of itself.

Thoughts?

Tags:

Mar 19 2012

Workaround for Azure SDK Invalid access to memory location error

Category: .NetRory Primrose @ 08:38

There is an issue with the Azure 1.6 SDK that I often hit when I run a cloud project. I get around 3-5 debug sessions with my cloud project before Visual Studio starts throwing an Invalid access to memory location error.

image

This issue seems like it is hitting many other people as well. This is a significant pain point for working with Azure projects and there is currently no suitable workaround or fix that I can find. Sometimes shutting down the emulator and trying again works. Most often however, the IDE needs to be recycled. Having to do this each fifth F5 is a productivity killer.

It seems that the most reliable information out there is that the issue is caused by addins/extensions in Visual Studio. This is consistent with my experience of running automated integration tests as defined in my previous posts. I quickly encounter this problem running F5 from the IDE, yet launching Azure and IISExpress for integration tests does not suffer from this error. This leads me to a reliable workaround for this issue based on my integration test implementation using the External Tools support in Visual Studio to manually launch the emulator.

Configuring External Tools

We need to add three External Tools entries. These are for launching the compute emulator in the Debug configuration, another one for the Release configuration and finally the storage emulator.

image

Click Tools –> External Tools

image

First up is to configure launching the emulator for the debug configuration.

  1. Click Add
  2. Enter the title for running the compute under debug
  3. Enter the command as C:\Program Files\Windows Azure Emulator\emulator\csrun.exe
  4. Enter the arguments as /run:"$(SolutionDir)[YourCloudProjectName]\csx\Debug";"$(SolutionDir)[YourCloudProjectName]\bin\Debug\ServiceConfiguration.cscfg" /launchBrowser
  5. Setting the Initial directory is optional, but $(SolutionDir) is as good as any setting
  6. Check Use Output Window and click Apply

Unfortunately the ExternalTools window does not process the $(ConfigurationName) macro like the build events function does. This is why we need an entry for each build configuration (typically Debug and Release).

Create a new External Tools entry for the compute emulator to load the Release build. It will have the same configuration except that references to Debug are replaced with Release.

Create a new External Tools entry for the storage emulator. The arguments for this field are /devstore:start

image

The requirement for a separate entry for the storage emulator is unfortunate. It is required because the csrun.exe does not support command line parameters for both the compute emulator and the storage emulator in the one execution.

Attaching the debugger

You can configure csrun to attach to the debugger using the /launchDebugger switch. I did not want to use this switch because it will open new instances of the debugger (typically devenv.exe) for each role instance (web and worker). This is unhelpful because I want to use the existing Visual Studio instance. I also found that the browser could not hit the compute instance anyway when the debugger was attached in this way (this could be just a timing issue though).

The easiest way around this is to just attach to the process manually.

Click Debug –> Attach to Process…

image

Find w3wp.exe and click Attach.

image

NOTES:

This workaround only spins up the compute emulator or the storage emulator. It does not do any of the following:

  • Build/Rebuild your projects
  • Request shutdowns of the emulators
  • Attach a debugger

Tags:

Mar 18 2012

Integration testing with Azure development fabric and IISExpress

Category: .NetRory Primrose @ 17:39

This post puts together the code posted in the previous two posts (here and here).

The following code is what I am using the spin up the Azure storage emulator, Azure compute emulator and IISExpress so that I can run my system through its integration tests.

namespace MySystem.Server.Web.IntegrationTests
{
    using System;
    using System.IO;
    using System.Threading.Tasks;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Neovolve.Toolkit.Azure;
    using Neovolve.Toolkit.TestSupport;
    using WatiN.Core;

    /// <summary>
    /// The <see cref="Initialization"/> class is used to run assembly initialization work for the test assembly.
    /// </summary>
    [TestClass]
    public static class Initialization
    {
        /// <summary>
        ///   Stores the IIS Express reference entity.
        /// </summary>
        private static IisExpress _iisExpress;

        #region Setup/Teardown

        /// <summary>
        /// Cleans up after running the unit tests in an assembly.
        /// </summary>
        [AssemblyCleanup]
        public static void AssemblyCleanup()
        {
            Task iisExpressTask = Task.Factory.StartNew(
                () =>
                    {
                        _iisExpress.Dispose();
                        _iisExpress = null;
                    });

            Task storageTask = Task.Factory.StartNew(AzureEmulator.StopStorage);
            Task computeTask = Task.Factory.StartNew(AzureEmulator.StopCompute);

            Task.WaitAll(iisExpressTask, storageTask, computeTask);
        }

        /// <summary>
        /// Initializes the assembly for running unit tests.
        /// </summary>
        /// <param name="context">
        /// The context. 
        /// </param>
        [AssemblyInitialize]
        public static void AssemblyInitialize(TestContext context)
        {
            // Set WatiN to not move the mouse
            Settings.Instance.AutoMoveMousePointerToTopLeft = false;

            Task iisExpressTask = Task.Factory.StartNew(
                () =>
                    {
                        String solutionDirectory = context.FindSolutionDirectory();
                        String stsProjectDirectory = Path.Combine(solutionDirectory, "[TheNameOfMySTSProject]");

                        _iisExpress = new IisExpress();

                        _iisExpress.Start(stsProjectDirectory, 35026);
                    });

            Task storageTask = Task.Factory.StartNew(AzureEmulator.StartStorage);
            Task computeTask = Task.Factory.StartNew(AzureEmulator.StartCompute);

            Task.WaitAll(iisExpressTask, storageTask, computeTask);
        }

        #endregion
    }
}

Enjoy

Tags:

Mar 18 2012

Spinning up IISExpress for integration testing

Category: .NetRory Primrose @ 17:34

The system I am currently working uses the development fabric in the Azure SDK for working with Azure web roles and worker roles. I am also using a local WIF STS site to simulate Azure ACS. This allows me to integrate claims based security into the system without having to actually start using an Azure subscription.

The local STS is running on IISExpress. Like the previous post about running the Azure emulator for integration testing, the STS also needs to be spun up to run the system. The following class provides the wrapper logic for spinning up IISExpress.

namespace Neovolve.Toolkit.TestSupport
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Threading;

    /// <summary>
    /// The <see cref="IisExpress"/> class is used to manage an IIS Express instance for running integration tests.
    /// </summary>
    public class IisExpress : IDisposable
    {
        /// <summary>
        ///   Stores whether this instance has been disposed.
        /// </summary>
        private Boolean _isDisposed;

        /// <summary>
        ///   Stores the IIS Express process.
        /// </summary>
        private Process _process;

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Starts IIS Express using the specified directory path and port.
        /// </summary>
        /// <param name="directoryPath">
        /// The directory path. 
        /// </param>
        /// <param name="port">
        /// The port. 
        /// </param>
        public void Start(String directoryPath, Int32 port)
        {
            String iisExpressPath = DetermineIisExpressPath();
            String arguments = String.Format(
                CultureInfo.InvariantCulture, "/path:\"{0}\" /port:{1}", directoryPath, port);

            ProcessStartInfo info = new ProcessStartInfo(iisExpressPath)
                                        {
                                            WindowStyle = ProcessWindowStyle.Hidden,
                                            ErrorDialog = true,
                                            LoadUserProfile = true,
                                            CreateNoWindow = false,
                                            UseShellExecute = false,
                                            Arguments = arguments
                                        };

            Thread startThread = new Thread(() => StartIisExpress(info))
                                     {
                                         IsBackground = true
                                     };

            startThread.Start();
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing">
        /// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources. 
        /// </param>
        protected virtual void Dispose(Boolean disposing)
        {
            if (_isDisposed)
            {
                return;
            }

            if (disposing)
            {
                // Free managed resources
                if (_process.HasExited == false)
                {
                    _process.CloseMainWindow();
                }

                _process.Dispose();
            }

            // Free native resources if there are any
            _isDisposed = true;
        }

        /// <summary>
        /// Determines the IIS express path.
        /// </summary>
        /// <returns>
        /// A <see cref="String"/> instance. 
        /// </returns>
        private static String DetermineIisExpressPath()
        {
            String iisExpressPath;

            if (Environment.Is64BitOperatingSystem)
            {
                iisExpressPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
            }
            else
            {
                iisExpressPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
            }

            iisExpressPath = Path.Combine(iisExpressPath, @"IIS Express\iisexpress.exe");

            return iisExpressPath;
        }

        /// <summary>
        /// Starts the IIS express.
        /// </summary>
        /// <param name="info">
        /// The info. 
        /// </param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Required here to ensure that the instance is disposed.")]
        private void StartIisExpress(ProcessStartInfo info)
        {
            try
            {
                _process = Process.Start(info);

                _process.WaitForExit();
            }
            catch (Exception)
            {
                Dispose();
            }
        }
    }
}

Unfortunately this implementation is not able to automatically resolve project information that the Azure implementation can. The code would have to make too many inappropriate assumptions in order to make this work. This implementation therefore requires the information about the site it will host to be provided to it.

Tags: ,

Mar 18 2012

Boosting integration testing with Azure development fabric

Category: .NetRory Primrose @ 17:24

I posted previously about manually spinning up Azure storage emulator in the development fabric so that it can be used with integration tests. Ever since then I have been using a vastly updated version of the code I previously published.

This updated one might be helpful for others to leverage as well. This version allows for starting and stopping both the storage emulator and the compute emulator. It makes its best attempt at automatically finding the Azure project service directory and the service configuration for the current build configuration. If this does not work for your scenario, then you can also manually provide this information.

namespace Neovolve.Toolkit.Azure
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.IO;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// The <see cref="AzureEmulator"/> class is used to provide functionality for starting and stopping Azure storage and compute emulators.
    /// </summary>
    public static class AzureEmulator
    {
        /// <summary>
        ///   Stores the CS run path.
        /// </summary>
        public const String AzureRunPath = @"C:\Program Files\Windows Azure Emulator\emulator\csrun.exe";

        /// <summary>
        ///   Defines the default build type to search for.
        /// </summary>
#if DEBUG
        private const String DefaultBuildType = "Debug";
#else
        private const String DefaultBuildType = "Release";
#endif

        /// <summary>
        /// Starts the compute.
        /// </summary>
        public static void StartCompute()
        {
            StartCompute(null, null);
        }

        /// <summary>
        /// Starts the compute.
        /// </summary>
        /// <param name="serviceDirectory">
        /// The service directory. 
        /// </param>
        /// <param name="configurationPath">
        /// The configuration path. 
        /// </param>
        public static void StartCompute(String serviceDirectory, String configurationPath)
        {
            if (String.IsNullOrWhiteSpace(serviceDirectory))
            {
                // Attempt to resolve the service directory using default searching parameters
                serviceDirectory = FindServiceDirectory(null, null);
            }

            if (String.IsNullOrWhiteSpace(configurationPath))
            {
                configurationPath = FindServiceConfiguration(null, null);
            }

            if (Directory.Exists(serviceDirectory) == false)
            {
                throw new InvalidOperationException("Azure service directory does not exist.");
            }

            if (File.Exists(configurationPath) == false)
            {
                throw new InvalidOperationException("Azure service configuration does not exist.");
            }

            String arguments = "/run:\"" + serviceDirectory + "\";\"" + configurationPath + "\"";

            Contract.Assume(String.IsNullOrWhiteSpace(arguments) == false);

            ExecuteAzureEmulator(arguments);
        }

        /// <summary>
        /// Starts the storage.
        /// </summary>
        public static void StartStorage()
        {
            const String Arguments = "/devstore:start";

            ExecuteAzureEmulator(Arguments);
        }

        /// <summary>
        /// Stops the compute.
        /// </summary>
        public static void StopCompute()
        {
            const String Arguments = "/devfabric:shutdown";

            ExecuteAzureEmulator(Arguments);
        }

        /// <summary>
        /// Stops the storage.
        /// </summary>
        public static void StopStorage()
        {
            const String Arguments = "/devstore:shutdown";

            ExecuteAzureEmulator(Arguments);
        }

        /// <summary>
        /// Finds the service configuration.
        /// </summary>
        /// <param name="context">
        /// The context. 
        /// </param>
        /// <param name="buildType">
        /// Type of the build. 
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance. 
        /// </returns>
        private static String FindServiceConfiguration(this TestContext context, String buildType)
        {
            Contract.Ensures(Contract.Result<String>() != null);

            return FindServiceConfiguration(context, buildType, String.Empty);
        }

        /// <summary>
        /// Finds the service configuration.
        /// </summary>
        /// <param name="context">
        /// The context. 
        /// </param>
        /// <param name="buildType">
        /// Type of the build. 
        /// </param>
        /// <param name="projectName">
        /// Name of the project. 
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance. 
        /// </returns>
        private static String FindServiceConfiguration(this TestContext context, String buildType, String projectName)
        {
            Contract.Ensures(Contract.Result<String>() != null);

            if (String.IsNullOrWhiteSpace(buildType))
            {
                buildType = DefaultBuildType;
            }

            String solutionDirectory = context.FindSolutionDirectory();
            String searchPattern = projectName + @"\bin\" + buildType + @"\ServiceConfiguration.cscfg";

            IEnumerable<String> enumerateFiles = Directory.EnumerateFiles(
                solutionDirectory, "*", SearchOption.AllDirectories);

            Contract.Assume(enumerateFiles != null);

            IEnumerable<String> matchingFiles = from x in enumerateFiles
                                                where x.EndsWith(searchPattern, StringComparison.OrdinalIgnoreCase)
                                                select x;

            if (matchingFiles == null)
            {
                throw new InvalidOperationException("Failed to find any service configuration files.");
            }

            List<String> serviceConfigurations = matchingFiles.ToList();

            if (serviceConfigurations.Count == 0)
            {
                throw new InvalidOperationException("No service configuration was found.");
            }

            if (serviceConfigurations.Count > 1)
            {
                throw new InvalidOperationException("Multiple service configurations were found.");
            }

            Contract.Assume(String.IsNullOrWhiteSpace(serviceConfigurations[0]) == false);

            return serviceConfigurations[0];
        }

        /// <summary>
        /// Finds the service directory.
        /// </summary>
        /// <param name="context">
        /// The context. 
        /// </param>
        /// <param name="buildType">
        /// Type of the build. 
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance. 
        /// </returns>
        private static String FindServiceDirectory(this TestContext context, String buildType)
        {
            Contract.Ensures(Contract.Result<String>() != null);

            return FindServiceDirectory(context, buildType, String.Empty);
        }

        /// <summary>
        /// Finds the service directory.
        /// </summary>
        /// <param name="context">
        /// The context. 
        /// </param>
        /// <param name="buildType">
        /// Type of the build. 
        /// </param>
        /// <param name="projectName">
        /// Name of the project. 
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance. 
        /// </returns>
        private static String FindServiceDirectory(this TestContext context, String buildType, String projectName)
        {
            Contract.Ensures(Contract.Result<String>() != null);

            if (String.IsNullOrWhiteSpace(buildType))
            {
                buildType = DefaultBuildType;
            }

            String solutionDirectory = context.FindSolutionDirectory();
            String searchPattern = projectName + @"\csx\" + buildType;

            IEnumerable<String> enumerateDirectories = Directory.EnumerateDirectories(
                solutionDirectory, "*", SearchOption.AllDirectories);

            Contract.Assume(enumerateDirectories != null);

            IEnumerable<String> matchingDirectories = from x in enumerateDirectories
                                                      where
                                                          x.EndsWith(searchPattern, StringComparison.OrdinalIgnoreCase)
                                                      select x;

            if (matchingDirectories == null)
            {
                throw new InvalidOperationException("No failed to identify any service directories.");
            }

            List<String> serviceDirectories = matchingDirectories.ToList();

            if (serviceDirectories.Count == 0)
            {
                throw new InvalidOperationException("No service directory was found.");
            }

            if (serviceDirectories.Count > 1)
            {
                throw new InvalidOperationException("Multiple service directories were found.");
            }

            Contract.Assume(String.IsNullOrWhiteSpace(serviceDirectories[0]) == false);

            return serviceDirectories[0];
        }

        /// <summary>
        /// Executes the azure emulator.
        /// </summary>
        /// <param name="arguments">
        /// The arguments. 
        /// </param>
        private static void ExecuteAzureEmulator(String arguments)
        {
            Contract.Requires<ArgumentNullException>(String.IsNullOrWhiteSpace(arguments) == false);

            ProcessStartInfo processStartInfo = new ProcessStartInfo
                                                {
                                                    FileName = AzureRunPath, 
                                                    Arguments = arguments, 
                                                    RedirectStandardOutput = true, 
                                                    UseShellExecute = false, 
                                                    CreateNoWindow = true, 
                                                    WindowStyle = ProcessWindowStyle.Hidden
                                                };

            using (Process process = Process.Start(processStartInfo))
            {
                process.WaitForExit();

                using (StreamReader reader = process.StandardOutput)
                {
                    Trace.WriteLine(reader.ReadToEnd());
                }
            }
        }
    }
}

This makes use of an extension method on the TestContext class.

namespace Neovolve.Toolkit.Azure
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// The <see cref="TestContextExtensions"/> class is used to provide extension methods to the <see cref="TestContext"/> class.
    /// </summary>
    public static class TestContextExtensions
    {
        /// <summary>
        /// Finds the solution directory.
        /// </summary>
        /// <param name="context">
        /// The context. 
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance. 
        /// </returns>
        public static String FindSolutionDirectory(this TestContext context)
        {
            Contract.Ensures(String.IsNullOrWhiteSpace(Contract.Result<String>()) == false);

            String startPath;

            if (context != null)
            {
                startPath = context.TestDir;
            }
            else
            {
                startPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            }

            if (String.IsNullOrWhiteSpace(startPath))
            {
                throw new InvalidOperationException(
                    "No reference point was determined in order to search for the solution directory.");
            }

            DirectoryInfo directory = new DirectoryInfo(startPath);

            while (directory.Exists)
            {
                IEnumerable<FileInfo> solutionFiles = directory.EnumerateFiles("*.sln", SearchOption.TopDirectoryOnly);

                Contract.Assume(solutionFiles != null);

                if (solutionFiles.Any())
                {
                    Contract.Assume(String.IsNullOrWhiteSpace(directory.FullName) == false);

                    // We have found the first parent directory that a the solution file
                    return directory.FullName;
                }

                if (directory.Parent == null)
                {
                    throw new InvalidOperationException("Failed to identify the solution directory.");
                }

                directory = directory.Parent;
            }

            throw new InvalidOperationException("Failed to identify the solution directory.");
        }
    }
}

Next up, how to provide a similar implementation for IISExpress.

Tags:

Mar 2 2012

SSAS fails to process TFS cube in Tfs_Analysis

Category: Rory Primrose @ 05:43

One of the TFS instances that I am responsible for started failing to process its Analysis Services cube a few days ago. The nature of the environment is that I can only do a reboot after hours. I also wanted to try to find out what was wrong before resorting to a reboot so that we could try to fix the problem rather than just doing a band-aid.

The topology of the TFS deployment is the following:

  • TFS App Tier – also hosts SSRS
  • TFS Data Tier – SQL Server, SSIS and SSAS
  • TFS SharePoint Tier
  • TFS Controllers
  • TFS Build Environment
  • CI Host Platform

Each of the services use domain accounts that are unique to each service.

The primary problem was that the 2 hourly job to reprocess the Tfs_Analysis SSAS cube from the Tfs_Warehouse database failed because SSAS could not connect to the SQL Server data engine. The following is most of the exception detail of the failure.

[Full Analysis Database Sync]: 
AnalysisDatabaseProcessingType=Full, needCubeSchemaUpdate=True. 
Microsoft.TeamFoundation.Server.WarehouseException: TF221122: An error occurred running job Full Analysis Database Sync for team project collection or Team Foundation server TEAM FOUNDATION. 
Microsoft.TeamFoundation.Server.WarehouseException: Failed to Process Analysis Database 'Tfs_Analysis'. 
Microsoft.TeamFoundation.Server.WarehouseException: Internal error: The operation terminated unsuccessfully.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Day Of Year' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
Server: The operation has been cancelled.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Day Of Month' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Year' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Month Of Year' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Week Of Year' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Month' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Week' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.
OLE DB error: OLE DB or ODBC error: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; Client unable to establish connection; 08001; Encryption not supported on the client.; 08001.
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'.
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Today', Name of 'Today' was being processed.
Errors in the OLAP storage engine: An error occurred while the 'Date' attribute of the 'Today' dimension from the 'Tfs_Analysis' database was being processed.

These two services are running on the same local VM and are each responding to other remote requests. TFS still works for WIT and source control, SSAS still responds to report requests. Weirdly, SQL Profiler was showing activity on SQL Server when attempting to process the cube. Perhaps only part of the processing could get a connection to SQL Server whereas a later part of the process could not.

Some search results were indicating that it might be a problem with a loopback address. The datasource in Tfs_Analysis was using the CNAME for the data tier to point to the local box so this might be a contributing factor. This did seem unlikely though given that this TFS instance has worked fine for months. We changed the data source to several combinations (localhost, (local) and .) but the cube processing continued to fail. To rule out a DNS problem, we also tried the data source with the local IP address of 127.0.0.1 with no joy.

The configuration of the data source in SSAS uses impersonation to connect to SQL Server rather than the service account of SSAS. Just for kicks, we added the SSAS service account as a local admin on the VM. We then got a different error. Processing the cube then failed because of a login timeout rather than a connectivity problem.

[Full Analysis Database Sync]: 
---> AnalysisDatabaseProcessingType=Full, needCubeSchemaUpdate=False. ---> Microsoft.TeamFoundation.Server.WarehouseException: TF221122: An error occurred running job Full Analysis Database Sync for team project collection or Team Foundation server TEAM FOUNDATION. 
---> Microsoft.TeamFoundation.Server.WarehouseException: Failed to Process Analysis Database 'Tfs_Analysis'. 
---> Microsoft.TeamFoundation.Server.WarehouseException: Internal error: The operation terminated unsuccessfully. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim Build Platform', Name of 'Build Platform' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'Build Platform' attribute of the 'Build Platform' dimension from the 'Tfs_Analysis' database was being processed. Server: The operation has been cancelled. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim WorkItem Link Type', Name of 'Work Item Link Type' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'TeamProjectCollectionSK' attribute of the 'Work Item Link Type' dimension from the 'Tfs_Analysis' database was being processed. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim WorkItem Link Type', Name of 'Work Item Link Type' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'Link ID' attribute of the 'Work Item Link Type' dimension from the 'Tfs_Analysis' database was being processed. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim WorkItem Link Type', Name of 'Work Item Link Type' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'WorkItemLinkTypeBK' attribute of the 'Work Item Link Type' dimension from the 'Tfs_Analysis' database was being processed. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim WorkItem Link Type', Name of 'Work Item Link Type' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'Reference Name' attribute of the 'Work Item Link Type' dimension from the 'Tfs_Analysis' database was being processed. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim WorkItem Link Type', Name of 'Work Item Link Type' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'Link Name' attribute of the 'Work Item Link Type' dimension from the 'Tfs_Analysis' database was being processed. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 08001. 
Errors in the high-level relational engine. A connection could not be made to the data source with the DataSourceID of 'Tfs_AnalysisDataSource', Name of 'Tfs_AnalysisDataSource'. 
Errors in the OLAP storage engine: An error occurred while the dimension, with the ID of 'Dim WorkItem Link Type', Name of 'Work Item Link Type' was being processed. 
Errors in the OLAP storage engine: An error occurred while the 'Rules' attribute of the 'Work Item Link Type' dimension from the 'Tfs_Analysis' database was being processed. 
OLE DB error: OLE DB or ODBC error: Login timeout expired; HYT00; A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.; 08001; SQL Server Network Interfaces: Error getting enabled protocols list from registry [xFFFFFFFF]. ; 0800

We then removed impersonation and added the SSAS service account as a warehouse reader in Tfs_Warehouse. Processing the cube could now get an authenticated connection to SQL Server, but it then failed because of schema issues. The cube was trying to reference columns in a Tfs_Warehouse view that did not exist.

There were no answers after several days of trying to resolve this issue without a reboot. The problems being experienced also did not make any sense.

A reboot fixed all these problems last night. SSAS can now connect to SQL Server using its original configuration and can successfully process the cube. It’s frustrating that several days were lost and there are no answers other than a band-aid. On the positive side however, the problem has been resolved.

Tags:

Feb 16 2012

Quick Poll–UX of UI navigation for list with a single item

Category: Software DesignRory Primrose @ 17:31

I’m after some feedback from the community regarding the UX of UI navigation when dealing with a list of items where there is only one item in the list.

I have a scenario in a UI where there is a list of subscriptions for a user account. Most of the time (~>90%) there will only be one subscription. There are two options for handling this.

  1. Always display the list when the user navigates to the list UI and force the user to manually select the only item available
  2. Automatically redirect the user to the item display screen if there is only one item

Option #1 is consistent but includes an unnecessary navigation (+ human intervention). Option #2 is streamlined, but provides an inconsistent UX when the list changes to have a second item.

I have leant towards #2 because of the expected metrics of my specific scenario in addition to it being more streamlined. The point of inconsistency is a thorn in my side however.

Thoughts? Votes? Any UX experts want to shed some opinions?

Tags:

Feb 7 2012

Finding solutions not covered by automated builds

Category: Rory Primrose @ 12:01

I am slowing building a set of automated tasks in my current role as a TFS administrator to verify the state of TFS. My latest task looks for solutions that are not covered by automated builds.

It’s a fairly straight forward task that enumerates solution files and matches them to build definitions across all projects and collections in a TFS instance.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Xml;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace TFSVerifier
{
    [Export(typeof(ITask))]
    public class FindSolutionsWithoutBuildsTask : ITask
    {
        private Uri TfsAddress = new Uri("http://[TfsAddressHere]:8080/tfs", UriKind.Absolute);

        public string Name
        {
            get { return GetType().Name; }
        }

        public void Execute()
        {
            TfsTeamProjectCollection server = new TfsTeamProjectCollection(TfsAddress);

            server.EnsureAuthenticated();

            TfsConfigurationServer configurationServer = server.ConfigurationServer;

            ReadOnlyCollection<CatalogNode> collectionNodes = configurationServer.CatalogNode.QueryChildren(
                new[] { CatalogResourceTypes.ProjectCollection },
                false, CatalogQueryOptions.None);

            collectionNodes.ToList().ForEach(x => ProcessCollection(server, x));
        }

        private void ProcessCollection(TfsTeamProjectCollection server, CatalogNode collectionNode)
        {
            Guid collectionId = new Guid(collectionNode.Resource.Properties["InstanceId"]);
            TfsTeamProjectCollection teamProjectCollection = server.ConfigurationServer.GetTeamProjectCollection(collectionId);

            ReadOnlyCollection<CatalogNode> projectNodes = collectionNode.QueryChildren(
                new[] { CatalogResourceTypes.TeamProject },
                false, CatalogQueryOptions.None);

            projectNodes.ToList().ForEach(x => ProcessProject(server, collectionNode, x));
        }

        private void ProcessProject(TfsTeamProjectCollection server, CatalogNode collectionNode, CatalogNode projectNode)
        {
            String projectName = projectNode.Resource.DisplayName;
            
            VersionControlServer versionControl = server.GetService<VersionControlServer>();
            ItemSpec spec = new ItemSpec("$/" + projectName + "/*.sln", RecursionType.Full);
            ItemSet set = versionControl.GetItems(spec, VersionSpec.Latest, DeletedState.NonDeleted, ItemType.File, false);

            if (set.Items.Any() == false)
            {
                return;
            }

            IEnumerable<String> solutionsInProject = from x in set.Items
                                                     select x.ServerItem;

            IBuildServer buildServer = server.GetService<IBuildServer>();
            IBuildDefinition[] definitions = buildServer.QueryBuildDefinitions(projectNode.Resource.DisplayName, QueryOptions.Definitions);
            IEnumerable<String> projectsBeingBuild = ProjectsBuiltInProject(definitions);
            IEnumerable<String> projectsNotBeingBuild = solutionsInProject.Except(projectsBeingBuild);

            if (projectsNotBeingBuild.Any())
            {
                Console.WriteLine(collectionNode.Resource.DisplayName + ": " + projectName);

                Console.ForegroundColor = ConsoleColor.Yellow;

                projectsNotBeingBuild.ToList().ForEach(x => Console.WriteLine(x));

                Console.ForegroundColor = ConsoleColor.Gray;
            }
        }

        private IEnumerable<String> ProjectsBuiltInProject(IBuildDefinition[] definitions)
        {
            foreach (IBuildDefinition definition in definitions)
            {
                IEnumerable<String> projectsToBuild = ProjectsToBuild(definition);

                foreach (String projectToBuild in projectsToBuild)
                {
                    yield return projectToBuild;
                }
            }
        }

        private IEnumerable<String> ProjectsToBuild(IBuildDefinition definition)
        {
            XmlDocument doc = new XmlDocument();
            XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);

           manager.AddNamespace("y", "clr-namespace:System.Collections.Generic;assembly=mscorlib");
            manager.AddNamespace("x", "clr-namespace:Microsoft.TeamFoundation.Build.Workflow.Activities;assembly=Microsoft.TeamFoundation.Build.Workflow");

            doc.LoadXml(definition.ProcessParameters);

            XmlNode node = doc.SelectSingleNode("//y:Dictionary/x:BuildSettings/@ProjectsToBuild", manager);

            if (node == null)
            {
                return new List<String>();
            }

            String projectsToBuild = node.Value;
            String[] projects = projectsToBuild.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            return projects;
        }
    }
}

Enjoy

Tags:

Jan 29 2012

Refreshing an expired STSTestCert WIF certificate

Category: .Net | Applications | PersonalRory Primrose @ 18:32

I have been using WIF for the last couple of years on a few of my projects and the STSTestCert gets a bit of a workout on my development machines. This certificate is only valid for 12 months. All the applications that use this test certificate will fail to execute authentication requests once this certificate has expired.

Here is the easiest way to renew the certificate.

  1. Open up MMC and attach the Certificate Manager plugin for the local machine.
  2. Navigate to Certificates (Local Computer) -> Personal -> Certificates.
  3. Select and delete the expired STSTestCert certificate.
  4. Open VS with elevated rights
  5. Add a new solution
  6. Add a new STS project to that solution using the Tools -> Add STS Reference… menu item
  7. Continue through the wizard
  8. Refresh the MMC console and you should now have a fresh STSTestCert

image

Tags: