diff --git a/src/ConsoleApp/App.config b/src/ConsoleApp/App.config
new file mode 100644
index 0000000..8324aa6
--- /dev/null
+++ b/src/ConsoleApp/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ConsoleApp/AssemblyReferenceRegularExpressions.cs b/src/ConsoleApp/AssemblyReferenceRegularExpressions.cs
new file mode 100644
index 0000000..b0d699c
--- /dev/null
+++ b/src/ConsoleApp/AssemblyReferenceRegularExpressions.cs
@@ -0,0 +1,13 @@
+using NuGet.Packaging.Core;
+using System.Text.RegularExpressions;
+
+namespace ConsoleApp
+{
+ internal class AssemblyReferenceRegularExpressions : RegularExpressionsForPackagesBase
+ {
+ protected override Regex GetRegularExpression(PackageIdentity packageIdentity)
+ {
+ return new Regex($@".*{Regex.Escape(packageIdentity.Id)}\.{Regex.Escape(packageIdentity.Version.ToString())}\\.+", RegexOptions.IgnoreCase);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ConsoleApp/ConsoleApp.csproj b/src/ConsoleApp/ConsoleApp.csproj
new file mode 100644
index 0000000..2cb0be6
--- /dev/null
+++ b/src/ConsoleApp/ConsoleApp.csproj
@@ -0,0 +1,68 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {1FE13D69-D7BE-44D9-96ED-190E7D379704}
+ Exe
+ ConsoleApp
+ PackagesConfigProjectConverter
+ v4.6
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 15.3.409
+
+
+ 1.1.0
+
+
+ 4.4.0
+
+
+
+
\ No newline at end of file
diff --git a/src/ConsoleApp/ExtensionsMethods.cs b/src/ConsoleApp/ExtensionsMethods.cs
new file mode 100644
index 0000000..adc7d69
--- /dev/null
+++ b/src/ConsoleApp/ExtensionsMethods.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ConsoleApp
+{
+ internal static class ExtensionsMethods
+ {
+ private static readonly char[] ArgumentValueSeparators = { ';', ',' };
+ private static readonly char[] ArgumentNameSeparators = { ':' };
+
+ public static IEnumerable GetCommandLineArgumentValues(this string[] args, params string[] argumentNames)
+ {
+ foreach (string arg in args.Where(i => argumentNames.Any(x => i.StartsWith(x, StringComparison.OrdinalIgnoreCase))).Select(i => i.Split(ArgumentNameSeparators, 2).LastOrDefault()).Where(y => !String.IsNullOrWhiteSpace(y)))
+ {
+ foreach (string item in arg.Split(ArgumentValueSeparators, StringSplitOptions.RemoveEmptyEntries).Select(i => i.Trim()).Where(i => !String.IsNullOrWhiteSpace(i)))
+ {
+ yield return item;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ConsoleApp/ImportRegularExpressions.cs b/src/ConsoleApp/ImportRegularExpressions.cs
new file mode 100644
index 0000000..1702775
--- /dev/null
+++ b/src/ConsoleApp/ImportRegularExpressions.cs
@@ -0,0 +1,13 @@
+using System.Text.RegularExpressions;
+using NuGet.Packaging.Core;
+
+namespace ConsoleApp
+{
+ internal class ImportRegularExpressions : RegularExpressionsForPackagesBase
+ {
+ protected override Regex GetRegularExpression(PackageIdentity packageIdentity)
+ {
+ return new Regex($@".*{Regex.Escape(packageIdentity.Id)}\.{Regex.Escape(packageIdentity.Version.ToString())}.+{Regex.Escape(packageIdentity.Id)}\.(props|targets)", RegexOptions.IgnoreCase);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ConsoleApp/Program.cs b/src/ConsoleApp/Program.cs
new file mode 100644
index 0000000..8a4c79f
--- /dev/null
+++ b/src/ConsoleApp/Program.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+
+namespace ConsoleApp
+{
+ internal class Program
+ {
+ // @"D:\Truman2\private\Libraries\ServiceClient\public\sample"
+ private static int Main(string[] args)
+ {
+ try
+ {
+ if (args.Any(i => i.Equals("/debug", StringComparison.OrdinalIgnoreCase)))
+ {
+ Debugger.Break();
+ }
+
+ if (args.Any(i => i.Equals("/?", StringComparison.OrdinalIgnoreCase)))
+ {
+ return PrintUsage();
+ }
+
+ bool quiet = args.Any(i => i.StartsWith("/q", StringComparison.OrdinalIgnoreCase));
+
+ string repositoryPath = args.SingleOrDefault(i => i[0] != '/');
+
+ if (String.IsNullOrWhiteSpace(repositoryPath))
+ {
+ return PrintUsage("You must specify a repository path");
+ }
+
+ List exclusions = args.GetCommandLineArgumentValues("/e", "exclude").ToList();
+
+ Console.WriteLine($" EnlistmentRoot: '{repositoryPath}'");
+ Console.WriteLine($" Exclusion: '{String.Join($"{Environment.NewLine} Exclusion: '", exclusions)}'");
+ Console.WriteLine();
+
+ if (!quiet)
+ {
+ Console.Write("Ensure there are no files checked out in git before continuing! Continue? (Y/N) ");
+ if (!Console.ReadLine().StartsWith("Y", StringComparison.OrdinalIgnoreCase))
+ {
+ return 0;
+ }
+ }
+
+ using (ProjectConverter projectConverter = new ProjectConverter())
+ {
+ Console.WriteLine("Converting...");
+
+ projectConverter.ConvertRepository(repositoryPath, exclusions);
+
+ Console.WriteLine("Success!");
+ }
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e.ToString());
+
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private static int PrintUsage(string errorMessage = null)
+ {
+ if (!String.IsNullOrWhiteSpace(errorMessage))
+ {
+ Console.Error.WriteLine(errorMessage);
+ }
+
+ Console.WriteLine("Converts a repository from packages.config to PackageReference.");
+ Console.WriteLine();
+
+ Console.WriteLine($"{Assembly.GetExecutingAssembly().GetName().Name}.exe repositoryPath [/quiet] [/exclude:path1;path2]");
+ Console.WriteLine();
+ Console.WriteLine(" Repository Full path to the repository root to convert");
+ Console.WriteLine();
+ Console.WriteLine(" /quiet Do not prompt before converting the tree");
+ Console.WriteLine();
+ Console.WriteLine(" /exclude One or more full paths to any directories to exclude");
+ Console.WriteLine();
+ Console.WriteLine(" Example:");
+ Console.WriteLine();
+ Console.WriteLine(" /exclude:\"D:\\RepoA\\src\\ProjectX\"");
+
+ Console.WriteLine();
+ Console.WriteLine(" /debug Launch the debugger before executing");
+
+ return String.IsNullOrWhiteSpace(errorMessage) ? 0 : 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ConsoleApp/ProjectConverter.cs b/src/ConsoleApp/ProjectConverter.cs
new file mode 100644
index 0000000..77d6160
--- /dev/null
+++ b/src/ConsoleApp/ProjectConverter.cs
@@ -0,0 +1,230 @@
+using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+
+namespace ConsoleApp
+{
+ internal sealed class ProjectConverter : IDisposable
+ {
+ // ReSharper disable once CollectionNeverUpdated.Local
+ private static readonly AssemblyReferenceRegularExpressions AssemblyReferenceRegularExpressions = new AssemblyReferenceRegularExpressions();
+
+ // ReSharper disable once CollectionNeverUpdated.Local
+ private static readonly ImportRegularExpressions ImportRegularExpressions = new ImportRegularExpressions();
+
+ private static readonly string[] ItemsToRemove = {"packages.config"};
+ private static readonly string[] ItemTypesToRemove = {"Analyzer"};
+ private static readonly string[] PropertiesToRemove = {"NuGetPackageImportStamp"};
+ private readonly ProjectCollection _projectCollection;
+
+ public ProjectConverter()
+ : this(new ProjectCollection())
+ {
+ }
+
+ public ProjectConverter(ProjectCollection projectCollection)
+ {
+ _projectCollection = projectCollection ?? throw new ArgumentNullException(nameof(projectCollection));
+ }
+
+ public void ConvertProject(string projectPath)
+ {
+ string packagesConfigPath = Path.Combine(Path.GetDirectoryName(projectPath), "packages.config");
+
+ if (!File.Exists(packagesConfigPath))
+ {
+ return;
+ }
+
+ PackagesConfigReader packagesConfigReader = new PackagesConfigReader(XDocument.Load(packagesConfigPath));
+
+ List packages = packagesConfigReader.GetPackages(allowDuplicatePackageIds: true).Select(i => i.PackageIdentity).ToList();
+
+ ProjectRootElement project = ProjectRootElement.Open(projectPath, _projectCollection, preserveFormatting: true);
+
+ try
+ {
+ RemoveImports(project, packages);
+
+ RemoveTargets(project);
+
+ RemoveProperties(project);
+
+ RemoveItems(project);
+
+ ReplaceReferences(project, packages);
+
+ project.Save();
+
+ File.Delete(packagesConfigPath);
+ }
+ catch (Exception)
+ {
+ Console.WriteLine($"Failed to convert '{projectPath}'");
+ }
+ }
+
+ public void ConvertRepository(string repositoryPath, IEnumerable exclusions = null)
+ {
+ HashSet exlustionsHashSet = new HashSet(exclusions ?? Enumerable.Empty());
+
+ foreach (string file in Directory.EnumerateFiles(repositoryPath, "*.csproj", SearchOption.AllDirectories).Where(i => !exlustionsHashSet.Any(e => i.StartsWith(e, StringComparison.OrdinalIgnoreCase))))
+ {
+ ConvertProject(file);
+ }
+ }
+
+ public void Dispose()
+ {
+ _projectCollection?.Dispose();
+ }
+
+ private void RemoveImports(ProjectRootElement project, List packages)
+ {
+ var importsToRemove = new List();
+
+ foreach (ProjectImportElement importElement in project.Imports)
+ {
+ foreach (PackageIdentity package in packages)
+ {
+ Regex regex = ImportRegularExpressions[package];
+
+ if (regex.IsMatch(importElement.Project))
+ {
+ importsToRemove.Add(importElement);
+ }
+ }
+ }
+
+ foreach (ProjectImportElement projectImportElement in importsToRemove)
+ {
+ projectImportElement.Parent.RemoveChild(projectImportElement);
+ }
+ }
+
+ private void RemoveItems(ProjectRootElement project)
+ {
+ foreach (string itemSpec in ItemsToRemove)
+ {
+ foreach (ProjectItemElement itemElement in project.Items.Where(i => i.Include.Equals(itemSpec, StringComparison.OrdinalIgnoreCase)).ToList())
+ {
+ if (itemElement.Parent.Count == 1)
+ {
+ itemElement.Parent.Parent.RemoveChild(itemElement.Parent);
+ }
+ else
+ {
+ itemElement.Parent.RemoveChild(itemElement);
+ }
+ }
+ }
+
+ foreach (string itemType in ItemTypesToRemove)
+ {
+ foreach (ProjectItemElement itemElement in project.Items.Where(i => i.ItemType.Equals(itemType, StringComparison.OrdinalIgnoreCase)).ToList())
+ {
+ if (itemElement.Parent.Count == 1)
+ {
+ itemElement.Parent.Parent.RemoveChild(itemElement.Parent);
+ }
+ else
+ {
+ itemElement.Parent.RemoveChild(itemElement);
+ }
+ }
+ }
+ }
+
+ private void RemoveProperties(ProjectRootElement project)
+ {
+ foreach (string propertyName in PropertiesToRemove)
+ {
+ foreach (ProjectPropertyElement propertyElement in project.Properties.Where(i => i.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)).ToList())
+ {
+ propertyElement.Parent.RemoveChild(propertyElement);
+ }
+ }
+ }
+
+ private void RemoveTargets(ProjectRootElement project)
+ {
+ foreach (ProjectTargetElement targetElement in project.Targets.Where(i => i.Name.Equals("EnsureNuGetPackageBuildImports", StringComparison.OrdinalIgnoreCase)).ToList())
+ {
+ targetElement.Parent.RemoveChild(targetElement);
+ }
+ }
+
+ private void ReplaceReferences(ProjectRootElement project, List packages)
+ {
+ HashSet allPackages = new HashSet(packages);
+
+ Dictionary itemsToReplace = new Dictionary();
+
+ foreach (ProjectItemElement itemElement in project.Items.Where(i => i.ItemType.Equals("Reference")))
+ {
+ foreach (PackageIdentity packageIdentity in packages)
+ {
+ Regex regex = AssemblyReferenceRegularExpressions[packageIdentity];
+
+ ProjectMetadataElement metadatum = itemElement.Metadata.FirstOrDefault(i => i.Name.Equals("HintPath"));
+
+ if (metadatum != null && regex.IsMatch(metadatum.Value))
+ {
+ itemsToReplace.Add(itemElement, packageIdentity);
+
+ allPackages.Remove(packageIdentity);
+ }
+ }
+ }
+
+ List packagesAdded = new List();
+
+ ProjectItemElement lastItem = project.Items.First(i => i.ItemType.Equals("Reference")) ?? project.ItemGroups.First().Items.First();
+
+ foreach (KeyValuePair pair in itemsToReplace)
+ {
+ if (!packagesAdded.Contains(pair.Value))
+ {
+ ProjectItemElement item = project.CreateItemElement("PackageReference", pair.Value.Id);
+
+ pair.Key.Parent.InsertAfterChild(item, pair.Key);
+
+ item.AddMetadata("Version", pair.Value.Version.ToString());
+
+ packagesAdded.Add(pair.Value);
+
+ lastItem = item;
+ }
+
+ pair.Key.Parent.RemoveChild(pair.Key);
+ }
+
+ foreach (PackageIdentity package in allPackages)
+ {
+ if (lastItem == null)
+ {
+ var itemGroup = project.AddItemGroup();
+
+ lastItem = itemGroup.AddItem("PackageReference", package.Id, new List> {new KeyValuePair("Version", package.Version.ToString())});
+ }
+ else
+ {
+ ProjectItemElement item = project.CreateItemElement("PackageReference", package.Id);
+
+ lastItem.Parent.InsertAfterChild(item, lastItem);
+
+ item.AddMetadata("Version", package.Version.ToString());
+
+ lastItem = item;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ConsoleApp/Properties/AssemblyInfo.cs b/src/ConsoleApp/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2fb0dab
--- /dev/null
+++ b/src/ConsoleApp/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ConsoleApp")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ConsoleApp")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1fe13d69-d7be-44d9-96ed-190e7d379704")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/ConsoleApp/RegularExpressionsForPackagesBase.cs b/src/ConsoleApp/RegularExpressionsForPackagesBase.cs
new file mode 100644
index 0000000..50fd80e
--- /dev/null
+++ b/src/ConsoleApp/RegularExpressionsForPackagesBase.cs
@@ -0,0 +1,92 @@
+using NuGet.Packaging.Core;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace ConsoleApp
+{
+ internal abstract class RegularExpressionsForPackagesBase : IDictionary
+ {
+ private readonly Dictionary _regexes = new Dictionary();
+
+ public int Count => _regexes.Count;
+
+ public bool IsReadOnly => false;
+
+ public ICollection Keys => _regexes.Keys;
+
+ public ICollection Values => _regexes.Values;
+
+ public Regex this[PackageIdentity key]
+ {
+ get
+ {
+ if (!_regexes.ContainsKey(key))
+ {
+ _regexes.Add(key, GetRegularExpression(key));
+ }
+
+ return _regexes[key];
+ }
+ set => _regexes[key] = value;
+ }
+
+ public void Add(KeyValuePair item)
+ {
+ _regexes.Add(item.Key, item.Value);
+ }
+
+ public void Add(PackageIdentity key, Regex value)
+ {
+ _regexes.Add(key, value);
+ }
+
+ public void Clear()
+ {
+ _regexes.Clear();
+ }
+
+ public bool Contains(KeyValuePair item)
+ {
+ return _regexes.ContainsKey(item.Key);
+ }
+
+ public bool ContainsKey(PackageIdentity key)
+ {
+ return _regexes.ContainsKey(key);
+ }
+
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return _regexes.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool Remove(PackageIdentity key)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool TryGetValue(PackageIdentity key, out Regex value)
+ {
+ return _regexes.TryGetValue(key, out value);
+ }
+
+ protected abstract Regex GetRegularExpression(PackageIdentity packageIdentity);
+ }
+}
\ No newline at end of file
diff --git a/src/PackagesConfigConverter.sln b/src/PackagesConfigConverter.sln
new file mode 100644
index 0000000..61779dd
--- /dev/null
+++ b/src/PackagesConfigConverter.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27013.2
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{1FE13D69-D7BE-44D9-96ED-190E7D379704}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1FE13D69-D7BE-44D9-96ED-190E7D379704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1FE13D69-D7BE-44D9-96ED-190E7D379704}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1FE13D69-D7BE-44D9-96ED-190E7D379704}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1FE13D69-D7BE-44D9-96ED-190E7D379704}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {AD6227D3-772B-4734-82C6-ADA5639226B3}
+ EndGlobalSection
+EndGlobal