• No recently listened tracks.

Deploying to Multiple Locations in TFS 2010

This is just a forewarning, I am not an expert in the ways of TFS. I’ve only been working with TFS in general for about a year, and TFS 2010 for about 2 months or so. That being said, if anyone has recommendations on a better way to do this, please point me in the right direction 🙂

Scenario:

We want to deploy our built code to different environments, but, we don’t want everything that’s dumped into the Release folder.

The Plan:

The plan is to create a custom build activity to copy files from one place to another checking each file against an exclusion list.

The Guts:

Referencing my previous post, Dependency Replication in TFS 2010, I will build upon the write-ups by Ewald Hofman.

You can grab the file here: DeployFiles.cs

Here’s the whole activity:

using System;
using System.Activities;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.Build.Workflow.Tracking;

namespace BuildProcess.Activities
{
    [BuildActivity(HostEnvironmentOption.Agent)]
    public sealed class DeployFiles : CodeActivity
    {
        //Source dir being deployed from
        [RequiredArgument]
        public InArgument<string> SourceDir { get; set; }
        //Destination dir being copied from
        [RequiredArgument]
        public InArgument<string[]> DestinationDir { get; set; }
        //Files to exclude
        [RequiredArgument]
        public InArgument<string> FileExclusions { get; set; }
        //Folders to exclude
        [RequiredArgument]
        public InArgument<string> FolderExclusions { get; set; }

        //globals
        public string fileExclusionPattern = "";
        public string folderExclusionPattern = "";

        protected override void Execute(CodeActivityContext context)
        {
            // Obtain the runtime value of the Text input argument
            string fileExclusions = context.GetValue(this.FileExclusions);
            string folderExclusions = context.GetValue(this.FolderExclusions);
            string[] destinations = context.GetValue(this.DestinationDir);
            DirectoryInfo sourceDir = new DirectoryInfo(context.GetValue(this.SourceDir));

            //parse exclusions, add them to regex patterns
            if (!String.IsNullOrWhiteSpace(fileExclusions))
            {
                string[] fileexarr = fileExclusions.Split(',');
                fileExclusionPattern = "(";
                foreach (string s in fileexarr)
                {
                    fileExclusionPattern += s.ToUpper().Trim().Replace(".", @"\.").Replace("*", @"[a-zA-Z0-9]*") + "|";
                }

                if(fileExclusionPattern.EndsWith("|"))
                    fileExclusionPattern = fileExclusionPattern.Substring(0, fileExclusionPattern.Length - 1);

                fileExclusionPattern += ")";
            }

            if (!String.IsNullOrWhiteSpace(folderExclusions))
            {
                string[] folderexarr = folderExclusions.Split(',');
                folderExclusionPattern = "(";

                foreach (string s in folderexarr)
                {
                    folderExclusionPattern += s.ToUpper().Trim().Replace(".", @"\.").Replace("*", @"[a-zA-Z0-9]*") + "|";
                }

                if(folderExclusionPattern.EndsWith("|"))
                    folderExclusionPattern = folderExclusionPattern.Substring(0, folderExclusionPattern.Length - 1);

                folderExclusionPattern += ")";
            }

            foreach (string dir in destinations)
            {
                DirectoryInfo destDir = new DirectoryInfo(dir);

                context.Track(new BuildInformationRecord<BuildMessage>()
                {
                    Value = new BuildMessage()
                    {
                        Importance = BuildMessageImportance.High,
                        Message = "Source: " + sourceDir.FullName + "\nDestination: " + destDir.FullName +
                            "\nFolder Exclusion Pattern: " + folderExclusionPattern +
                            "\nFile Exclusion Pattern: " + fileExclusionPattern,
                    },
                });

                CopyDir(sourceDir, destDir);
            }
        }

