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:
using System;
using System.IO;
using System.Collections.Generic;
namespace CloneSolution
{
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
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();
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");
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
public void CloneSolution(string destinationPath, string sourcePath)
{
DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
DirectoryInfo destDirectoryInfo = new DirectoryInfo(destinationPath);
CopyDirectory(destDirectoryInfo, sourceDirectoryInfo);
}
protected void CopyDirectory(DirectoryInfo destDirectoryInfo, DirectoryInfo sourceDirectoryInfo)
{
string destDirectoryFullName = destDirectoryInfo.FullName;
destDirectoryFullName = destDirectoryFullName.Replace(m_oldSolutionName, m_newSolutionName);
if (!Directory.Exists(destDirectoryFullName))
{
Directory.CreateDirectory(destDirectoryFullName);
}
foreach (FileInfo fileInfo in sourceDirectoryInfo.GetFiles())
{
m_sourceFileName = fileInfo.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);
}
foreach (DirectoryInfo sourceSubDirectoryInfo in sourceDirectoryInfo.GetDirectories())
{
DirectoryInfo destinationSubDirectoryInfo =
destDirectoryInfo.CreateSubdirectory(sourceSubDirectoryInfo.Name);
CopyDirectory(destinationSubDirectoryInfo, sourceSubDirectoryInfo);
}
}
protected void CopyFile(string destinationPathFileName, string sourcePathFileName)
{
string sourceFileExtension = Path.GetExtension(sourcePathFileName).ToLower();
if (!m_listOfFileExtensionsToSkip.Contains(sourceFileExtension))
{
string sourceFileName = Path.GetFileName(sourcePathFileName);
if (m_listOfTextFileExtensions.Contains(sourceFileExtension))
{
CopyFileAndChangeGuids(destinationPathFileName, sourcePathFileName);
}
else
{
CopyBinaryFile(destinationPathFileName, sourcePathFileName);
}
}
}
protected void CopyFileAndChangeGuids(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))
using (StreamReader sourceStreamReader = new StreamReader(sourceStream))
using (StreamWriter destStreamWriter = new StreamWriter(destinationStream))
{
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);
if (!lineOfText.Contains("<Service Include=\"{"))
{
lineOfText = m_guidReplacer.ChangeGuids(lineOfText);
}
destStreamWriter.WriteLine(lineOfText);
}
}
}
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);
byte[] buffer = new Byte[1024];
int bytesRead = 0;
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.
using System;
using System.IO;
namespace CloneSolution
{
class Program
{
static void Main(string[] args)
{
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 (args.Length > 2)
{
sourcePath = args[2];
}
else
{
sourcePath = Directory.GetCurrentDirectory();
}
if (!Directory.Exists(sourcePath))
{
Console.WriteLine("The source path does not exist.");
}
else
{
DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
DirectoryInfo parentDirectoryInfo = sourceDirectoryInfo.Parent;
string parentPath = parentDirectoryInfo.FullName;
string destinationPath = Path.Combine(parentPath, newSolutionName);
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.