Re-load Unloaded Projects in EnvDTE - c#

I have listed all the projects in my solution, using EnvDTE, but I found a bug in my code: I can't get the projects that are Unloaded.
I found a way to skip the Unloaded projects:
if (string.Compare(EnvDTE.Constants.vsProjectKindUnmodeled, project.Kind, System.StringComparison.OrdinalIgnoreCase) == 0)
continue;
This way, my code doesn't crash - but I am unable to load the missing projects through code, since they exist already.
How can I Load the Unloaded projects into the solution ?
I have tried:
project.DTE.ExecuteCommand("Project.ReloadProject");
And got error:
System.Runtime.InteropServices.COMException (...): Command "Project.ReloadProject" is not available.
So I tried to somehow get
application.DTE.ExecuteCommand("Project.ReloadProject");
But before that, from every place I searched on the NET, I must pre-select the project in the solution - and for that, I need project.Name (which I have), and the path, which I don't (every example I have found assumes that the solution path is the same as the project path, which is highly unlikely in a generic situation).

The Visual Studio SDK is apparently the way to do this.
var dte = (EnvDTE.DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
Microsoft.VisualStudio.Shell.Interop.IVsUIHierarchyWindow hierarchy;
ServiceProvider sp = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
IVsSolution sol = (IVsSolution)sp.GetService(typeof(SVsSolution));
foreach (ProjInfo info in GetProjectInfo(sol))
{
info.Dump();
}
//from http://social.msdn.microsoft.com/Forums/en-US/vsx/thread/60fdd7b4-2247-4c18-b1da-301390edabf3/
static IEnumerable<ProjInfo> GetProjectInfo(IVsSolution sol)
{
Guid ignored = Guid.Empty;
IEnumHierarchies hierEnum;
if (ErrorHandler.Failed(sol.GetProjectEnum((int)__VSENUMPROJFLAGS.EPF_ALLPROJECTS, ref ignored, out hierEnum)))
{
yield break;
}
IVsHierarchy[] hier = new IVsHierarchy[1];
uint fetched;
while ((hierEnum.Next((uint)hier.Length, hier, out fetched) == VSConstants.S_OK) && (fetched == hier.Length))
{
int res = (int)VSConstants.S_OK;
Guid projGuid;
if (ErrorHandler.Failed(res = sol.GetGuidOfProject(hier[0], out projGuid)))
{
Debug.Fail(String.Format("IVsolution::GetGuidOfProject returned 0x{0:X}.", res));
continue;
}
string uniqueName;
if (ErrorHandler.Failed(res = sol.GetUniqueNameOfProject(hier[0], out uniqueName)))
{
Debug.Fail(String.Format("IVsolution::GetUniqueNameOfProject returned 0x{0:X}.", res));
continue;
}
if( System.IO.Path.GetInvalidPathChars().Any (p =>uniqueName.Contains(p) ))
{
uniqueName.Dump("invalid filename found");
yield return new ProjInfo(projGuid,uniqueName);
}
else {
yield return new ProjInfo(projGuid, Path.GetFileName(uniqueName).BeforeOrSelf("{"));
}
}
}
got most of it from http://social.msdn.microsoft.com/Forums/en-US/vsx/thread/60fdd7b4-2247-4c18-b1da-301390edabf3/

Related

Vsix (Visual Studio extension) full path of right-clicked file

I would like to retrieve the full path of a right-clicked file (within the editor of Visual Studio 2017). I already implemented the following code to retrieve the path of a file when a project and/or solution is opened.
This implementation isn't working if a single file is opened.
Scenario:
Open VS (2017)
Navigate to File -> Open -> File.
Right click on a file and click on the desired context menu. (It calls the IsSingleProjectItemSelection method).
monitorSelection.GetCurrentSelection(out hierarchyPtr fails, because hierarchyPtr remains IntPtr.Zero.
Value cannot be null. Parameter name: pUnk
Perhaps you know a solution to retrieve the full path of a right-clicked file within the editor of Visual Studio (2017)?
Thank you in advance.
if (!IsSingleProjectItemSelection(out hierarchy, out itemid)) return;
// Get the file path
string itemFullPath = null;
((IVsProject) hierarchy).GetMkDocument(itemid, out itemFullPath);
var transformFileInfo = new FileInfo(itemFullPath);
string fullPath = itemFullPath.FullName;
public static bool IsSingleProjectItemSelection(out IVsHierarchy hierarchy, out uint itemid)
{
hierarchy = null;
itemid = VSConstants.VSITEMID_NIL;
int hr = VSConstants.S_OK;
var monitorSelection = Package.GetGlobalService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution;
if (monitorSelection == null || solution == null)
{
return false;
}
IVsMultiItemSelect multiItemSelect = null;
IntPtr hierarchyPtr = IntPtr.Zero;
IntPtr selectionContainerPtr = IntPtr.Zero;
try
{
hr = monitorSelection.GetCurrentSelection(out hierarchyPtr, out itemid, out multiItemSelect, out selectionContainerPtr);
if (ErrorHandler.Failed(hr) || hierarchyPtr == IntPtr.Zero || itemid == VSConstants.VSITEMID_NIL)
{
// there is no selection
return false;
}
// multiple items are selected
if (multiItemSelect != null) return false;
// there is a hierarchy root node selected, thus it is not a single item inside a project
if (itemid == VSConstants.VSITEMID_ROOT) return false;
hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
if (hierarchy == null) return false;
Guid guidProjectID = Guid.Empty;
if (ErrorHandler.Failed(solution.GetGuidOfProject(hierarchy, out guidProjectID)))
{
return false; // hierarchy is not a project inside the Solution if it does not have a ProjectID Guid
}
// if we got this far then there is a single project item selected
return true;
}
finally
{
if (selectionContainerPtr != IntPtr.Zero)
{
Marshal.Release(selectionContainerPtr);
}
if (hierarchyPtr != IntPtr.Zero)
{
Marshal.Release(hierarchyPtr);
}
}
}
DTE.ActiveDocument.FullName returns full path of the file you right clicked in.

How to cast ComObject to ENVDTE.Project for Unmodeled projects?

My question is very similar to this one: How to cast ComObject to ENVDTE.Project?
I want to process the Project items selected in Visual Studio -> Solution Explorer. If project is loaded the code works fine but I have troubles for unloaded projects (they are called Unmodeled projects (http://msdn.microsoft.com/en-us/library/hw7ek4f4%28v=vs.80%29.aspx).
Casting selected item for loaded projects uiItem.Object is EnvDTE.Project is fine, but how to cast Unmodeled projects?
There is no 'UnmodeledProject' class and casting uiItem.Object is ProjectItem does not work.
This is my code:
Window solutionExplorer = mApplicationObject.Windows.Item(Constants.vsWindowKindSolutionExplorer);
if(solutionExplorer != null)
{
UIHierarchy uiHierarchy = (UIHierarchy)solutionExplorer.Object;
if (uiHierarchy != null)
{
object[] selectedItems = (object[])uiHierarchy.SelectedItems;
foreach (UIHierarchyItem uiItem in selectedItems)
{
// Valid project
if (uiItem.Object is EnvDTE.Project)
{
EnvDTE.Project project = uiItem.Object as EnvDTE.Project;
if (project.FullName.Contains(".vdproj") || project.Kind == "{54435603-DBB4-11D2-8724-00A0C9A8B90C}")
{
}
}
else if (uiItem.Object is ProjectItem)
{
// This is never jumped...
}
else
{ ...
As I did not find a solution for this situation I used this trick:
string pathToVdProject = null;
try
{
Window solutionExplorer = mApplicationObject.Windows.Item(Constants.vsWindowKindSolutionExplorer);
if (solutionExplorer != null)
{
UIHierarchy uiHierarchy = (UIHierarchy)solutionExplorer.Object;
if (uiHierarchy != null)
{
object[] selectedItems = (object[])uiHierarchy.SelectedItems;
foreach (UIHierarchyItem uiItem in selectedItems)
{
// Valid project
if (uiItem.Object is EnvDTE.Project)
{
EnvDTE.Project project = uiItem.Object as EnvDTE.Project;
if (project.FullName.Contains(".vdproj") || project.UniqueName.Contains(".vdproj")
|| (String.Compare(project.Kind, ProjectsGuids.guidVdSetupProject, true) == 0))
{
// Valid Project has property FullName which is full path to .vdproj file
pathToVdProject = project.FullName;
break;
}
}
else if (uiItem.Object is ProjectItem)
{
// This never happens...
}
else
{
// This is a little tricky: Unmodeled Projects cannot be casted to EnvDTE.Project http://msdn.microsoft.com/en-us/library/hw7ek4f4%28v=vs.80%29.aspx
Solution2 solution = (Solution2)mApplicationObject.Solution;
// So get all projects in solution (including unmodeled) and try to find a match by name
foreach (Project project in solution.Projects)
{
if (project.Kind == EnvDTE.Constants.vsProjectKindUnmodeled)
{
// Unmodeled project found (Normal projects are recognized in 'uiItem.Object is EnvDTE.Project'
if (project.Name.Contains(uiItem.Name))
{
// This is 'Project' for selected item
if (project.Name.Contains(".vdproj") || project.UniqueName.Contains(".vdproj"))
{
// Unmodeled projects does not offer property FullName and UniqueName does NOT contain full path to file!
FileInfo fileInfo = new FileInfo(solution.FullName);
// Create full path from solution (.sln) path and project relative path
pathToVdProject = fileInfo.DirectoryName + "\\" + project.UniqueName;
break;
}
}
}
}
}
}
}
}
List of all loaded/Unloaded projects inside the solution explorer will be available in your EnvDTE application object. Without using solution Explorer window and UIHierarchy i got the project details. Below code snippets working fine for me. Please check out weather it will fit for you..
For Each item As EnvDTE.Project In mApplicationObject.Solution.Projects
If item.Globals Is Nothing AndAlso item.Object Is Nothing Then
Console.WriteLine(item.Name + " is currently unloaded!")
End If
Next

Getting the Last Modified Date of an assembly in the GAC

I have implemented the fusion.dll wrapper mentioned in many posts and now find that at least one dll I need to determine if it needs to be updated is not using build and revision numbers. Consequently I cannot compare on version numbers and need to compare on Last Modified date.
fusion.dll or it's wrappers have no such method which I guess is fair enough but how do I determine the 'real' path of the dll so that I can discovers it's last Modified date.
My code so far:
private DateTime getGACVersionLastModified(string DLLName)
{
FileInfo fi = new FileInfo(DLLName);
string dllName = fi.Name.Replace(fi.Extension, "");
DateTime versionDT = new DateTime(1960,01,01);
IAssemblyEnum ae = AssemblyCache.CreateGACEnum();
IAssemblyName an;
AssemblyName name;
while (AssemblyCache.GetNextAssembly(ae, out an) == 0)
{
try
{
name = GetAssemblyName(an);
if (string.Compare(name.Name, dllName, true) == 0)
{
FileInfo dllfi = new FileInfo(string.Format("{0}.dll", name.Name));
if (DateTime.Compare(dllfi.LastWriteTime, versionDT) >= 0)
versionDT = dllfi.LastWriteTime;
}
}
catch (Exception ex)
{
logger.FatalException("Unable to get version number: ", ex);
}
}
return versionDT;
}
From the problem description in your question I can see there are really 2 primary tasks that you are trying to accomplish:
1) Determine if a given assembly name can be loaded from the GAC.
2) Return the file modified date for the given assembly.
I believe these 2 points can be accomplished in a much simpler fashion and without having to work with the unmanaged fusion API. An easier way to go about this task might be as follows:
static void Main(string[] args)
{
// Run the method with a few test values
GetAssemblyDetail("System.Data"); // This should be in the GAC
GetAssemblyDetail("YourAssemblyName"); // This might be in the GAC
GetAssemblyDetail("ImaginaryAssembly"); // This just plain doesn't exist
}
private static DateTime? GetAssemblyDetail(string assemblyName)
{
Assembly a;
a = Assembly.LoadWithPartialName(assemblyName);
if (a != null)
{
Console.WriteLine("'{0}' is in GAC? {1}", assemblyName, a.GlobalAssemblyCache);
FileInfo fi = new FileInfo(a.Location);
Console.WriteLine("'{0}' Modified: {1}", assemblyName, fi.LastWriteTime);
return fi.LastWriteTime;
}
else
{
Console.WriteLine("Assembly '{0}' not found", assemblyName);
return null;
}
}
An example of the resulting output:
'System.Data' is in GAC? True
'System.Data' Modified: 10/1/2010 9:32:27 AM
'YourAssemblyName' is in GAC? False
'YourAssemblyName' Modified: 12/30/2010 4:25:08 AM
Assembly 'ImaginaryAssembly' not found

Getting location of file tnsnames.ora by code

How can I get the location of the tnsnames.ora file by code, in a machine with the Oracle client installed?
Is there a windows registry key indicating the location of this file?
Some years ago I had the same problem.
Back then I had to support Oracle 9 and 10 so the code only takes care of those versions, but maybe it saves you from some research.
The idea is to:
search the registry to determine the oracle client version
try to find the ORACLE_HOME
finally get the tnsnames from HOME
public enum OracleVersion
{
Oracle9,
Oracle10,
Oracle0
};
private OracleVersion GetOracleVersion()
{
RegistryKey rgkLM = Registry.LocalMachine;
RegistryKey rgkAllHome = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE\ALL_HOMES");
/*
* 10g Installationen don't have an ALL_HOMES key
* Try to find HOME at SOFTWARE\ORACLE\
* 10g homes start with KEY_
*/
string[] okeys = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE").GetSubKeyNames();
foreach (string okey in okeys)
{
if (okey.StartsWith("KEY_"))
return OracleVersion.Oracle10;
}
if (rgkAllHome != null)
{
string strLastHome = "";
object objLastHome = rgkAllHome.GetValue("LAST_HOME");
strLastHome = objLastHome.ToString();
RegistryKey rgkActualHome = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\ORACLE\HOME" + strLastHome);
string strOraHome = "";
object objOraHome = rgkActualHome.GetValue("ORACLE_HOME");
string strOracleHome = strOraHome = objOraHome.ToString();
return OracleVersion.Oracle9;
}
return OracleVersion.Oracle0;
}
private string GetOracleHome()
{
RegistryKey rgkLM = Registry.LocalMachine;
RegistryKey rgkAllHome = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE\ALL_HOMES");
OracleVersion ov = this.GetOracleVersion();
switch(ov)
{
case OracleVersion.Oracle10:
{
string[] okeys = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE").GetSubKeyNames();
foreach (string okey in okeys)
{
if (okey.StartsWith("KEY_"))
{
return rgkLM.OpenSubKey(#"SOFTWARE\ORACLE\" + okey).GetValue("ORACLE_HOME") as string;
}
}
throw new Exception("No Oracle Home found");
}
case OracleVersion.Oracle9:
{
string strLastHome = "";
object objLastHome = rgkAllHome.GetValue("LAST_HOME");
strLastHome = objLastHome.ToString();
RegistryKey rgkActualHome = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\ORACLE\HOME" + strLastHome);
string strOraHome = "";
object objOraHome = rgkActualHome.GetValue("ORACLE_HOME");
string strOracleHome = strOraHome = objOraHome.ToString();
return strOraHome;
}
default:
{
throw new Exception("No supported Oracle Installation found");
}
}
}
public string GetTNSNAMESORAFilePath()
{
string strOracleHome = GetOracleHome();
if (strOracleHome != "")
{
string strTNSNAMESORAFilePath = strOracleHome + #"\NETWORK\ADMIN\TNSNAMES.ORA";
if (File.Exists(strTNSNAMESORAFilePath))
{
return strTNSNAMESORAFilePath;
}
else
{
strTNSNAMESORAFilePath = strOracleHome + #"\NET80\ADMIN\TNSNAMES.ORA";
if (File.Exists(strTNSNAMESORAFilePath))
{
return strTNSNAMESORAFilePath;
}
else
{
throw new SystemException("Could not find tnsnames.ora");
}
}
}
else
{
throw new SystemException("Could not determine ORAHOME");
}
}
On Windows, the most likely locations are either %ORACLE_HOME%/network/admin or %TNS_ADMIN% (or the TNS_ADMIN registry setting). These two cover almost every installation.
Of course it is possible to have a working Oracle client without this file. Oracle has bewildering array of networking options, and there are plenty of ways to achieve a working setup with using TNSNAMES. Depending on what you are trying to achieve here, your first port of call might be the sqlnet.ora file, which is also found in %ORACLE_HOME%/network/admin. This should contain a line that looks something like this:
NAMES.DIRECTORY_PATH= (LDAP, TNSNAMES, HOSTNAME)
TNSNAMES means it will use the TNSNAMES.ora file (second in this case). LDAP and HOSTNAME are alternate ways of resolving the database. If there is no TNSNAMES the TNSNAMES.ora file will be ignored if it exists in the right place.
In C# / .NET this should get you the environment variables:
Environment.GetEnvironmentVariable("ORACLE_HOME");
Environment.GetEnvironmentVariable("TNS_ADMIN");
List<string> logicalDrives = Directory.GetLogicalDrives().ToList();
List<string> result = new List<string>();
foreach (string drive in logicalDrives)
{
Console.WriteLine("Searching " + drive);
DriveInfo di = new DriveInfo(drive);
if(di.IsReady)
result = Directory.GetFiles(drive, "tnsnames.ora", SearchOption.AllDirectories).ToList();
if (0 < result.Count) return;
}
foreach (string file in result) { Console.WriteLine(result); }
According to the net that depends on the version of Oracle and the working directory of the SQL*Plus process. This first link tells you the environment variable that specifies the base path for some versions (7, 8, 9i) of Oracle. If you use a different one, I'm sure there's a similar way to get to the system directory.
If you spread versions of these files all over the place though and rely on the "look for a local tnsnames.ora first" behaviour of the client, then I guess you're out of luck.
I'm not a C# or a Windows guy for that matter so hopefully this helps. The tnsnames.ora file should be located in:
ORACLE_HOME\network\admin
If an alternate location has been specified, it should be available via the TNS_ADMIN registry key.
See this link for more information on how Oracle handles tns names on Windows.

Visual Studio Extensibility, Programmatically Creating A Project

I am attempting to programmatically add a test project to a solution. However when the code below executes I receive a File IO exception on the line "vhaSolution.GetProjectTemplate("TestProject.zip", "Csharp")". The error indicates that "he language specified is not supported by any of the installed packages". Does anyone have any idea what could be causing this?
public enum TestProjectType
{
Unit,
Acceptance,
Integration
}
public static void CreateTestProject(string fullyQualifiedSolutionFileName,string projectName,TestProjectType testProjectType)
{
#region Argument Validation
if (String.IsNullOrEmpty(fullyQualifiedSolutionFileName) || String.IsNullOrEmpty(fullyQualifiedSolutionFileName.Trim()))
{
throw new ArgumentNullException("fullyQualifiedSolutionFileName", "The solution file location is required.");
}
if (String.IsNullOrEmpty(projectName) || String.IsNullOrEmpty(projectName.Trim()))
{
throw new ArgumentNullException("projectName", "The project name is required.");
}
if (!File.Exists(fullyQualifiedSolutionFileName))
{
throw new ArgumentException(String.Format("The file {0} specified does not exist.", fullyQualifiedSolutionFileName));
}
if (testProjectType == null) testProjectType = TestProjectType.Unit;
#endregion
System.Type vsType = System.Type.GetTypeFromProgID("VisualStudio.DTE.8.0");
Object vs = System.Activator.CreateInstance(vsType, true);
EnvDTE80.DTE2 dte8Obj = (EnvDTE80.DTE2)vs;
Solution2 vhaSolution = (Solution2)dte8Obj.Solution;
vhaSolution.Open(fullyQualifiedSolutionFileName);
//TODO: Externalize company name
string cmpnyName = "Vha";
string testProjectName = String.Format("{0}.{1}.{2}{3}",cmpnyName,projectName,testProjectType.ToString(),"Test");
string testTemplateLocation = vhaSolution.GetProjectTemplate("TestProject.zip", "CSharp");
FileInfo rootSolutionFolder = new FileInfo(fullyQualifiedSolutionFileName);
//TODO: Externalize test directory name
string testDirName = String.Format("{0}\\{1}\\{2}\\{3}",rootSolutionFolder.DirectoryName,"test",testProjectType.ToString(),testProjectName);
if (!Directory.Exists(testDirName))
{
//may throw an exception if the dir can't be created...
Directory.CreateDirectory(testDirName);
}
Project vhaTestProj = vhaSolution.AddFromTemplate(testTemplateLocation,testDirName,testProjectName + ".proj",false);
vhaTestProj.Save(String.Format("{0}\\{1}.proj",testDirName , testProjectName));
}
I figured it out. I need to use the prog ID VisualStudio.DTE.9.0 so that it would point to the correct registry location for VS 2008.

Categories