        private void CopyDir(DirectoryInfo sourceDir, DirectoryInfo destDir)
        {
            //create destination dir if it doesn't exist
            if (!destDir.Exists)
            {
                destDir.Create();
            }

            // get all files from current dir
            FileInfo[] files = sourceDir.GetFiles();

            //copy ze files!!
            foreach (FileInfo file in files)
            {
                if (!String.IsNullOrWhiteSpace(fileExclusionPattern))
                {
                    if (!Regex.IsMatch(file.Name.ToUpper(), fileExclusionPattern))
                    {
                        if (File.Exists(Path.Combine(destDir.FullName, file.Name)))
                        {
                            File.Delete(Path.Combine(destDir.FullName, file.Name));
                        }
                        file.CopyTo(Path.Combine(destDir.FullName, file.Name));
                    }
                }
                else
                {
                    if (File.Exists(Path.Combine(destDir.FullName, file.Name)))
                    {
                        File.Delete(Path.Combine(destDir.FullName, file.Name));
                    }
                    file.CopyTo(Path.Combine(destDir.FullName, file.Name),true);
                }
            }

            // get subdirectories.
            DirectoryInfo[] dirs = sourceDir.GetDirectories();

            foreach (DirectoryInfo dir in dirs)
            {
                // Get destination directory.
                string destinationDir = Path.Combine(destDir.FullName, dir.Name);

                if (!String.IsNullOrWhiteSpace(folderExclusionPattern))
                {
                    if (!Regex.IsMatch(dir.Name.ToUpper(), folderExclusionPattern))
                    {
                        // Call CopyDirectory() recursively.
                        CopyDir(dir, new DirectoryInfo(destinationDir));
                    }
                }
                else
                {
                    // Call CopyDirectory() recursively.
                    CopyDir(dir, new DirectoryInfo(destinationDir));
                }
            }
        }
    }
}

Explanation:

Now i’ll go section by section explaining exactly what’s going on. First up are the input requirements:

         //Source dir being deployed from
        [RequiredArgument]
        public InArgument<string> SourceDir { get; set; }

The SourceDir is where the deployment starts from. This means what subdirectory from the BinariesDirectory gets deployed. This is important for Websites, since they get dropped in a _PublishedWebsites folder inside the BinariesDirectory.

        //Destination dir being copied from
        [RequiredArgument]
        public InArgument<string[]> DestinationDir { get; set; }

The DestinationDir argument is a string array of paths being passed in (we use UNC network paths, ie: \\deploymentbox\websites\sitename)

        //Files to exclude
        [RequiredArgument]
        public InArgument<string> FileExclusions { get; set; }

The FileExclusions argument is meant to be a comma delimited string of filenames and patterns you want to exclude. (ie: “web.config, *.pdb”)

        //Folders to exclude
        [RequiredArgument]
        public InArgument<string> FolderExclusions { get; set; }

The FolderExclusions argument is similar to the FileExclusions, except that if there are certain folders you don’t want deployed, you can specify them here

        //globals
        public string fileExclusionPattern = "";
        public string folderExclusionPattern = "";

These two variables will be used to build the Regex pattern which we will check files and folders against.

Now we’ll go into

protected override void Execute(CodeActivityContext context)

explaining each part in more detail.

            // Obtain the runtime value of the Text input argument
            string fileExclusions = context.GetValue(this.FileExclusions);
            string folderExclusions = context.GetValue(this.FolderExclusions);
            string[] destinations = context.GetValue(this.DestinationDir);
            DirectoryInfo sourceDir = new DirectoryInfo(context.GetValue(this.SourceDir));

