public MainViewModel() { var curDir = Directory.GetCurrentDirectory(); while (null != curDir) { var slns = Directory.GetFiles(curDir, "*.sln"); if (slns.Length > 0) { var vcxprojs = Directory.GetFiles(curDir, "*.vcxproj", SearchOption.AllDirectories); if (vcxprojs.Length > 0) { foreach (var x in vcxprojs) { ProjectProposals.Add(new SourceProjectViewModel(slns[0], x)); } break; // done! } } var di = Directory.GetParent(curDir); if (null == di) { break; // didn't find any .vcxproj files } curDir = di.FullName; } CloneProject = new DelegateCommand(_ => { Messages.Clear(); Messages.Add(MessageViewModel.CreateInfo("Let's go...")); var newProjName = NameOfNewProject.Trim(); if (string.IsNullOrEmpty(newProjName) || newProjName.Contains(' ')) { Messages.Add(MessageViewModel.CreateError("Please enter a valid new project's name (without spaces!)")); return; } var newProjNameAlphaNum = RegexNonAlphaNumChars.Replace(newProjName, ""); try { // 1. See if there is a .vcxproj-file in the original folder; if not => exit! var origProjFile = new FileInfo(OriginalVsProjFile); var origDir = origProjFile.Directory; var origUserFile = new FileInfo(OriginalVsProjFile + ".user"); var origFiltersFile = new FileInfo(OriginalVsProjFile + ".filters"); // 2. Copy to AND rename the 3 files in the target folder: .vcxproj, .vcxproj.user, .vcxproj.filters var targetDir = new DirectoryInfo(TargetCopyProjFolder); if (targetDir.GetFiles().Count() > 0 || targetDir.GetDirectories().Count() > 0) { Messages.Add(MessageViewModel.CreateWarning("Target directory '" + targetDir.FullName + "' is not empty")); } FileInfo targetProjFile; FileInfo targetFiltersFile; { var targetProjFilePath = Path.Combine(targetDir.FullName, newProjName + ".vcxproj"); var targetUserFilePath = Path.Combine(targetDir.FullName, newProjName + ".vcxproj.user"); var targetFiltersFilePath = Path.Combine(targetDir.FullName, newProjName + ".vcxproj.filters"); // The .vcxproj must always exist File.Copy(origProjFile.FullName, targetProjFilePath); targetProjFile = new FileInfo(targetProjFilePath); targetFiltersFile = new FileInfo(targetFiltersFilePath); Messages.Add(MessageViewModel.CreateSuccess($"Created '{targetProjFile.Name}' file in '{targetDir.FullName}'.")); // ...but not so the other two if (origUserFile.Exists) { File.Copy(origUserFile.FullName, targetUserFilePath); var targetUserFile = new FileInfo(targetUserFilePath); Messages.Add(MessageViewModel.CreateSuccess($"Created '{targetUserFile.Name}' file in '{targetDir.FullName}'.")); } else { Messages.Add(MessageViewModel.CreateWarning($"There is no '{origUserFile.Name}' file in '{origDir.FullName}'.")); } if (origFiltersFile.Exists) { File.Copy(origFiltersFile.FullName, targetFiltersFilePath); Messages.Add(MessageViewModel.CreateSuccess($"Created '{targetFiltersFile.Name}' file in '{targetDir.FullName}'.")); } else { Messages.Add(MessageViewModel.CreateWarning($"There is no '{origFiltersFile.Name}' file in '{origDir.FullName}'.")); } } // 3. Parse target .vcxproj-file and modify it... var targetProjFileContents = File.ReadAllText(targetProjFile.FullName); var targetFiltersFileContents = File.ReadAllText(targetFiltersFile.FullName); var itemGroups = RegexFindAllItemGroups.Matches(targetProjFileContents); var sb = new StringBuilder(); var lastIndex = 0; foreach (Match itemGroup in itemGroups) { var part1 = targetProjFileContents.Substring(lastIndex, itemGroup.Index - lastIndex); var part2 = itemGroup.Value; lastIndex += part1.Length + part2.Length; // 3.1 Remove all the <ClCompile/> entries which are childs of an <ItemGroup/> element part2 = RegexClCompile.Replace(part2, string.Empty); // 3.2 Remove all the <ClInclude/> entries which are childs of an <ItemGroup/> element part2 = RegexClInclude.Replace(part2, string.Empty); sb.Append(part1).Append(part2); } sb.Append(targetProjFileContents.Substring(lastIndex)); targetProjFileContents = sb.ToString(); // 3.3 Set a newly generated GUID inside the <ProjectGuid/> element var newGuid = Guid.NewGuid(); var newGuidString = newGuid.ToString(); var oldGuidMatch = RegexProjectGuid.Match(targetProjFileContents); if (!oldGuidMatch.Success) { Messages.Add(MessageViewModel.CreateError("Couldn't find <ProjectGuid> element")); return; } var oldGuidString = oldGuidMatch.Groups[1].Value; targetProjFileContents = targetProjFileContents.Replace(oldGuidString, newGuidString); // 3.4 Change all relative paths of the <ProjectReference Include="..."> elements, with '...' being the path to change, IF the path does NOT start with a '$' character. { var projRefs = RegexProjectReference.Matches(targetProjFileContents); foreach (Match match in projRefs) { var relPath = match.Groups[1].Value; var realPath = Path.Combine(origDir.FullName, relPath); var relativeFromTarget = GetRelativePathIfPossible(realPath, targetDir); var updatedProjRef = match.Value.Replace(relPath, relativeFromTarget); targetProjFileContents = targetProjFileContents.Replace(match.Value, updatedProjRef); } } // 3.5 Change the content of the <RootNamespace/> element to the new project's name, leaving only alpha-numeric characters { var rootNamespace = RegexRootNamespace.Match(targetProjFileContents); if (rootNamespace.Success) { var oldNamespace = rootNamespace.Groups[1].Value; var updatedRootNamespace = rootNamespace.Value.Replace(oldNamespace, newProjNameAlphaNum); targetProjFileContents = targetProjFileContents.Replace(rootNamespace.Value, updatedRootNamespace); } } // 3.6 Change the content of the <ProjectName/> element to the new project's name. var projectName = RegexProjectName.Match(targetProjFileContents); if (projectName.Success) { var oldName = projectName.Groups[1].Value; var updatedRootNamespace = projectName.Value.Replace(oldName, newProjName); targetProjFileContents = targetProjFileContents.Replace(projectName.Value, updatedRootNamespace); } // 3.7 Change all relative paths of the <Import Project="..." /> elements where the path ('...') does NOT start with a '$' character. { var importProjs = RegexImportProject.Matches(targetProjFileContents); foreach (Match match in importProjs) { var relPath = match.Groups[1].Value; var realPath = Path.Combine(origDir.FullName, relPath); var relativeFromTarget = GetRelativePathIfPossible(realPath, targetDir); var updatedImportProj = match.Value.Replace(relPath, relativeFromTarget); targetProjFileContents = targetProjFileContents.Replace(match.Value, updatedImportProj); } } // 3.8 Disable usage of precompiled headers { var precompHeaders = RegexPrecompiledHeader.Matches(targetProjFileContents); foreach (Match match in precompHeaders) { targetProjFileContents = targetProjFileContents.Replace(match.Value, "<PrecompiledHeader>NotUsing</PrecompiledHeader>"); } } // 3.9 Removing forced include files { var forceInclude = RegexForcedIncludeFiles.Matches(targetProjFileContents); foreach (Match match in forceInclude) { targetProjFileContents = targetProjFileContents.Replace(match.Value, "<ForcedIncludeFiles></ForcedIncludeFiles>"); } } // 3.10 Change all relative paths of the <None Include="..." /> elements { { // .vxcproj: var nones = RegexNone.Matches(targetProjFileContents); foreach (Match match in nones) { var relPath = match.Groups[1].Value; var realPath = Path.Combine(origDir.FullName, relPath); var relativeFromTarget = GetRelativePathIfPossible(realPath, targetDir); var updatedImportProj = match.Value.Replace(relPath, relativeFromTarget); targetProjFileContents = targetProjFileContents.Replace(match.Value, updatedImportProj); } } { // .vxcproj.filters: var nones = RegexNone.Matches(targetFiltersFileContents); foreach (Match match in nones) { var relPath = match.Groups[1].Value; var realPath = Path.Combine(origDir.FullName, relPath); var relativeFromTarget = GetRelativePathIfPossible(realPath, targetDir); var updatedImportProj = match.Value.Replace(relPath, relativeFromTarget); targetFiltersFileContents = targetFiltersFileContents.Replace(match.Value, updatedImportProj); } } } // 3.11 Change all relative paths of the <Image Include="..." /> elements { { // .vxcproj: var images = RegexImage.Matches(targetProjFileContents); foreach (Match match in images) { var relPath = match.Groups[1].Value; var realPath = Path.Combine(origDir.FullName, relPath); var relativeFromTarget = GetRelativePathIfPossible(realPath, targetDir); var updatedImportProj = match.Value.Replace(relPath, relativeFromTarget); targetProjFileContents = targetProjFileContents.Replace(match.Value, updatedImportProj); } } { // .vxcproj.filters: var images = RegexImage.Matches(targetFiltersFileContents); foreach (Match match in images) { var relPath = match.Groups[1].Value; var realPath = Path.Combine(origDir.FullName, relPath); var relativeFromTarget = GetRelativePathIfPossible(realPath, targetDir); var updatedImportProj = match.Value.Replace(relPath, relativeFromTarget); targetFiltersFileContents = targetFiltersFileContents.Replace(match.Value, updatedImportProj); } } } // We're done with the .vcxproj-file => save it for good. File.WriteAllText(targetProjFile.FullName, targetProjFileContents); Messages.Add(MessageViewModel.CreateSuccess($"Successfully altered the '{targetProjFile.Name}' file.")); // We're done with the .vcxproj.filters-file => save it for good. File.WriteAllText(targetFiltersFile.FullName, targetFiltersFileContents); Messages.Add(MessageViewModel.CreateSuccess($"Successfully altered the '{targetFiltersFile.Name}' file.")); // 4. IF we should NOT also modify the .sln file => exit if (!DoModifySln) { Messages.Add(MessageViewModel.CreateWarning("Adding the project to a solution-file was not requested (checkbox unticked) => exiting.")); return; } // 5. Parse the .sln file and modify it... var slnFile = new FileInfo(PathToSln); var slnFileContents = File.ReadAllText(slnFile.FullName); // 5.1 Duplicate the lines from "Project" to "EndProject" which contains the original project's GUID, // change the name to the new project's name, change the path to the new project's path, change the GUID to the new project's GUID { var slnProjectMatches = RegexSlnProject.Matches(slnFileContents); foreach (Match match in slnProjectMatches) { var matchGuid = match.Groups[3].Value; if (string.Compare(matchGuid, oldGuidString, true) != 0) { continue; // not the droids, we're looking for } // Start building the new project's entry into the .sln file var newProjSb = new StringBuilder(slnFileContents.Substring(match.Groups[0].Index, match.Groups[1].Index - match.Groups[0].Index)); // A small helper function for adding the parts between two groups of a regex-match void AppendNonMatchedPartBetweenGroups(int groupIndexBefore, int?groupIndexAfter) { var startIndex = match.Groups[groupIndexBefore].Index + match.Groups[groupIndexBefore].Length; if (groupIndexAfter.HasValue) { newProjSb.Append(slnFileContents.Substring(startIndex, match.Groups[groupIndexAfter.Value].Index - startIndex)); } else { newProjSb.Append(slnFileContents.Substring(startIndex, match.Groups[0].Index + match.Groups[0].Length - startIndex)); } } newProjSb.Append(newProjName); AppendNonMatchedPartBetweenGroups(1, 2); newProjSb.Append(GetRelativePathIfPossible(targetProjFile.FullName, slnFile.Directory)); AppendNonMatchedPartBetweenGroups(2, 3); newProjSb.Append(newGuidString); AppendNonMatchedPartBetweenGroups(3, null); // till the end // got it => insert it as last element var indexToInsert = slnProjectMatches[slnProjectMatches.Count - 1].Index + slnProjectMatches[slnProjectMatches.Count - 1].Length; slnFileContents = slnFileContents.Insert(indexToInsert, newProjSb.ToString()); } } // 5.2 Duplicate all the lines between "GlobalSection(ProjectConfigurationPlatforms)" and "EndGlobalSection" which contain the original project's GUID // Insert them just above the "EndGlobalSection"-line. { var slnGlobalSection = RegexSlnGlobalSectionPost.Match(slnFileContents); if (!slnGlobalSection.Success) { Messages.Add(MessageViewModel.CreateError("Couldn't find the 'GlobalSection(ProjectConfigurationPlatforms) = postSolution' section in the .sln")); return; } var slnGlobalSectionContent = slnGlobalSection.Groups[1].Value; var projectEntries = RegexSlnProjectConfigEntry.Matches(slnGlobalSectionContent); var newProjEntriesSb = new StringBuilder(); foreach (Match match in projectEntries) { if (string.Compare(match.Groups[1].Value, oldGuidString, true) == 0) { // These ARE the droids, we're looking for newProjEntriesSb.Append(match.Value.Replace(match.Groups[1].Value, newGuidString)); } } // Insert the copied entries: slnFileContents = slnFileContents.Insert(slnGlobalSection.Groups[1].Index + slnGlobalSection.Groups[1].Length, newProjEntriesSb.ToString()); } // Done and done! File.WriteAllText(slnFile.FullName, slnFileContents); Messages.Add(MessageViewModel.CreateSuccess($"Successfully altered the '{slnFile.Name}' file.")); } catch (Exception ex) { Messages.Add(MessageViewModel.CreateError(ex.ToString())); } }); }