Visual Studio 解决方案克隆器






4.93/5 (15投票s)
创建 Visual Studio C++ 或 C# 解决方案的副本
引言
CloneSolution
程序是为 Visual Studio 2008 编写的,用于创建 Visual Studio C++ 解决方案或 Visual Studio C# 解决方案的整个目录树的副本。
我想尝试几个版本的程序。 我发现复制整个项目目录并在 Visual Studio 中加载复制的项目会导致问题,因为尽管在不同的文件夹中,复制的解决方案文件包含与原始解决方案文件相同的全局唯一标识符 (GUID)。
我相信它适用于更高版本的 Visual Studio,尽管我没有对此进行测试。
此项目允许复制解决方案和项目中的文件,只要它们都在同一个文件夹下,并对复制的解决方案和项目文件 GUID 进行必要的更改。
Using the Code
用法
对于使用 Visual Studio 向导创建的任何项目,解决方案名称是解决方案 (.sln) 文件的名称,并删除了扩展名。
生成另一个解决方案的命令格式如下
CloneSolution <NewSolutionName> <OriginalSolutionName> [PathToOldSolution]
将在一个名为 *NewSolutionName* 的文件夹中创建一个新的解决方案,该文件夹与原始解决方案的路径位于目录树中的同一级别。
如果在原始文件夹中运行该程序,则可以省略最后一个参数 PathToOldSolution
。
每个文件名和每个文件的文本中每次出现的“OriginalSolutionName
”都将更改为“NewSolutionName
”。 每次出现的 ORIGINALSOLUTIONNAME
都将更改为“NEWSOLUTIONNAME
”。
CloneSolution
程序会将 GUID 更改为新值,但之前彼此相等的 GUID 仍然相等,只是值不同。
外部服务、COM 对象以及其他 GUID 的 GUID 不应更改。 如果您确定任何不应更改的 GUID,则可以将其添加到名为 *exclusion.txt* 的文件中。 每个 GUID 必须在该文件的新行中。 GUID 的格式必须为
{5903BC26-D583-362A-619C-DA5CB9C74321}
我多年前使用过文件 *exclusion.txt*,但我很久没有需要它了。 我忘记了文件 *exclusion.txt* 是否必须复制到原始解决方案文件夹,但我认为这是必要的。 应该只需要一个 *exclusion.txt* 文件,因此可以修改代码以从已知位置加载文件 *exclusion.txt*。 我没有这样做。
示例
我使用以下命令克隆了 CloneSolution
程序的副本:(出于安全原因,我修改了路径)。
CloneSolution BizarreSolution CloneSolution C:\Users\MyUserName\Projects\Tools\CloneSolution\
该命令行创建了一个文件夹 *C:\Users\MyUserName\Projects\Tools\BizarreSolution\*,其中包含 *BizarreSolution* 文件,这些文件是 CloneSolution
项目文件的相同或修改副本。 可以成功加载并构建此解决方案。
SolutionCloner 类
文件 *SolutionCloner.cs* 中的 SolutionCloner
类完成了所有繁重的工作。 文件 *Program.cs* 中 'main
' 函数的解析参数调用 SolutionCloner
类实例的 CloneSolution
方法。 这会递归地遍历原始解决方案目录和文件,并为新解决方案创建相同的目录和相应的文件。
SolutionCloner
类显示如下
//=======================================================================
// 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);
}
}
}
}
}
只需将项目文件的文件扩展名添加到 *SolutionCloner.cs* 文件中,就可以轻松地修改此程序以处理其他 .NET 语言。
主程序
'main
' 函数位于文件 *Program.cs* 中。 这会进行简单的参数解析并调用 SolutionCloner
类的 CloneSolution
方法。
//=======================================================================
// 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);
}
}
}
}
}
关注点
警告:此程序创建 *NewSolutionName* 文件夹并覆盖该文件夹中的文件。 如果您指定的文件夹已经有文件,您可能会擦除代码。 如果您将此程序的前两个参数颠倒了,并且 *NewSolutionName* 文件夹已存在,您将覆盖要克隆的文件。 在指定程序的参数时,请务必小心。 如果您不习惯使用此程序,您可能需要进行备份,直到您了解如何使用它。
历史
- 最初的文章创建
- 制作了扩展名列表,并删除了用于文件扩展名检查的长
if
语句。 修复了错误的错误消息,该消息表明缺少新的解决方案名称,而实际上是旧的解决方案名称。 将数据成员更改为以“m_
”开头,而不是仅以“_
”开头。 其他次要的文章文本更改。 - 小更新以修复帮助提示。