I'm writing a Visual Studio extension and I would like to change the hint path of an assembly reference of a C#-project without to trigger the "File Modification Detected"-dialog.
<Reference Include="SomeAssembly">
<HintPath>C:\ChangeMe\SomeAssembly.dll</HintPath>
</Reference>
But in the VSLangProj110.Reference5-interface I can't find any property that I can use. (Accessed through VSLangProj140.VSProject3.References)
Microsoft.Build.BuildEngine.Project is outdated. Here is an updated working solution.
foreach (var dteProject in dte.Solution.Projects.OfType<Project>())
{
// You can edit the project through an object of Microsoft.Build.Evaluation.Project
var buildProject = ProjectCollection.GlobalProjectCollection.GetLoadedProjects(dteProject.FullName).First();
foreach (var item in buildProject.Items.Where(obj => obj.ItemType == "Reference"))
{
var newPath = SomeMethod(item.GetMetadata("HintPath"));
item.SetMetadataValue("HintPath", newPath);
}
// But you have to save through an object of EnvDTE.Project
dteProject.Save();
}
I create a demo and reproduce your issue on my side. I think it is a by design issue, if you modify the project outside the environment, it will popup the "File Modification Detected" dialog, we need to change it by manually.
you could post a feedback on the following link: https://connect.microsoft.com/VisualStudio/Feedback
Update:
DTE2 dte = (DTE2)this.ServiceProvider.GetService(typeof(DTE));
EnvDTE.Project currentProject = dte.Solution.Projects.Item(1);
// Create a new Project object.
Microsoft.Build.BuildEngine.Project project = new Microsoft.Build.BuildEngine.Project();
project.Load(currentProject.FullName);
foreach (BuildItemGroup ig in project.ItemGroups)
{
//var items = ig.ToArray();
foreach (BuildItem item in ig.ToArray())
{
if (item.Include == "ClassLibrary1")
{
item.Include = "Utils";
item.SetMetadata("HintPath", #"C:\relativePath\Utils.dll");
}
}
}
project.Save(currentProject.FullName);
Related
I am successfully getting dependencies between projects with Roslyn, and now I would like to get dependencies between classes, similar to the Code Map feature in Visual Studio Enterprise.
Here is my code, the "?????" part is where I imagine I could get something. I am very new to the Roslyn API, though, and I don't know how to proceed from there on.
Solution solution = MSBuildWorkspace.Create()
.OpenSolutionAsync(Path.Combine(repoRootFolder, "MySolution.sln"))
.Result;
ProjectDependencyGraph projdeps = solution.GetProjectDependencyGraph();
Digraph graph = new Digraph();
foreach (ProjectId projectId in projdeps.GetTopologicallySortedProjects())
{
string projName = solution.GetProject(projectId).Name;
var projDeps = projdeps.GetProjectsThatThisProjectDirectlyDependsOn(projectId);
foreach (ProjectId depId in projDeps)
{
Project dep = solution.GetProject(depId);
Compilation compilation = dep.GetCompilationAsync().Result;
foreach (var syntree in compilation.SyntaxTrees)
{
foreach (var classNode in syntree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>())
{
var classesThisClassNodeReferences = ?????????
}
}
string depName = dep.Name;
graph.Dependencies.Add(new Dependency
{
Source = projName,
Target = depName
});
}
}
I'm not sure about your requirements, but you can probably go for checking all descendant SyntaxNodes of the class and get the corresponding symbol, and it's type, and then collect these types. Something like the following:
var semantic = compilation.GetSemanticModel(syntree);
var typesForCurrentClass = classNode.DescendantNodes().Select(n =>
semantic.GetTypeInfo(n).Type);
Note that there can be multiple typesForCurrentClass for a given class symbol because of partial classes.
I'm doing a project Template. there is some custom parameters ( The services and attributes that he will be using during the implementation of the project ). Each service needs a specific reference. So depending on the custom parameters, I prepare a list that contains the paths of the needed assemblies. How can I add them to the project ?! I tried the following code but no result.`
var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(#"path").Result;
var projects = solution.Projects;
foreach (EnvDTE.Project proj in solution.Projects)
{
if (proj.Name == projectName)
{
VSLangProj.VSProject vsproj = (VSLangProj.VSProject)proj.Object;
foreach (string dll in Wizard.View.View.refs)
{
vsproj.References.Add(dll);
}
}
}`
I'm working on a code report project.
Currently, I'm able to compile the solution projects, get the diagnostics related to the compilation, etc..
The problem appears when I try to load my custom IDiagnosticAnalyzers, I've tried to use the AnalyzerFileReference and the AnalyzerImageReference without any result, Always I access the projects.Analizers are empty.
var inmutableArray = (new List<IDiagnosticAnalyzer>
{
new VariableEndedWithIdNamedCorrectlyDiagnosticAnalyzer()
}).ToImmutableArray();
var analyzerImageReference = new AnalyzerImageReference(inmutableArray);
foreach (Project project in solution.Projects)
{
project.AddAnalyzerReference(analyzerImageReference );
//No analizers loaded....
}
UPDATE (thanks for the feedback [Josh Varty])
I've tried this two ways:
var newProjects = new List<Project>();
foreach (Project project in solution.Projects)
{
var newSolutionn= solution.AddAnalyzerReference(project.Id, analyzerImageReference);
newProjects.Add(newSolutionn.Projects.FirstOrDefault(p=> p.Id == project.Id));
}
foreach (Project project in solution.Projects)
{
var newProject = project.AddAnalyzerReference( analyzerImageReference);
}
In both cases have the analyzers loaded but when I get the compilation and I get the diagnostics, I don't get the output related to this analyzers (I think they are not being called at the get compilation function).
var compilation = newProject.GetCompilationAsync().Result;
var diagnostics = compilation.GetDiagnostics();
Any suggestions?
As I commented, most Roslyn objects are immutable. This means methods like AddAnalyzerReference() don't mutate the project, but instead return a new one.
I don't have an analyzer to test this, but I believe you can use the following. Note that I'm using Solution.AddAnalyzerReference() instead of the one you were using.
var inmutableArray =(new List<IDiagnosticAnalyzer>
{
new VariableEndedWithIdNamedCorrectlyDiagnosticAnalyzer()
}).ToImmutableArray();
var analyzerImageReference = new AnalyzerImageReference(inmutableArray);
Solution newSolution = solution;
//We iterate over the original solution
foreach (Project project in solution.Projects)
{
//But we save our work in the newSolution
newSolution = newSolution.AddAnalyzerReference(project.Id, analyzerImageReference);
}
//Now newSolution should contain all your changes.
//Maybe you want to save this reference?
solution = newSolution;
I've found the way to do it:
public static Task<ImmutableArray<Diagnostic>> GetDiagnosticsAsync(this Compilation compilation, ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerOptions options, CancellationToken cancellationToken = default(CancellationToken))
{
options = options ?? new AnalyzerOptions(ImmutableArray<AdditionalStream>.Empty, ImmutableDictionary<string, string>.Empty);
Compilation newCompilation = null;
var analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, options, out newCompilation, cancellationToken);
newCompilation.GetDiagnostics(cancellationToken);
return analyzerDriver.GetDiagnosticsAsync();
}
I've published a version of the open source project that I've been working using Roslyn, you can see the code and other thing related to analyzers and codefix.
https://bitbucket.org/jrierapeiro/codeanalyzer
I had similar question which i answered over here.
You have to use compilation.WithAnalyzer(analyzer) and then getDiagnostics()
I´m using C# Microsoft.Build.Evaluation.Project class to retrieve specific information from a VS project file like the TargetFramework, AssemblyName...etc. But I'm unable to find the way or the property name to identify if the project is a Web Project. For a VS Project in framework 1.1 I'm using Xml parsing like this.
string path = "C:\\Test.csproj";
XElement root = XElement.Load(path);
var type = from el in root.Elements("CSHARP")
select el.Attribute("ProjectType");
if (type.Count() > 0)
{
// Local or Web
return((XAttribute)type.First()).Value);
}
I'll appreciate if someone could give me the name of the property or another suggestion to solve this problem.
Thanks in advance.
My full method code
public bool IsWeb(string path)
{
bool isWeb = false;
try
{
Project project = new Project(path);
// I dont know the name of the propety to identify if its a web project
//string type = project.GetPropertyValue();
//if (type.Equals("Web"))
//{
// isWeb = true;
//}
}
catch (InvalidProjectFileException)
{
//It's fw 1.1
XElement root = XElement.Load(path);
var type = from el in root.Elements("CSHARP")
select el.Attribute("ProjectType");
if (type.Count() > 0)
{
if ((((XAttribute)type.First()).Value).Equals("Web"))
{
isWeb = true;
}
}
}
return isWeb;
}
I went through a few project files. In one of my mvc projects, I found this node in the PropertyGroup node <MvcBuildViews> it's definitely not an end-all answer to your question but it looks like more information than you were working with.
You can check if project type guid belongs to one of the known types.
I have an MSI file built from my C# Visual Studio 2010. The version is set through the Version property. I wanted to know if there is a way to determine the version without having to install the file. Currently when right click and view the properties it isn't displayed.
The following code may be helpful. But remember that you should first add a COM reference to the Microsoft Windows Installer Object Library and add the WindowsInstaller namespace to your code. The following function may be what you need.
public static string GetMsiInfo( string msiPath, string Info)
{
string retVal = string.Empty;
Type classType = Type.GetTypeFromProgID( “WindowsInstaller.Installer” );
Object installerObj = Activator.CreateInstance( classType );
Installer installer = installerObj as Installer;
// Open msi file
Database db = installer.OpenDatabase( msiPath, 0 );
// Fetch the property
string sql = String.Format(“SELECT Value FROM Property WHERE Property=’{0}’”, Info);
View view = db.OpenView( sql );
view.Execute( null );
// Read in the record
Record rec = view.Fetch();
if ( rec != null )
retVal = rec.get_StringData( 1 );
return retVal;
}
If you need the version, pass in the name of the MSI file you want, e.g.
string version = GetMsiInfo( "d:\product.msi", “ProductVersion” );
Yes - I think you need to inspect the MSI database however, which requires either some API calls or a wrapper utility.
Microsofts ORCA application should let you do this (although I've never tried it myself).
Instead of using the COM library, you can use the Microsoft.Deployment.WindowsInstaller library from the wixtoolset SDK. Once referenced, you can very similarly get the version info.
private string GetMsiInfo(string msiPath)
{
using (var database = new Microsoft.Deployment.WindowsInstaller.Database(msiPath))
{
var sql = "SELECT Value FROM Property WHERE Property ='ProductVersion'";
using (var view = database.OpenView(sql))
{
view.Execute();
using (var record = view.Fetch())
{
var version = record?.GetString(1);
return version;
}
}
}
}
I haven't found a way to get the correct assembly via nuget installer. However, after I installed the wixtoolset https://wixtoolset.org/releases/, I was able to add a reference in my project directly under assemblies -> extensions -> Microsoft.Deployment.WindowsInstaller.
Based on Gupta's answer, I added COM release calls. If you want to recreate or replace the file you accessed in your further workflow, it might be still in use and you will get an exception if the GC did not yet release the objects, so let's do this manually.
public static string GetMsiInfo(string msiPath, string info)
{
string retVal = string.Empty;
Type classType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
dynamic installer = Activator.CreateInstance(classType);
try
{
// Open msi file
var db = installer.OpenDatabase(msiPath, 0);
try
{
// Fetch the property
string sql = $"SELECT Value FROM Property WHERE Property ='{info}'";
var view = db.OpenView(sql);
try
{
view.Execute(null);
// Read in the record
var rec = view.Fetch();
if (rec != null)
retVal = rec.StringData(1);
return retVal;
}
finally
{
view.Close();
Marshal.ReleaseComObject(view);
}
}
finally
{
//db.Commit();
Marshal.ReleaseComObject(db);
}
}
finally
{
Marshal.ReleaseComObject(installer);
}
}
I think using this code, there is no need to add a COM reference or an extra namespace as mentioned by Gupta, because we use late binding here (see the dynamic).