Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Visual Studio Solution Cloner

0.00/5 (No votes)
25 Oct 2013 1  
Creates copies of a Visual Studio C++ or C# solution

Introduction

The CloneSolution program was written for Visual Studio 2008 to create entire copies of a directory tree of either a Visual Studio C++ solution or a Visual Studio C# solution.

I wanted to experiment with several versions of a program. I found that copying the entire project directory and loading the copied project in Visual Studio caused problems, because, although in a different folder, a copied solution file contained the same Globally Unique Identifiers (GUIDs) as the original solution file.

I believe it will work with later versions of Visual Studio, although I have not tested this.

This project allows copying the files in a solution and projects as long as they are all under the same folder and making the necessary changes to the copied solution and project file GUIDs.

Using the Code

Usage

For any project created with the Visual Studio Wizard, the solution name is the name of the solution (.sln) file with the extension removed.

The command to generate another solution is in the form:

CloneSolution <NewSolutionName> <OriginalSolutionName> [PathToOldSolution]

will create a new solution in a folder named NewSolutionName that is at the same level in the directory tree as the path to the original solution.

If the program is run in the original folder, then the last parameter, PathToOldSolution, can be omitted.

Every occurrence of "OriginalSolutionName" in every file name and in every file's text will be changed to "NewSolutionName". Every occurrence of ORIGINALSOLUTIONNAME will be changed to "NEWSOLUTIONNAME".

The CloneSolution program will change the GUIDs to be new values, but GUIDs that were equal to each other before will still be equal, just different values.

GUIDs for external services, COM objects, and perhaps other GUIDs, should not be changed. If you identify any GUID that should not be changed, it can be added to a file named exclusion.txt. Each GUID must be on a new line in the file. The GUIDs must be in the form:

{5903BC26-D583-362A-619C-DA5CB9C74321}

I used file exclusion.txt years ago, but I haven't needed it for a long time. I forget whether file exclusion.txt must be copied to the original solution folder, but I think that is necessary. There should only be a need for one exclusion.txt file, so the code could be modified to load file exclusion.txt from a known location. I have not done that.

Example:

I cloned a copy of the CloneSolution program with the following command: (I modified the path for security reasons).

CloneSolution BizarreSolution CloneSolution C:\Users\MyUserName\Projects\Tools\CloneSolution\

That command line created a folder C:\Users\MyUserName\Projects\Tools\BizarreSolution\ with the BizarreSolution files that are either identical, or modified, copies of the CloneSolution project files. This solution could be loaded and built successfully.

The SolutionCloner Class

The SolutionCloner class in file SolutionCloner.cs does all the heavy lifting. The parse arguments from the 'main' function in file Program.cs call the CloneSolution method of the SolutionCloner class instance. This recursively traverses the original solutions directories and files and creates identical directories and corresponding files for the new solution.

The SolutionCloner class is show here:

//=======================================================================
// Copyright (C) 2013 William Hallahan
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//=======================================================================

using System;
using System.IO;
using System.Collections.Generic;

namespace CloneSolution
{
    /// <summary>
    /// This class clones an entire solution.
    /// </summary>
    public class SolutionCloner
    {
        protected string m_newSolutionName;
        protected string m_newSolutionNameUpperCase;
        protected string m_newSolutionNameLowerCase;

        protected string m_oldSolutionName;
        protected string m_oldSolutionNameUpperCase;
        protected string m_oldSolutionNameLowerCase;

        protected GuidReplacer m_guidReplacer;

        protected string m_sourceFileName;

        protected List<string> m_listOfFileExtensionsToSkip;
        protected List<string> m_listOfTextFileExtensions;