Here we get the values being passed in at execution time

            //parse exclusions, add them to regex patterns
            if (!String.IsNullOrWhiteSpace(fileExclusions))
            {
                string[] fileexarr = fileExclusions.Split(',');
                fileExclusionPattern = "(";
                foreach (string s in fileexarr)
                {
                    fileExclusionPattern += s.ToUpper().Trim().Replace(".", @"\.").Replace("*", @"[a-zA-Z0-9]*") + "|";
                }

                if(fileExclusionPattern.EndsWith("|"))
                    fileExclusionPattern = fileExclusionPattern.Substring(0, fileExclusionPattern.Length - 1);

                fileExclusionPattern += ")";
            }

            if (!String.IsNullOrWhiteSpace(folderExclusions))
            {
                string[] folderexarr = folderExclusions.Split(',');
                folderExclusionPattern = "(";

                foreach (string s in folderexarr)
                {
                    folderExclusionPattern += s.ToUpper().Trim().Replace(".", @"\.").Replace("*", @"[a-zA-Z0-9]*") + "|";
                }

                if(folderExclusionPattern.EndsWith("|"))
                    folderExclusionPattern = folderExclusionPattern.Substring(0, folderExclusionPattern.Length - 1);

                folderExclusionPattern += ")";
            }

Here we check to see if anything was passed into the file or folder exclusion parameters. If something was passed in, we split the comma delimited string into an array and then iterate through it adding the exclusions to their appropriate Regex pattern.

            foreach (string dir in destinations)
            {
                DirectoryInfo destDir = new DirectoryInfo(dir);

                context.Track(new BuildInformationRecord<BuildMessage>()
                {
                    Value = new BuildMessage()
                    {
                        Importance = BuildMessageImportance.High,
                        Message = "Source: " + sourceDir.FullName + "\nDestination: " + destDir.FullName +
                            "\nFolder Exclusion Pattern: " + folderExclusionPattern +
                            "\nFile Exclusion Pattern: " + fileExclusionPattern,
                    },
                });

                CopyDir(sourceDir, destDir);
            }

Now we loop through all the destinations we need to deploy to, copying the files and folders.

Now lets take a look at

private void CopyDir(DirectoryInfo sourceDir, DirectoryInfo destDir)
            //create destination dir if it doesn't exist
            if (!destDir.Exists)
            {
                destDir.Create();
            }

Let’s make sure the destination directory exists, creating it if it doesn’t exist already.

            // get all files from current dir
            FileInfo[] files = sourceDir.GetFiles();

Here we get the all the files from the source directory

            //copy ze files!!
            foreach (FileInfo file in files)
            {
                if (!String.IsNullOrWhiteSpace(fileExclusionPattern))
                {
                    if (!Regex.IsMatch(file.Name.ToUpper(), fileExclusionPattern))
                    {
                        if (File.Exists(Path.Combine(destDir.FullName, file.Name)))
                        {
                            File.Delete(Path.Combine(destDir.FullName, file.Name));
                        }
                        file.CopyTo(Path.Combine(destDir.FullName, file.Name));
                    }
                }
                else
                {
                    if (File.Exists(Path.Combine(destDir.FullName, file.Name)))
                    {
                        File.Delete(Path.Combine(destDir.FullName, file.Name));
                    }
                    file.CopyTo(Path.Combine(destDir.FullName, file.Name),true);
                }
            }

Let’s copy some files! Check each file against the exclusion pattern, if one is set, and copy it if needed. If the file needs to be copied and it exists in the destination already, we’ll delete it and copy the file over.

            // get subdirectories.
            DirectoryInfo[] dirs = sourceDir.GetDirectories();

            foreach (DirectoryInfo dir in dirs)
            {
                // Get destination directory.
                string destinationDir = Path.Combine(destDir.FullName, dir.Name);

                if (!String.IsNullOrWhiteSpace(folderExclusionPattern))
                {
                    if (!Regex.IsMatch(dir.Name.ToUpper(), folderExclusionPattern))
                    {
                        // Call CopyDirectory() recursively.
                        CopyDir(dir, new DirectoryInfo(destinationDir));
                    }
                }
                else
                {
                    // Call CopyDirectory() recursively.
                    CopyDir(dir, new DirectoryInfo(destinationDir));
                }
            }

Last we check to see if the directory has any sub-directories and we call CopyDir() recursively to copy all sub directories and files inside them, only of course if they aren’t on the folder exclusion list.