How to get Custom Build Tool property from VCConfiguration - visual-studio

Currently working on making an extension for Visual Studio C++ when dealing with files that are built using a custom build tool. For the life of me I can't find a way to drill down into the item that's currently selected's property panel.
Scenario:
I have a test.myfile file in solution
It is set to be built with a custom build tool
You can imagine it looks like this
I currently have a command button in the item menu that executes as expected. I can get the selected items and from that grab the project items selected. I have the VCProjectItem and the VCConfiguration of the project tied to the file for the active configuration.
How can I get the "Command Line" property from the property page?

How can I get the "Command Line" property from the property page?
After l do a deep research, I found that we cannot use the property VCCustomBuildTool.CommandLine to get the custom file's property Command. It can be used to get the project's property(right-click on the project) not the specific file's property(right-click on the file).
Or another way to think about it, since the file is created with the node like CustomBuild, so we can obtain them in xxxx.vcxproj file.
We can use DTE interface to get the current proj file of the current project and then read the child node command in the parent node custom build.
For an example:
using EnvDTE;
using System.Xml;
.........
VCProject prj;
XmlDocument doc = new XmlDocument();
DTE dTE = Package.GetGlobalService(typeof(DTE)) as DTE;
prj = dTE.Solution.Projects.Item(1).Object;
doc.Load(prj.ProjectFile); //read the proj file for the current project
XmlElement root = doc.DocumentElement;
XmlNodeList nodeList = root.GetElementsByTagName("CustomBuild"); //get the customBuild Node
string str="";
foreach (XmlNode node in nodeList) //search the child node 'command' in the parent node named custombuild
{
str += node["Command"].InnerText.ToString()+"-----";
}
Please so not forget to reference the envdte.dll which exists in C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies.
Hope it could help you.

Related

visual studio extension (VSPackage) add items to Test Explorer context menu

I am writing an extension to Visual studio 2012 using VSPackage. I need to add a context menu entry to Test Explorer and on click of this menu item, I need to get the selected unit test(s). I tried to add an item using
((CommandBars)DTE.CommandBars)["Test Window Context Menu"].Controls.Add(Type: MsoControlType.msoControlButton);
and adding an event handler by subscribing to the event
DTE.Events.CommandBarEvents[command].Click
I succeeded in adding an item to Context menu but the Click event handler never gets fired. MSDN said, I needed to set the OnAction property of the command to a valid string value for the Click event handler to get fired. It didn't work either.
Then, I figured out I needed to add a command through the VSCT file in a VSPackage. However, I am not able to find the Test Window Context menu so that I can attach the command to it. Also, I need to get all the unit tests (TestCase objects) listed in the Test Explorer.
Any help is greatly appreciated!
Usually these are the files I look for Visual Studio shell GUIDs or command, context menu, group, etc IDs:
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VSSDK\VisualStudioIntegration\Common\Inc\stdidcmd.h
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VSSDK\VisualStudioIntegration\Common\Inc\vsshlids.h
Actually they are included in the top of your newly created .vsct file (<Extern href="vsshlids.h" />). I guess you've already checked them. I did a quick search, but what I found for "Test" is just a test ribbon and a test dialog. Probably now that you're looking for. It might be still useful for someone finding this post.
You might also want try it brute force style:
Search your Program Files (x86)\Visual Studio [VERSION] for regexp: ^#define.*TEST.*$
This shall give you the defines containing TEST.
Also you might want to consider asking Microsoft directly.
I wrote some exploratory code to loop over commands in that context menu. I also played around with registering a priority command target and seeing what group GUID and command ID I got. The GUID for that context menu appears to be {1e198c22-5980-4e7e-92f3-f73168d1fb63}. You can probably use that to add a command via the .vsct file without using the DTE.CommandBars to add it dynamically.
Here's my experiment code which lists the GUID and command ID of the commands currently in that context menu, in case it helps anyone.
var bars = ((Microsoft.VisualStudio.CommandBars.CommandBars)DTE.CommandBars);
var teContextMenu = bars["Test Window Context Menu"];
var ctls = teContextMenu.Controls;
foreach (var ctl in ctls)
{
var cmdCtl = ctl as Microsoft.VisualStudio.CommandBars.CommandBarControl;
string guid; int id;
DTE.Commands.CommandInfo(ctl, out guid, out id);
Debug.WriteLine($"{cmdCtl?.accName} {guid} {id}");
}
This article on command routing was helpful to me:
https://learn.microsoft.com/en-us/visualstudio/extensibility/internals/command-routing-algorithm
My experimental priority command target, where I set a breakpoint to see what GUID and command IDs were sent, is registered as follows. The TestCommandInterceptor class is a bare-bones implementation of IOleCommandTarget.
var cmdService = GetService(typeof(SVsRegisterPriorityCommandTarget)) as IVsRegisterPriorityCommandTarget;
var target = new TestCommandInterceptor();
cmdService.RegisterPriorityCommandTarget(0, target, out _testCmdInterceptorRegistrationCookie);
I would still like to know the answer to the second part of this question about how to determine the selected tests.