        #region constructor
        /// <summary>
        /// This constructor takes a new solution name and an old solution name.
        /// </summary>
        /// <param name="newSolutionName">The new name of the solution.</param>
        /// <param name="oldSolutionName">The old name of a solution.</param>
        public SolutionCloner(string newSolutionName, string oldSolutionName)
        {
            m_newSolutionName = newSolutionName;
            m_newSolutionNameUpperCase = newSolutionName.ToUpper();
            m_newSolutionNameLowerCase = newSolutionName.ToLower();

            m_oldSolutionName = oldSolutionName;
            m_oldSolutionNameUpperCase = oldSolutionName.ToUpper();
            m_oldSolutionNameLowerCase = oldSolutionName.ToLower();

            m_guidReplacer = new GuidReplacer();

            // Create a list of file extensions to skip.
            m_listOfFileExtensionsToSkip = new List<String>();
            m_listOfFileExtensionsToSkip.Add(".suo");
            m_listOfFileExtensionsToSkip.Add(".pdb");
            m_listOfFileExtensionsToSkip.Add(".tlb");
            m_listOfFileExtensionsToSkip.Add(".cache");
            m_listOfFileExtensionsToSkip.Add(".resource");
            m_listOfFileExtensionsToSkip.Add(".ilk");
            m_listOfFileExtensionsToSkip.Add(".ncb");
            m_listOfFileExtensionsToSkip.Add(".plg");
            m_listOfFileExtensionsToSkip.Add(".exe");
            m_listOfFileExtensionsToSkip.Add(".obj");
            m_listOfFileExtensionsToSkip.Add(".sbr");
            m_listOfFileExtensionsToSkip.Add(".aps");
            m_listOfFileExtensionsToSkip.Add(".res");
            m_listOfFileExtensionsToSkip.Add(".pch");
            m_listOfFileExtensionsToSkip.Add(".idb");
            m_listOfFileExtensionsToSkip.Add(".bsc");
            m_listOfFileExtensionsToSkip.Add(".dep");
            m_listOfFileExtensionsToSkip.Add(".intermediate");
            m_listOfFileExtensionsToSkip.Add(".user");
            m_listOfFileExtensionsToSkip.Add(".embed");
            m_listOfFileExtensionsToSkip.Add(".zip");

            // Create a list of file extensions to be copied as text files.
            m_listOfTextFileExtensions = new List<String>();
            m_listOfTextFileExtensions.Add(".sln");
            m_listOfTextFileExtensions.Add(".csproj");
            m_listOfTextFileExtensions.Add(".vcproj");
            m_listOfTextFileExtensions.Add(".c");
            m_listOfTextFileExtensions.Add(".cs");
            m_listOfTextFileExtensions.Add(".c");
            m_listOfTextFileExtensions.Add(".cpp");
            m_listOfTextFileExtensions.Add(".cc");
            m_listOfTextFileExtensions.Add(".h");
            m_listOfTextFileExtensions.Add(".hh");
            m_listOfTextFileExtensions.Add(".hpp");
            m_listOfTextFileExtensions.Add(".resx");
            m_listOfTextFileExtensions.Add(".rc");
            m_listOfTextFileExtensions.Add(".rc2");
            m_listOfTextFileExtensions.Add(".def");
            m_listOfTextFileExtensions.Add(".idl");
            m_listOfTextFileExtensions.Add(".odl");
            m_listOfTextFileExtensions.Add(".rgs");
            m_listOfTextFileExtensions.Add(".reg");
            m_listOfTextFileExtensions.Add(".txt");
            m_listOfTextFileExtensions.Add(".log");
            m_listOfTextFileExtensions.Add(".fml");
            m_listOfTextFileExtensions.Add(".xml");
            m_listOfTextFileExtensions.Add(".settings");
            m_listOfTextFileExtensions.Add(".config");
        }
        #endregion

        /// <summary>
        /// This is the main method to clone a solution.
        /// </summary>
        /// <param name="destinationPath">
        /// The path to where the new solution will be located.</param>
        /// <param name="sourcePath">
        /// The path to where the old solution to be copied is located.</param>
        public void CloneSolution(string destinationPath, string sourcePath)
        {
            DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
            DirectoryInfo destDirectoryInfo = new DirectoryInfo(destinationPath);

            CopyDirectory(destDirectoryInfo, sourceDirectoryInfo);
        }

