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

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.

Related

How can I retrieve items and their details from the shell:AppsFolder virtual folder using c#?

I am trying to get all the items from the FOLDERID_AppsFolder, which you can access by running explorer.exe shell:appsFolder command, and their details, specifically the AppUserModelID.
I can get the name of the items using the code below but I am not sure how to get the AppUserModelID. Can I get this value somehow?
IShellItem appsFolder;
string str;
var res = ShellItemUtilities.SHGetKnownFolderItem(ShellItemUtilities.FOLDERID_AppsFolder,
0, IntPtr.Zero, typeof(IShellItem).GUID, out appsFolder);
if (res < 0) return;
try
{
var pidl = default(PIDLIST_ABSOLUTE);
foreach (var app in appsFolder.Enumerate())
{
try
{
recyleBin.GetDisplayName(2, out ptr);
// Get the actual name of the item
str = Marshal.PtrToStringUni(ptr);
}
finally
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(ptr);
ptr = IntPtr.Zero;
}
}
}
}
...
Perhaps the IShellItem::GetAttributes method is what I need but it can only retrieve the attribute that I specify through the sfgaoMask parameter and the documentation regarding the values for this parameter does not include anything related to the AppUserModelID.
And for reference, this is what the apps folder looks like:
Can you hear the crickets chirping?
I was eventually able to find a solution to this problem when I stumbled upon the Microsoft.WindowsAPICodePack-Shell nuget package which wraps all the shell commands I needed so that I don't have to P/Ivoke them. The code now becomes:
// GUID taken from https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
var FODLERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FODLERID_AppsFolder);
foreach (var app in (IKnownFolder)appsFolder)
{
string name = app.Name;
// The ParsingName property is the AppUserModelID
string appUserModelID = app.ParsingName; // or app.Properties.System.AppUserModel.ID
ImageSource icon = app.Thumbnail.MediumBitmapSource;
}
The ShellObject object actually contains a whole lot more proeprties available through it's Properties.System props.
In case you are wondering why I am casting the appsFolder to a ShellObject at instantiation only to cast it back to an IKnownFolder when enumerating, that's because the API code pack actually comes with a ShellObjectWatcher which takes a ShellObject and monitors it for changes. If a new app is installed and it is listed in this virtual folder, the watcher can be used to monitor for this:
ShellObjectWatcher sow = new ShellObjectWatcher(appFolder, false);
sow.AllEvents += (s, e) => DoWhatever();
sow.Start();

VSX: Save ProjectItem in folder

I have an visual studio 2015 extension which reads files from a DB and creates a solution and project from a template. The files are then added as ProjectItems to the project (type VSProject2).
I would like to save the files in a folder, together with the csproj and sln file so that one can open it later without the extension.
However, the folder which contains the files in the solution window, is empty on disk.
So I'm confused. I've tried to save the projectItems, but it still doesn't end up in die folder on disk.
I add the folder with variable name "foldername" to the project. Let's make it TimeAndAttendance. So on disk a folder TempProject is created and in that, a folder TimeAndAttendance. I would like the csproj file and the source files (added as ProjectItems) to be saved in that folder.
public void CreateSolution()
{
// This function creates a solution and adds a Visual C# Class library project to it.
try
{
Solution2 soln = (Solution2)visualStudioInstance.Solution;
String csTemplatePath;
Project projCS = null;
parameterWindowEventsList = new Dictionary<EnvDTE.Window, WindowEvents>();
String csPrjPath = Path.Combine(Config.Cluster.WorkingPath, "TempProject");
if (Directory.Exists(csPrjPath))
{
DirectoryInfo di = new DirectoryInfo(csPrjPath);
foreach (var dir in di.GetDirectories())
{
dir.Attributes = FileAttributes.Normal;
FileInfo fi = new FileInfo(dir.FullName);
fi.IsReadOnly = false;
dir.Delete(true);
}
di.Attributes = FileAttributes.Normal;
di.Delete(true);
}
csTemplatePath = soln.GetProjectTemplate("TAEngineTemplate.zip", "CSharp");
// Create a new C# Class Library project using the template obtained above.
soln.AddFromTemplate(csTemplatePath, csPrjPath, "TAEngineProject", false);
foreach (Project p in soln.Projects)
{
if (String.Compare(p.Name, "TAEngineProject") == 0)
{
projCS = p;
break;
}
}
System.Windows.Forms.Application.DoEvents();
if (projCS != null)
{
taEngineProject = projCS.Object as VSProject2;
}
taEngineProject.References.Add(Path.Combine(Config.Engine.EngineBinPath, "Engines.TimeAndAttendance.dll"));
taEngineProject.References.Add("System.Data.DLL");
taEngineProject.References.Add("System.Xml.DLL");
taEngineProject.References.Add("System.Core.dll");
taEngineProject.Project.ProjectItems.AddFolder(folderName);
taEngineProject.Project.Save(null);
}
catch (SystemException ex)
{
log.Error("Problem with creating solution. " + ex);
}
}
The last line
taEngineProject.Project.Save(null);
doesn't seem to do anything.
The following is where I load a source file as a ProjectItem. Any improvement on how to do it without the try-catch functionality is also welcome. I inherited this code and just shook my head the first time I saw it, but I don't know how to improve it either. :-(
public ProjectItem LoadAsProjectItem(string sourcePath)
{
if (taEngineProject != null)
{
string filename = sourcePath.Substring(sourcePath.LastIndexOf("\\") + 1);
ProjectItem retItem;
ProjectItem projFolderItem = taEngineProject.Project.ProjectItems.Item(folderName);
try
{
retItem = projFolderItem.ProjectItems.Item(filename);
}
catch (Exception)
{
retItem = projFolderItem.ProjectItems.AddFromFile(sourcePath);
}
return retItem;
}
return null;
}

Files that are upload does not show up in the Project solution(Mvc5)

I have implement the following code to upload a file. The file is upload to the location ("../App_Data/uploads") but it does not show up in the project. I have to include the file manually in the project. Why is the file not showing up?
public ActionResult ChangeSetting(SettingViewModel setting)
{
string userId = User.Identity.GetUserId();
ApplicationUser currentUser = this._appUserRepo.Find(e => e.Id.Equals(userId)).FirstOrDefault();
if (setting.PictureUrl != null)
{
if (setting.PictureUrl.ContentLength > 0)
{
string fileName = Path.GetFileName(setting.PictureUrl.FileName);
if (fileName != null)
{
string path = Path.Combine(Server.MapPath("../App_Data/uploads"), fileName);
setting.PictureUrl.SaveAs(path);
if (currentUser != null)
{
currentUser.PictureUrl = path;
currentUser.PictureSmalUrl = path;
currentUser.PictureBigUrl = path;
}
}
}
}
if (setting.FirstName != null)
{
if (currentUser != null) currentUser.FirstName = setting.FirstName;
}
_appUserRepo.Update(currentUser);
return RedirectToAction("index", "Admin");
}
Why is the file not showing up?
Because that's how Visual Studio projects work - only files that you manually include into the project are shown in the solution explorer. Files that get added dynamically to folders afterwards by eternal processes do not show up in Visual Studio (unless you manually include them and make them part of the solution). But that's probably not something you should be worried about. The files uploaded by users won't automagically appear in your Visual Studio project. The important thing is that they are present at the expected location and that your web application is capable of accessing them at runtime. Just open the physical location of the folder in Windows Explorer to confirm that your code is working as expected.

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

Re-load Unloaded Projects in EnvDTE

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/

Categories