How do I programmatically refresh/reload a VS project after modifying the underlying file?

I am developing a Visual Studio package and I have written some code that will make a file in Solution Explorer dependant upon another file.
What this means is that it gives them the same relationship as code-behind files or designer files, where they appear nested under the parent file with a plus/minus icon.
+ MainForm.cs
- MainForm.cs
MainForm.Designer.cs
MainForm.resx
The code that I have written successfully and correctly modifies the underlying project file, however the change is not reflected in Solution Explorer until the project is closed and re-opened.
I'm looking for some code that will refresh or reload the project so that the change is visible in Solution Explorer immediately.
Further Information...
Here is the sudo code that demonstrates the mechanism by which I create the dependant file.
IVsBuildPropertyStorage vsBuildPropertyStorage = GetBuildPropertyStorage();
vsBuildPropertyStorage.SetItemAttribute(projectItemIdentifier, "DependentUpon", parentFileName);
I have also tried adding this in an attempt to get the project to reload, but it doesn't have any effect.
project.Save();
VSProject obj = project.Object as VSProject;
obj.Refresh();
AFAIK the only way of doing this is via automation of the Solution Explorer tool-window:
EnvDTE.DTE dte = ...;
string solutionName = Path.GetFileNameWithoutExtension(dte.Solution.FullName);
string projectName = project.Name;
dte.Windows.Item(EnvDTE.Constants.vsWindowKindSolutionExplorer).Activate();
((DTE2)dte).ToolWindows.SolutionExplorer.GetItem(solutionName + #"\" + projectName).Select(vsUISelectionType.vsUISelectionTypeSelect);
dte.ExecuteCommand("Project.UnloadProject");
dte.ExecuteCommand("Project.ReloadProject");
Note that, if the project hasn't been saved, the user will get a dialog box prior to the "Project.UnloadProject" call.
Here is my code (with reactivating the old window):
public void RefreshSolutionExplorer(EnvDTE.Project activeProject, string captionOfActiveWindow)
{
DTE2 dte2 = activeProject.DTE as DTE2;
string solutionName = Path.GetFileNameWithoutExtension(dte2.Solution.FullName);
string projectName = activeProject.Name;
// Activate SolutionExplorer window
dte2.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate();
// Select your project to be updated
dte2.ToolWindows.SolutionExplorer.GetItem(solutionName + #"\" + projectName).Select(vsUISelectionType.vsUISelectionTypeSelect);
// Refresh SolutionExplorer window
dte2.ExecuteCommand("View.Refresh", String.Empty);
// Reactivate your old window
dte2.Windows.Item(captionOfActiveWindow).Activate();
}

Visual Studio Build Event Macros - Solution Configuration Name

In my Post-build event I call a batch file and pass it the current build config.
C:\Dev\Project\Build\build.bat /$(Configuration)
This passes the Project configuration name to the build script.
Is there anyway to pass the current Solution configuration name?
I created a VS2010 extension for this, it lets you use $(SolutionConfiguration) and $(SolutionPlaform). $(BuildMacro) build macros. You can download the source and build it yourself from here.
Showing some code, it's just about registering to UpdateSolution_Begin method
of IVsUpdateSolutionEvents VS and setting some Environment.SetEnvironmentVariable() there.
_IVsSolutionBuildManager = (IVsSolutionBuildManager)GetService<SVsSolutionBuildManager>();
_UpdateSolutionEvents = new UpdateSolutionEvents(); // IVsUpdateSolutionEvents
int hr;
uint pdwCookie;
hr = _IVsSolutionBuildManager.AdviseUpdateSolutionEvents(_UpdateSolutionEvents, out pdwCookie);
Not directly. When you use Edit/Macros on a property field, the only configuration name listed is the one for the project that you already have.
You can, however, define your own macro. For each solution configuration, create a new property sheet, use the "User Macros" tab to define a macro whose name is "SolutionConfiguration" and whose value is the name of the configuration, then add this property sheet to the appropriate project configuration of every project in the solution.
If there's a better way I'd love to learn of it.
There is property SolutionConfigurationContents witch is created by Msbulid during soluton file processing it contains solution configuration in it. When building from VS it will contains project (not solution) configuration.

Add a link to an existing item during project creation

I've created a custom VS template which uses an IWizard class to do some automatic actions when a user creates a project to set project properties and paths, I've managed to set some project properties like build path by saving the .csproj file with parameters inside $ signs and setting those parameters in the replacementDictionary, during the RunStarted method.
Unfortunately I'm having trouble adding items as links to the .csproj using the same method. I have a .cs file I need to add as an existing and as a link item to each project created, it's path would be determined by where the user chooses to save the project. I've got to the part where I know the path of the .cs file, (absolute and relative to the project's path).
Here's what I've tried so far:
Save the .csproj file with a section for the item, with placeholders for the path:
<Compile Include="$path_to_cs_file\cs_file_name.cs$">
<Link>$cs_file_name.cs$</Link>
</Compile>
I've tried doing this with both absolute and relative paths, but this for some reason makes VS replace the path with a completely different relative path under Documents and Settings\user\Local Settings.
In RunStarted, cast the automationObject as DTE and call it's ItemOperations.AddExistingItem method. Using either path results in errors (The parameter is incorrect).
In ProjectFinishedGenerating, save the project's path, then at RunFinished, create a Microsoft.Build.BuildEngine.Project object with that path, call DTE commands to save all files and unload the project, then call the project object's AddNewItem Method and SetMetaData on the resulting ProjectItem, afterwards I save the project and reload it with the DTE object, this, again results the same errors as before
I'd appreciate any help with the subject, I'm pretty much stumped. Thank you in advance.
I managed to "solve" this issue, what I did is the following:
Kept the placeholder in the csproj, but never added the related parameters to the replacement dictionary:
<Compile Include="$path_to_cs_file$\$cs_file_name.cs$">
<Link>$cs_file_name.cs$</Link>
</Compile>
At the ProjectFinishedGenerated method, unloaded the project, edited the csproj file to replace the paths, and reloaded the project:
projectFileName = project.FullName
// Unload file and manually add the linked item
dte.ExecuteCommand("File.SaveAll");
dte.ExecuteCommand("Project.UnloadProject"); // See Note Below
StreamReader reader = new StreamReader(projectFileName);
string content = reader.ReadToEnd();
reader.Close();
content = Regex.Replace(content, #"\$path_to_cs_file\$", ...);
content = Regex.Replace(content, #"\$cs_file_name\$", ...);
StreamWriter writer = new StreamWriter(projectFileName);
writer.Write(content);
writer.Close();
dte.ExecuteCommand("Project.ReloadProject");
Note: The above code assumes the project needed modifying is currently selected project, usually when ProjectFinishedGenerating runs this is the case, however in a multi-project template or if you've added a project manually to the solution this might not be the case, you'll have to call dte methods to choose your "main" project in the project explorer, then go on with unloading, editing, and reloading. The code to do so would look something like this:
UIHierarchy UIH = dte2.ToolWindows.SolutionExplorer;
UIHierarchyItem UIHItem = UIH.UIHierarchyItems.Item(1);
UIHItem.UIHierarchyItems.Item(testProjectName).Select(vsUISelectionType.vsUISelectionTypeSelect);
An alternative solution if you don't want to mess around with IWizard is to set CreateInPlace to true in your vstemplate in TemplateData.
<CreateInPlace>true</CreateInPlace>
I'm having exactly the same problem and it's driving me mad.
I have found one really dirty workaround however:
In my situation I am using the following in the RunStarted method:
EnvDTE.DTE dte = automationObject as EnvDTE.DTE;
string solutionPath = System.IO.Path.GetDirectory(dte.DTE.Solution.FullName);
This returns a path which includes the folder "documents". Calling System.IO.Directory.Exists() confirms that this is a valid directory, however on checking my file system, it seems that this does not exist. If you replace "documents" with "my documents", and then continue to use that path for the linked item, all works perfectly.
So it seems that VS is getting confused with the "documents" directory alias and therefore defaulting to some crazy "AppData" directory instead.
I hope this helps, but if you find a better way to do this, please let me know!

Visual Studio Extensibility: Adding existing folders to a project

I'm trying to use Visual Studio 2008's extensibility to write an addin that will create a project folder with various messages in it after parsing an interface. I'm having trouble at the step of creating/adding the folder, however. I've tried using
ProjectItem folder =
item.ProjectItem.Collection.AddFolder(newDirectoryName, string.Empty);
(item is my target file next to which I'm creating a folder with the same name but "Messages" appended to it) but it chokes when a folder already exists (no big surprise).
I tried deleting it if it already exists, such as:
DirectoryInfo dirInfo = new DirectoryInfo(newDirectoryParent +
newDirectoryName);
if (dirInfo.Exists)
{
dirInfo.Delete(true);
}
ProjectItem folder =
item.ProjectItem.Collection.AddFolder(newDirectoryName, string.Empty);
I can SEE that the folder gets deleted when in debug, but it still
seems to think the folder is still there and dies on a folder already
exists exception.
Any ideas???
Thanks.
AK
.... Perhaps the answer would lie in programmatically refreshing the project after the delete? How might this be done?
ProjectItem pi = null;
var dir = Path.Combine(
project.Properties.Item("LocalPath").Value.ToString(), SubdirectoryName);
if (Directory.Exists(dir))
pi = target.ProjectItems.AddFromDirectory(dir);
else
pi = target.ProjectItems.AddFolder(dir);
ProjectItems.AddFromDirectory will add the directory and everything underneath the directory to the project.
Yup, that was it...
DirectoryInfo dirInfo = new DirectoryInfo(newDirectoryParent + newDirectoryName);
if (dirInfo.Exists)
{
dirInfo.Delete(true);
item.DTE.ExecuteCommand("View.Refresh", string.Empty);
}
ProjectItem folder = item.ProjectItem.Collection.AddFolder(newDirectoryName, string.Empty);
If there's a more elegant way of doing this, it would be much appreciated...
Thanks.
This is my approach:
//Getting the current project
private DTE2 _applicationObject;
System.Array projs = (System.Array)_applicationObject.ActiveSolutionProjects;
Project proy=(Project)projs.GetValue(0);
//Getting the path
string path=proy.FullName.Substring(0,proy.FullName.LastIndexOf('\\'));
//Valitating if the path exists
bool existsDirectory= Directory.Exists(path + "\\Directory");
//Deleting and creating the Directory
if (existeClasses)
Directory.Delete(path + "\\Directory", true);
Directory.CreateDirectory(path + "\\Directory");
//Including in the project
proy.ProjectItems.AddFromDirectory(path + "\\Directory");
I am developing an extension for Visual Studio 2019 and had a similar issue. The question asked in the following page helped me out:
https://social.msdn.microsoft.com/Forums/en-US/f4a4f73b-3e13-40bf-99df-9c1bba8fe44e/include-existing-folder-path-as-project-item?forum=vsx
If the folder does not physically exist, you can use AddFolder(folderName). But if the folder is not included in the project while existing physically, you need to provide the full system path to the folder. (AddFolder(fullPath))
here's an idea i thought of because i've been using NAnt for so long and thought it might work.
Open the .csproj file in a text editor and add the directory as such:
<ItemGroup>
<compile include="\path\rootFolderToInclude\**\*.cs" />
</ItemGroup>
if an "ItemGroup" already esists, that's fine. Just add it into an existing one. Visual studio won't really know how to edit this entry, but it will scan the whole directory.
edit to whatever you'd like.

Resources