        /// <summary>
        /// This method copies a directory and recursively copies all subdirectories.
        /// </summary>
        /// <param name="destDirectoryInfo">
        /// Specifies the current directory where items are to be coped to.</param>
        /// <param name="sourceDirectoryInfo">
        /// Specified the current directory where items are to be copied from.</param>
        protected void CopyDirectory(DirectoryInfo destDirectoryInfo, DirectoryInfo sourceDirectoryInfo)
        {
            // If the destination directory does not exist, then create it.
            string destDirectoryFullName = destDirectoryInfo.FullName;
            // If the old solution name is in the path, then replace it with the new name.
            destDirectoryFullName = destDirectoryFullName.Replace(m_oldSolutionName, m_newSolutionName);

            if (!Directory.Exists(destDirectoryFullName))
            {
                Directory.CreateDirectory(destDirectoryFullName);
            }

            // Clone all the files in the directory.
            foreach (FileInfo fileInfo in sourceDirectoryInfo.GetFiles())
            {
                m_sourceFileName = fileInfo.Name;
                // Replace the old solution name string with the new solution name string to get the destination file name.
                string destinationFileName = m_sourceFileName;
                int position = destinationFileName.IndexOf(m_oldSolutionName);
                if (position > -1)
                {
                    destinationFileName = destinationFileName.Replace(m_oldSolutionName, m_newSolutionName);
                }

                string sourcePathFileName = Path.Combine(sourceDirectoryInfo.FullName, m_sourceFileName);
                string destinationPathFileName = Path.Combine(destDirectoryFullName, destinationFileName);
                CopyFile(destinationPathFileName, sourcePathFileName);
            }

            // Traverse each subdirectory and recursively descend to clone each subdirectory.
            foreach (DirectoryInfo sourceSubDirectoryInfo in sourceDirectoryInfo.GetDirectories())
            {
                DirectoryInfo destinationSubDirectoryInfo = 
                    destDirectoryInfo.CreateSubdirectory(sourceSubDirectoryInfo.Name);
                CopyDirectory(destinationSubDirectoryInfo, sourceSubDirectoryInfo);
            }
        }

        /// <summary>
        /// This method copies a file. Certain file types are skipped, and not copied.
        /// </summary>
        /// <param name="destinationPathFileName">
        /// The destination path and name for the copied file.</param>
        /// <param name="sourcePathFileName">
        /// The path and name of the file to be copied.</param>
        protected void CopyFile(string destinationPathFileName, string sourcePathFileName)
        {
            string sourceFileExtension = Path.GetExtension(sourcePathFileName).ToLower();

            // Skip copying files that have certain file extensions.
            if (!m_listOfFileExtensionsToSkip.Contains(sourceFileExtension))
            {
                string sourceFileName = Path.GetFileName(sourcePathFileName);

                // Check the file extension to test whether to do a binary copy,
                // or to copy the file as a text file.
                if (m_listOfTextFileExtensions.Contains(sourceFileExtension))
                {
                    CopyFileAndChangeGuids(destinationPathFileName, sourcePathFileName);
                }
                else
                {
                    CopyBinaryFile(destinationPathFileName, sourcePathFileName);
                }
            }
        }

        /// <summary>
        /// This method copies special text files, such as project files, solution files, and code.
        /// </summary>
        /// <param name="destinationPathFileName">
        /// The destination path and name for the copied file.</param>
        /// <param name="sourcePathFileName">The path and name of the file to be copied.</param>
        protected void CopyFileAndChangeGuids(string destinationPathFileName, string sourcePathFileName)
        {
            // Open the input file and the output file.
            using (FileStream sourceStream = new FileStream(sourcePathFileName,
                                                            FileMode.Open,
                                                            FileAccess.Read,
                                                            System.IO.FileShare.ReadWrite))
            using (FileStream destinationStream = new FileStream(destinationPathFileName,
                                                                 FileMode.Create,
                                                                 FileAccess.Write,
                                                                 System.IO.FileShare.ReadWrite))
            using (StreamReader sourceStreamReader = new StreamReader(sourceStream))
            using (StreamWriter destStreamWriter = new StreamWriter(destinationStream))
            //using (StreamReader sourceStreamReader = new StreamReader(sourcePathFileName))
            //using (StreamWriter destStreamWriter = new StreamWriter(destinationPathFileName))
            {
                string lineOfText = string.Empty;

                while ((lineOfText = sourceStreamReader.ReadLine()) != null)
                {
                    lineOfText = lineOfText.Replace(m_oldSolutionName, m_newSolutionName);
                    lineOfText = lineOfText.Replace(m_oldSolutionNameUpperCase, m_newSolutionNameUpperCase);
                    lineOfText = lineOfText.Replace(m_oldSolutionNameLowerCase, m_newSolutionNameLowerCase);
                    // Don't change certain GUIDs.
                    if (!lineOfText.Contains("<Service Include=\"{"))
                    {
                        lineOfText = m_guidReplacer.ChangeGuids(lineOfText);
                    }
                    destStreamWriter.WriteLine(lineOfText);
                }
            }
        }

