I am trying to work out how, in my Visual Studio Extension, I can work out which of the currently loaded projects are in TFS and then the TFS server uri and location for each of these projects.
I would like to be able to do something like this:
foreach (EnvDTE.Project project in dte.Solution.Projects)
{
if (project.IsInTFS) {
var uri = project.TFSUri; // http://tfsserver:8080/tfs
var location = project.TFSLocation; // $\TeamProject\Project
}
}
Can anyone help me?
Thank you.
You can use the SourceControl2 interface to accomplish this in the following way:
foreach (Project project in dte.Solution.Projects)
{
if (dte.SourceControl.IsItemUnderSCC(project.FileName))
{
var bindings = project.GetSourceControlBindings();
var uri = bindings.ServerName;
var location = bindings.ServerBinding;
// your logic goes here
}
}
Where GetSourceControlBindings is an extension method:
public static SourceControlBindings GetSourceControlBindings(this Project project)
{
SourceControl2 sourceControl = (SourceControl2)project.DTE.SourceControl;
return sourceControl.GetBindings(project.FullName);
}
Note: you will have to a add reference to the EnvDTE80.dll assembly.
If you're interested exclusively in TFS source control, then you should add one more condition:
if (bindings.ProviderName == "{4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}")
Where that magic guid specifies the Team Foundation Source Control Provider.
I'm trying to write a UnmatchedClassAndFilename diagnostic and code fix using the new Roslyn and Visual Studio API's. The idea is to rename a class or filename in case they aren't equal.
How can I use the Roslyn API to rename a file in Visual Studio? The Workspace class doesn't seem to support this.
Update: Created an issue at CodePlex (https://roslyn.codeplex.com/workitem/258)
No, there is no current support for this in the Workspaces API. It's a common request but I'm not sure we have something explicitly tracking that work, so feel free to file the bug on CodePlex.
I am using Visual Studio 2017 and the Code Refactoring VSIX project template to accomplish this.
Here is my code:
private async Task<Solution> ConvertTypeNameToPascalCaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
{
// Produce a PascalCased version of the type declaration's identifier token.
var identifierToken = typeDecl.Identifier;
var newName = identifierToken.Text.ToPascalCase();
// Get the symbol representing the type to be renamed.
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken);
// Produce a new solution that has all references to that type renamed, including the declaration.
var originalSolution = document.Project.Solution;
var optionSet = originalSolution.Workspace.Options;
var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
var newDocId = DocumentId.CreateNewId(document.Project.Id);
var newText = await newSolution.GetDocument(document.Id).GetTextAsync(cancellationToken).ConfigureAwait(false);
// rename document by adding a new document with the new name and removing the old document
newSolution = newSolution.AddAdditionalDocument(newDocId, newName + ".cs", newText);
newSolution = newSolution.RemoveDocument(document.Id);
// Return the new solution with the now PascalCased type name.
return newSolution;
}
Note: ToPascalCase() is an extension method that I added to the string class.
The major point to notice is that I used AddAdditionalDocument() and RemoveDocument() to effectively rename the existing document to match my new name.
Here is the code that sets up the Code Refactoring engine:
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
// Find the node at the selection.
var node = root.FindNode(context.Span);
// Only offer a refactoring if the selected node is a type declaration node.
var typeDecl = node as TypeDeclarationSyntax;
if (typeDecl == null)
{
return;
}
if (typeDecl.Identifier.Text.IsUpper())
{
// For any type declaration node, create a code action to reverse the identifier text.
var action = CodeAction.Create("Convert type name to PascalCase", c => ConvertTypeNameToPascalCaseAsync(context.Document, typeDecl, c));
// Register this code action.
context.RegisterRefactoring(action);
}
}
Note: IsUpper() is also an extension method that I added to the string class.
Incidentally, my specific use case is to convert all caps class names with underscores in them to PascalCased class names. Examples:
TEST = Test
TEST_CLASS = TestClass
TEST_A_CLASS = TestAClass
My specific problem is how can I automate "add-migration" in a build process for the Entity Framework. In researching this, it seems the mostly likely approach is something along the lines of automating these steps
Open a solution in Visual Studio 2013
Execute "Add-Migration blahblah" in the Package Manager Console (most likely via an add-in vsextention)
Close the solution
This initial approach is based on my own research and this question, the powershell script ultimately behind Add-Migration requires quite a bit of set-up to run. Visual Studio performs that setup automatically when creating the Package Manager Console and making the DTE object available. I would prefer not to attempt to duplicate that setup outside of Visual Studio.
One possible path to a solution is this unanswered stack overflow question
In researching the NuGet API, it does not appear to have a "send this text and it will be run like it was typed in the console". I am not clear on the lines between Visual Studio vs NuGet so I am not sure this is something that would be there.
I am able to find the "Pacakage Manager Console" ironically enough via "$dte.Windows" command in the Package Manager Console but in a VS 2013 window, that collection gives me objects which are "Microsoft.VisualStudio.Platform.WindowManagement.DTE.WindowBase". If there is a way stuff text into it, I think I need to get it to be a NuGetConsole.Implementation.PowerConsoleToolWindow" through reviewing the source code I am not clear how the text would stuffed but I am not at all familiar with what I am seeing.
Worst case, I will fall back to trying to stuff keys to it along the lines of this question but would prefer not to since that will substantially complicate the automation surrounding the build process.
All of that being said,
Is it possible to stream commands via code to the Package Manager Console in Visual Studio which is fully initialized and able to support an Entity Framework "add-migration" command?
Thanks for any suggestions, advice, help, non-abuse in advance,
John
The approach that worked for me was to trace into the entity framework code starting in with the AddMigrationCommand.cs in the EntityFramework.Powershell project and find the hooks into the EntityFramework project and then make those hooks work so there is no Powershell dependency.
You can get something like...
public static void RunIt(EnvDTE.Project project, Type dbContext, Assembly migrationAssembly, string migrationDirectory,
string migrationsNamespace, string contextKey, string migrationName)
{
DbMigrationsConfiguration migrationsConfiguration = new DbMigrationsConfiguration();
migrationsConfiguration.AutomaticMigrationDataLossAllowed = false;
migrationsConfiguration.AutomaticMigrationsEnabled = false;
migrationsConfiguration.CodeGenerator = new CSharpMigrationCodeGenerator(); //same as default
migrationsConfiguration.ContextType = dbContext; //data
migrationsConfiguration.ContextKey = contextKey;
migrationsConfiguration.MigrationsAssembly = migrationAssembly;
migrationsConfiguration.MigrationsDirectory = migrationDirectory;
migrationsConfiguration.MigrationsNamespace = migrationsNamespace;
System.Data.Entity.Infrastructure.DbConnectionInfo dbi = new System.Data.Entity.Infrastructure.DbConnectionInfo("DataContext");
migrationsConfiguration.TargetDatabase = dbi;
MigrationScaffolder ms = new MigrationScaffolder(migrationsConfiguration);
ScaffoldedMigration sf = ms.Scaffold(migrationName, false);
}
You can use this question to get to the dte object and from there to find the project object to pass into the call.
This is an update to John's answer whom I have to thank for the "hard part", but here is a complete example which creates a migration and adds that migration to the supplied project (project must be built before) the same way as Add-Migration InitialBase -IgnoreChanges would:
public void ScaffoldedMigration(EnvDTE.Project project)
{
var migrationsNamespace = project.Properties.Cast<Property>()
.First(p => p.Name == "RootNamespace").Value.ToString() + ".Migrations";
var assemblyName = project.Properties.Cast<Property>()
.First(p => p.Name == "AssemblyName").Value.ToString();
var rootPath = Path.GetDirectoryName(project.FullName);
var assemblyPath = Path.Combine(rootPath, "bin", assemblyName + ".dll");
var migrationAssembly = Assembly.Load(File.ReadAllBytes(assemblyPath));
Type dbContext = null;
foreach(var type in migrationAssembly.GetTypes())
{
if(type.IsSubclassOf(typeof(DbContext)))
{
dbContext = type;
break;
}
}
var migrationsConfiguration = new DbMigrationsConfiguration()
{
AutomaticMigrationDataLossAllowed = false,
AutomaticMigrationsEnabled = false,
CodeGenerator = new CSharpMigrationCodeGenerator(),
ContextType = dbContext,
ContextKey = migrationsNamespace + ".Configuration",
MigrationsAssembly = migrationAssembly,
MigrationsDirectory = "Migrations",
MigrationsNamespace = migrationsNamespace
};
var dbi = new System.Data.Entity.Infrastructure
.DbConnectionInfo("ConnectionString", "System.Data.SqlClient");
migrationsConfiguration.TargetDatabase = dbi;
var scaffolder = new MigrationScaffolder(migrationsConfiguration);
ScaffoldedMigration migration = scaffolder.Scaffold("InitialBase", true);
var migrationFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".cs");
File.WriteAllText(migrationFile, migration.UserCode);
var migrationItem = project.ProjectItems.AddFromFile(migrationFile);
var designerFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".Designer.cs");
File.WriteAllText(designerFile, migration.DesignerCode);
var designerItem = project.ProjectItems.AddFromFile(migrationFile);
foreach(Property prop in designerItem.Properties)
{
if (prop.Name == "DependentUpon")
prop.Value = Path.GetFileName(migrationFile);
}
var resxFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".resx");
using (ResXResourceWriter resx = new ResXResourceWriter(resxFile))
{
foreach (var kvp in migration.Resources)
resx.AddResource(kvp.Key, kvp.Value);
}
var resxItem = project.ProjectItems.AddFromFile(resxFile);
foreach (Property prop in resxItem.Properties)
{
if (prop.Name == "DependentUpon")
prop.Value = Path.GetFileName(migrationFile);
}
}
I execute this in my project template's IWizard implementation where I run a migration with IgnoreChanges, because of shared entites with the base project. Change scaffolder.Scaffold("InitialBase", true) to scaffolder.Scaffold("InitialBase", false) if you want to include the changes.
When the first time my app starts on a windows phone, I want to get some files(xml/images) from project folders and write them to the isolated storage .
How do I detect that my app is running for the first time?
How do I access file in project folders?
Here is another way to read files from your visual studio project. The following shows how to read a txt file but can be used for other file as well. Here the file is in the same directory as the .xaml.cs file.
var res = App.GetResourceStream(new Uri("test.txt", UriKind.Relative));
var txt = new StreamReader(res.Stream).ReadToEnd();
make sure your file is marked as Content.
If you mean project folders as in the folders in your visual studio project, I usually right click on the files and set the build action to 'Embedded Resource'. At runtime, you can read the data from the embedded resource like so:
// The resource name will correspond to the namespace and path in the file system.
// Have a look at the resources collection in the debugger to figure out the name.
string resourcePath = "assembly namespace" + "path inside project";
Assembly assembly = Assembly.GetExecutingAssembly();
string[] resources = assembly .GetManifestResourceNames();
List<string> files = new List<string>();
if (resource.StartsWith(resourcePath))
{
StreamReader reader = new StreamReader(assembly.GetManifestResourceStream(resource), Encoding.Default);
files.Add(reader.ReadToEnd());
}
To read the images you would need something like this to read the stream:
public static byte[] ReadAllBytes(Stream input)
{
byte[] buffer = new byte[32 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
I've checked out a branch of C# code from source control. It contains maybe 50 projects in various folders. There's no existing .sln file to be found.
I intended to create a blank solution to add existing solutions. The UI only lets me do this one project at a time.
Is there something I'm missing? I'd like to specify a list of *.csproj files and somehow come up with a .sln file that contains all the projects.
A PowerShell implementation that recursively scans the script directory for .csproj files and adds them to a (generated) All.sln:
$scriptDirectory = (Get-Item $MyInvocation.MyCommand.Path).Directory.FullName
$dteObj = [System.Activator]::CreateInstance([System.Type]::GetTypeFromProgId("VisualStudio.DTE.12.0"))
$slnDir = ".\"
$slnName = "All"
$dteObj.Solution.Create($scriptDirectory, $slnName)
(ls . -Recurse *.csproj) | % { $dteObj.Solution.AddFromFile($_.FullName, $false) }
$dteObj.Solution.SaveAs( (Join-Path $scriptDirectory 'All.sln') )
$dteObj.Quit()
A C# implementation that produces an executable, which creates a solution containing all unique *.csproj files from the directory and subdirectories it is executed in.
class Program
{
static void Main(string[] args)
{
using (var writer = new StreamWriter("All.sln", false, Encoding.UTF8))
{
writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 11.00");
writer.WriteLine("# Visual Studio 2010");
var seenElements = new HashSet<string>();
foreach (var file in (new DirectoryInfo(System.IO.Directory.GetCurrentDirectory())).GetFiles("*.csproj", SearchOption.AllDirectories))
{
string fileName = Path.GetFileNameWithoutExtension(file.Name);
if (seenElements.Add(fileName))
{
var guid = ReadGuid(file.FullName);
writer.WriteLine(string.Format(#"Project(""0"") = ""{0}"", ""{1}"",""{2}""", fileName, file.FullName, guid));
writer.WriteLine("EndProject");
}
}
}
}
static Guid ReadGuid(string fileName)
{
using (var file = File.OpenRead(fileName))
{
var elements = XElement.Load(XmlReader.Create(file));
return Guid.Parse(elements.Descendants().First(element => element.Name.LocalName == "ProjectGuid").Value);
}
}
}
There is extension for VS available, capable of adding all projects in selected directory (and more):
http://www.cyotek.com/blog/visual-studio-extension-for-adding-multiple-projects-to-a-solution
Use Visual Studio Extension "Add Existing Projects". It works with Visual Studio 2012, 2013, 2015, 2017.
To use the extension, open the Tools menu and choose Add Projects.
Plenty of answers here already, but none quite as clear as this powershell oneliner
Get-ChildItem -Recurse -Include *.csproj | ForEach-Object { dotnet sln add $_ }
You might be able to write a little PowerShell script or .NET app that parses all the projects' .csproj XML and extracts their details (ProjectGuid etc.) then adds them into the .sln file. It'd be quicker and less risky to add them all by hand, but an interesting challenge nonetheless.
Note: This is only for Visual Studio 2010
Found here is a cool add in for Visual Studio 2010 that gives you a PowerShell console in VS to let you interact with the IDE. Among many other things you can do using the built in VS extensibility as mentioned by #Avram, it would be pretty easy to add files or projects to a solution.
Here is a PowerShell version of Bertrand's script which assumes a Src and Test directory next to the solution file.
function GetGuidFromProject([string]$fileName) {
$content = Get-Content $fileName
$xml = [xml]$content
$obj = $xml.Project.PropertyGroup.ProjectGuid
return [Guid]$obj[0]
}
$slnPath = "C:\Project\Foo.sln"
$solutionDirectory = [System.IO.Path]::GetDirectoryName($slnPath)
$srcPath = [System.IO.Path]::GetDirectoryName($slnPath)
$writer = new-object System.IO.StreamWriter ($slnPath, $false, [System.Text.Encoding]::UTF8)
$writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 12.00")
$writer.WriteLine("# Visual Studio 2013")
$projects = gci $srcPath -Filter *.csproj -Recurse
foreach ($project in $projects) {
$fileName = [System.IO.Path]::GetFileNameWithoutExtension($project)
$guid = GetGuidFromProject $project.FullName
$slnRelativePath = $project.FullName.Replace($solutionDirectory, "").TrimStart("\")
# Assume the project is a C# project {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
$writer.WriteLine("Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""$fileName"", ""$slnRelativePath"",""{$($guid.ToString().ToUpper())}""")
$writer.WriteLine("EndProject")
}
$writer.Flush()
$writer.Close()
$writer.Dispose()
if you open the sln file with notepad you can see the format of the file which is easy to understand but for more info take a look # Hack the Project and Solution Files .understanding the structure of the solution files you can write an application which will open all project files and write the application name ,address and GUID to the sln file .
of course I think if it's just once you better do it manually
Every answer seems to flatten the directory structure (all the projects are added to the solution root, without respecting the folder hierarchy). So, I coded my own console app that generates the solution and uses solution folders to group them.
Check out the project in GitHub
Usage
SolutionGenerator.exe --folder C:\git\SomeSolutionRoot --output MySolutionFile.sln
when you have dotnet core installed, you can execute this from git bash:
donet new sln; find . -name "*.csproj" -exec dotnet sln add {} \;
the generated solution works with csproj created for old .NET Framework.
Depends on visual studio version.
But the name of this process is "Automation and Extensibility for Visual Studio"
http://msdn.microsoft.com/en-us/library/t51cz75w.aspx
Check this out:
http://nprove.codeplex.com/
It is a free addin for vs2010 that does that and more
if the projects are under the tfs
Building on Bertrand's answer at https://stackoverflow.com/a/16069782/492 - make a console app out of this and run it in the root folder where you want the VS 2015 Solution to appear. It works for C# & VB (hey! be nice).
It overwrites anything existing but you source control, right?
Check a recently used .SLN file to see what the first few writer.WriteLine() header lines should actually be by the time you read this.
Don't worry about the project type GUID Ptoject("0") - Visual Studio will work that out and write it in when you save the .sln file.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace AddAllProjectsToNewSolution
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("starting");
using (var writer = new StreamWriter("AllProjects.sln", false, Encoding.UTF8))
{
writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 14.00");
writer.WriteLine("# Visual Studio 14");
writer.WriteLine("VisualStudioVersion = 14.0.25420.1");
var seenElements = new HashSet<string>();
foreach (var file in (new DirectoryInfo(Directory.GetCurrentDirectory())).GetFiles("*.*proj", SearchOption.AllDirectories))
{
string extension = file.Extension;
if (extension != ".csproj" && extension != ".vbproj")
{
Console.WriteLine($"ignored {file.Name}");
continue;
}
Console.WriteLine($"adding {file.Name}");
string fileName = Path.GetFileNameWithoutExtension(file.Name);
if (seenElements.Add(fileName))
{
var guid = ReadGuid(file.FullName);
writer.WriteLine($"Project(\"0\") = \"{fileName}\", \"{GetRelativePath(file.FullName)} \", \"{{{guid}}}\"" );
writer.WriteLine("EndProject");
}
}
}
Console.WriteLine("Created AllProjects.sln. Any key to close");
Console.ReadLine();
}
static Guid ReadGuid(string fileName)
{
using (var file = File.OpenRead(fileName))
{
var elements = XElement.Load(XmlReader.Create(file));
return Guid.Parse(elements.Descendants().First(element => element.Name.LocalName == "ProjectGuid").Value);
}
}
// https://stackoverflow.com/a/703292/492
static string GetRelativePath(string filespec, string folder = null)
{
if (folder == null)
folder = Environment.CurrentDirectory;
Uri pathUri = new Uri(filespec);
// Folders must end in a slash
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString()))
folder += Path.DirectorySeparatorChar;
Uri folderUri = new Uri(folder);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}
}
}
Here is Bertrand's solution updated for
Microsoft Visual Studio Solution File, Format Version 12.00
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace AllSolutionGenerator
{
internal static class Program
{
private static void Main()
{
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
var projects = new Dictionary<string, string>();
var guid1 = Guid.NewGuid().ToString().ToUpperInvariant();
using (var writer = new StreamWriter("all.sln", false, Encoding.UTF8))
{
writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 12.00");
foreach (var file in dir.GetFiles("*.csproj", SearchOption.AllDirectories))
{
var fileName = Path.GetFileNameWithoutExtension(file.Name);
if (!projects.ContainsKey(fileName))
{
var guid = Guid.NewGuid().ToString().ToUpperInvariant();
projects.Add(fileName, guid);
writer.WriteLine(#$"Project(""{{{guid1}}}"") = ""{fileName}"", ""{file.FullName}"",""{guid}""");
writer.WriteLine("EndProject");
}
}
writer.WriteLine("Global");
writer.WriteLine(" GlobalSection(SolutionConfigurationPlatforms) = preSolution");
writer.WriteLine(" Debug|Any CPU = Debug|Any CPU");
writer.WriteLine(" Release|Any CPU = Release|Any CPU");
writer.WriteLine(" EndGlobalSection");
writer.WriteLine(" GlobalSection(ProjectConfigurationPlatforms) = postSolution");
foreach (var (_, guid) in projects)
{
writer.WriteLine(#$" {{{guid}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU");
writer.WriteLine(#$" {{{guid}}}.Debug|Any CPU.Build.0 = Debug|Any CPU");
}
writer.WriteLine(" EndGlobalSection");
writer.WriteLine("EndGlobal");
}
}
}
}
If you select 'Show all Files' in the Solution Explorer, you can than view all the files and folers and select them and right click to add them using 'Include in Project'.