        /// <summary>
        /// This method copies binary files. 
        /// Binary files are just copied by the system, and only the title can be modified.
        /// </summary>
        /// <param name="destinationPathFileName">
        /// The destination path and name for the copied file.</param>
        /// <param name="sourcePathFileName">The path and name of the file to be copied.</param>
        protected void CopyBinaryFile(string destinationPathFileName, string sourcePathFileName)
        {
            using (FileStream sourceStream = new FileStream(sourcePathFileName,
                                                            FileMode.Open,
                                                            FileAccess.Read,
                                                            System.IO.FileShare.ReadWrite))
            using (FileStream destinationStream = new FileStream(destinationPathFileName,
                                                                 FileMode.Create,
                                                                 FileAccess.Write,
                                                                 System.IO.FileShare.ReadWrite))
            {
                BinaryReader reader = new BinaryReader(sourceStream);
                BinaryWriter writer = new BinaryWriter(destinationStream);

                // Create a buffer for the file data.
                byte[] buffer = new Byte[1024];
                int bytesRead = 0;

                // Read bytes from the source stream and write the bytes to the destination stream.
                while ((bytesRead = sourceStream.Read(buffer, 0, 1024)) > 0)
                {
                    destinationStream.Write(buffer, 0, bytesRead);
                }
            }
        }
    }
}

It would be trivial to modify this program to handle other .NET languages merely by adding the file extension for the project file to the SolutionCloner.cs file.

The Main Program

The 'main' function is in the file Program.cs. This does a simple argument parsing and calls the CloneSolution method of the SolutionCloner class.

//=======================================================================
// Copyright (C) 2013 William Hallahan
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//=======================================================================

using System;
using System.IO;

namespace CloneSolution
{
    class Program
    {
        static void Main(string[] args)
        {
            // args[0] contains the new solution name.
            if (args.Length < 2)
            {
                if (args.Length < 1)
                {
                    Console.WriteLine("Usage:");
                    Console.WriteLine("");
                    Console.WriteLine("    CloneSolution NewSolutionName OldSolutionName <path>");
                    Console.WriteLine("");
                    Console.WriteLine("where <path> is an optional path to the old solution directory,");
                    Console.WriteLine("e.g. C:\\Projects\\SolutionName.  If <path> is omitted, then the");
                    Console.WriteLine("current directory is used for the path to the solution.");
                    Console.WriteLine("The new solution directory is always created in the parent directory");
                    Console.WriteLine("of the existing solution directory, i.e. the new solution has the");
                    Console.WriteLine("same parent folder as the old solution.");
                    Console.WriteLine("");
                    Console.WriteLine("All project folders and files are duplicated, and the solution name");
                    Console.WriteLine("is replaced with the new name, both for file names, and for text files.");
                    Console.WriteLine("contents. Solution, project, and assembly GUIDS, are all updated in a");
                    Console.WriteLine("correct and consistent fashion.  DLLs are copied, but not modified.");
                    Console.WriteLine("");
                    Console.WriteLine("");
                }
                else
                {
                    Console.WriteLine("No new solution name was specified.");
                }
            }
            else
            {
                string newSolutionName = args[0];
                string oldSolutionName = args[1];

                string sourcePath = Directory.GetCurrentDirectory();

                // If a third argument is not specified, then use the current directory for the source path.
                if (args.Length > 2)
                {
                    sourcePath = args[2];
                }
                else
                {
                    sourcePath = Directory.GetCurrentDirectory();
                }

                // Make sure the source path exists.
                if (!Directory.Exists(sourcePath))
                {
                    Console.WriteLine("The source path does not exist.");
                }
                else
                {
                    // Use the source path, and the new solution name, to calculate the destination path.
                    DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
                    DirectoryInfo parentDirectoryInfo = sourceDirectoryInfo.Parent;
                    string parentPath = parentDirectoryInfo.FullName;
                    string destinationPath = Path.Combine(parentPath, newSolutionName);

                    // Clone the solution.
                    SolutionCloner solutionCloner = new SolutionCloner(newSolutionName, oldSolutionName);
                    solutionCloner.CloneSolution(destinationPath, sourcePath);
                }
            }
        }
    }
}

Points of Interest

Warning: This program creates the NewSolutionName folder and overwrites the files in that folder. If you specify a folder that already has files, you might wipe out code. If you put the first two arguments to this program backwards, and the NewSolutionName folder already exists, you will overwrite the files you want to clone. Always be careful when you specify the arguments to the program. If you are not used to using this program, you might want to make a backup until you understand how to use it.

History

  • Initial article creation
  • Made extension lists and removed long if-statement for file extension checks. Fixed bad error message that stated the new solution name was missing, when it was actually the old solution name. Changed data members to start with 'm_' instead of just '_'. Other minor article text changes.
  • Minor update to fix the help prompt.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here