Click here to Skip to main content
15,886,919 members
Articles / Programming Languages / C#

VSLauncherX - Better Recent List (and More) for Visual Studio

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
27 Sep 2023CPOL10 min read 3.3K   3  
Better recentlist, solutions and projects management for Visual Studio, not replacing the start window
At its core, it is a list of recently used solutions and projects in any currently installed Visual Studio version. It can detect and use all installed versions from 2017 to 2022 currently, including all profiles created in any of them.
  • Source code and installer available on Github

Introduction

As an enterprise developer, one is often in the situation to run multiple Visual Studio instances side by side, with different solutions and configurations.

Doing so manually is most often a tedious and time consuming process. Launch the VS version you need, possibly "as admin", find the solution or project in the recent list, etc.

Also, as of late, the "Start Window" of Visual Studio is not the most helpful, besides its inherent limitations of managing its recently used list (number of items remembered, speed of searching the list, lack of grouping).

This brought me to the idea of creating a program that can help me in managing my solutions (at work, I got quite a few of them and some are very specific) and speed up the process of starting work on any of them.

Image 1

While there are a bunch of extensions for Visual Studio on the marketplace that remedy the situation one way or another, they all are limited to having to start Visual Studio before you can do anything.

Note: I will use SoP as abbreviation for "Solutions and/or Projects"

Main Features

  • Make groups of SoPs that can be used all at once
  • Define item specific settings like "Run as Admin", execute another application or command before and/or after Visual Studio
  • Use a specific VS Profile for each SoP
  • Import all SoPs from a folder as a group
  • Launch each SoP on a defined display or monitor
  • Quick search and execute
  • Automatically synchronize the list(s) with Visual Studios recent list
  • No limit on "recent files" list, can add an almost unlimited amount of SoPs
  • Grouping with subgroups
  • Favorites that can be invoked directly from the taskbar context menu
  • Show git status of solutions/projects (Clean/Modified)
  • Show admin privileges and elevation status of application
  • Autostart and always run as Admin

Working Features

  • Importing from folders
  • Importing from any Visual Studio version and profile
  • Support for VS 2017, 2019 and 2022
  • Launching an entire group with subgroups of SoPs, each on its defined VS version, working folder and VS Profile
  • Execute other apps before and/or after each group or SoP
  • Launch each SoP on a specific monitor (not 100% verified on different setups)
  • Quick search and execute
  • Detection of non-existing SoPs when importing from VS
  • User elevation for Run as Admin
  • Run other commands before and/or after an SoP or group, optionally wait for completion before the next action

Planned Features

  • Automatically synchronize with VS
  • Support for Visual Studio Code
  • Taskbar integration for even quicker launching (Work in progress)
  • Support for dark mode
  • Support for non-Windows environments

So if anyone feels up to help out here, head over to Github and contribute - https://github.com/Hefaistos68/VSLauncherX

Background

The program posed a few minor challenges, from detecting installed Visual Studio versions and instances, dealing with rights and privileges (more on that below), launching apps with specific privileges and on a given display, autostart and elevation.

Using the Application

First, either import your whole recent list from VS by using the toolbar button with the VS symbol, or import from a folder directly with the folder-up-arrow toolbar button. You can also drag solutions or projects from the explorer directly into it.

After you have at least one SoP in the list, you can start it by pressing enter on the selected item or double clicking it. When you double click on a folder/group, then the whole folder will be launched, each item in its own VS instance as defined in the items settings.

Image 2

The settings dialog allows you to chose a specific VS version to use for this item (and all contained items), chose an "Instance", add additional commands, define a specific display (monitor) where you want this VS to appear, elevation status and to show or not the Splash screen of Visual Studio.

Also, you can add the before and after execution settings, which allows you to run other commands or applications before or after starting the item, like git.exe for example or really anything you may need.

The dialog will bring you directly to the help pages for the specific item when you click on the question mark (or the version text for VS versions).

As expected, you can use drag and drop to order items, Alt+Enter to open the items settings, Del to delete an item, etc. Remember that the settings of a folder/group item are applied before all items in it. This means that when you set a folder to execute as admin, all the contained SoPs will run as admin too. You can set for each SoP which (installed) version of Visual Studio you want to open it with. Recommended version is shown for each as it is defined in the solution file. There are also context menus that you can invoke with right clicking on an item. When you "Favorite" an item, it is added to the Tasks context menu in the Taskbar app icon (this is still work in progress, expect errors).

Points of Interest

Run as Administrator

Lets talk about "Run as Admin" - this is a rather heavy discussed topic on any site related to programming or windows administration. Still, there is a big misunderstanding on how it actually works. Even Microsoft gets it wrong sometimes (see "Visual Studio does not recognize admin status" on the Visual Studio feedback site).

First, why would you ever need to run Visual Studio as admin??? Well, to debug services that require elevated privileges, like system services, any app that opens certain TCP ports or pipes that are blocked by policies. Probably, there are a few other situations where you would need to.

For most users, "Run as Admin" is of little use, either they are always admin anyway (developers love this) or they don't need it except in rare cases to install something new. When you move to a corporate environment, things quickly escalate. There, a domain user is usually member of the BUILTIN\Administrators group but the group is set to "deny" which means that no privileges from this group are available to the user. When you now run as admin, the UAC dialog pops up, asks for confirmation or credentials (depending on how its set up and your current elevation), then enables the administrators group and new process tokens receive the privileges of this group (ok, its simplified here).

So, how do programs determine if they have admin status? Usually, by checking the presence of the BUILTIN\Administrators group SID among the groups in the process token. Simple and easy, like this:

C#
public static bool IsAdmin(Process process) 
{
   WindowsIdentity processIdentity = GetProcessIdentity(process); 
   return new WindowsPrincipal(processIdentity).IsInRole(WindowsBuiltInRole.Administrator); 
}

Unfortunately, this is not correct, works for many situations, but not all. In my work environment for example, nobody is member of the Administrators group, except Domain Administrators. Instead, they use another group to perform the same task, for the sake of functionality all works as expected, it just breaks all programs that depend on the presence of the admin SID.

The correct way to determine elevation (and this is what we are talking about in the end) is checking the token integrity value or token elevation. I have found that the integrity value is more reliable in all situations. Check the ProcessHelper and SecurityHelper classes for details.

VSLauncherX does both of it - detects if the user is member of the BUILTIN\Administrators group and if the process has elevated privileges.

Both facts are shown in the application titlebar - "ADMIN" and/or "Elevated" will be added to the title, so you will always know what you are at.

Visual Studio and Its Caveats

There are many ways one can figure out which VS versions are installed and where, from searching through the registry, using the setup api, groping through the directories, all work to some degree and all have varying levels of difficulty. Fortunately for me, Microsoft has had the foresight to include WMI registration for Visual Studio. You can read up on it here.

So it all comes down to a simple WMI query and we have a list of all installed VS versions and their location:

C#
ManagementObjectSearcher searcher = new ManagementObjectSearcher
{
	Query = new SelectQuery("MSFT_VSInstance ", "", 
            new[] { "Name", "Version", "ProductLocation", "IdentifyingNumber" })
};
ManagementObjectCollection collection = searcher.Get();
ManagementObjectCollection.ManagementObjectEnumerator em = collection.GetEnumerator();
 
while (em.MoveNext())
{
	ManagementBaseObject baseObj = em.Current;
	if (baseObj.Properties["Version"].Value != null)
	{
		try
		{
			string? name = baseObj.Properties["Name"].Value.ToString();
			string? version = baseObj.Properties["Version"].Value.ToString();
			string? location = baseObj.Properties["ProductLocation"].Value.ToString();
			string? identifier = 
                    baseObj.Properties["IdentifyingNumber"].Value.ToString();
 
			if (name != null && version != null && location != null && identifier != null)
			{
				list.Add(new VisualStudioInstance(name, version, location, 
                identifier, VisualStudioInstanceManager.YearFromVersion(version[..2])));
			}
		}
		catch (Exception ex)
		{
			Debug.WriteLine(ex.ToString());
		}
	}
}

As we are only interested in Version, Location and the Instance number, that are all the properties requested in this query.

More interesting is how VS stores its recent list. Its an JSON string inside a XML file stored in the local application data folder (I wonder who had this idea).

From there is a simple path, read the list, check if the target still exists and add it to the list to import. Also parse the .SLN and project files to get a version number in which Visual Studio version the file was created and set it as recommended version. If that version is not currently installed, the user has the option to either download it from the link in the item settings dialog, or select an installed version, or just leave it to launch with the default (always the highest installed version).

Auto-Start and Always Admin

Also interesting was how to make the application auto-start and always run-as-admin through a setting as opposed to modifying the application shortcut.

I opted for implementing this through the TaskScheduler using a logon trigger when set to auto-start, the tasks allow for elevating the application when always-run-as-admin is chosen, to do so, the application restarts itself with the "runas" verb in the ProcessStartInfo so the UAC dialog comes up and grants access, then it adds the task.

Image 3

All functionality in doing so, is inside the AutoRun class. The SetupLauncherTask() method, does all the work of creating a folder in the TaskScheduler, creating a task with (or without) a trigger.

C#
using (TaskService ts = new TaskService())
{
	TaskFolder folder;
    // check if the folder exists, if not create a new one
	var bFolderExists = ts.RootFolder.SubFolders.Any(sf => sf.Name == FolderName);
 
	if (!bFolderExists)
	{
		folder = ts.RootFolder.CreateFolder(FolderName);
	}
	else
	{
		folder = ts.RootFolder.SubFolders.First(sf => sf.Name == FolderName);
	}
 
	var user = System.Security.Principal.WindowsIdentity.GetCurrent();
 
    // setup the application to start, and add a new task called "Autostart for <user>"
	string location = Assembly.GetExecutingAssembly().Location.Replace(".dll", ".exe");
	var execAction = new ExecAction(location, "autostart" ,
                     workingDirectory: Path.GetDirectoryName(location));
	var taskName = TaskName + GetUserName(user);
 
	// create the task if it doesn't exist
	if (folder.AllTasks.Any(t => t.Name == taskName))
	{
		folder.DeleteTask(taskName);
	}
 
	// Create a new task definition and assign properties
	TaskDefinition td = ts.NewTask();
	td.RegistrationInfo.Author = user.Name;
	td.RegistrationInfo.Description = "Visual Studio Launcher";
 
    // if the app should run-as-admin or elevated, 
    // then give it highest available privileges
	td.Principal.RunLevel = bElevated ? TaskRunLevel.Highest : TaskRunLevel.LUA;
	td.Principal.LogonType = TaskLogonType.InteractiveToken;
 
    // some more less interesting start options to fine tune
	td.Settings.StartWhenAvailable = true;
	td.Settings.AllowDemandStart = true;
	td.Settings.IdleSettings.StopOnIdleEnd = false;
	td.Settings.DisallowStartIfOnBatteries = false;
	td.Settings.StopIfGoingOnBatteries = false;
	td.Settings.ExecutionTimeLimit = TimeSpan.Zero;
	td.Settings.AllowHardTerminate = true;
	td.Settings.MultipleInstances = TaskInstancesPolicy.Parallel;
 
    // so here is the autostart setup, which causes the app to start 
    // after the user logs on
    // this is archived through an LogonTrigger
	if (asAutostart)
	{
		// Create a trigger that will fire the task when the user logs on
		var logonTrigger = new LogonTrigger
		{
			UserId = user.User.ToString(),
			Delay = TimeSpan.FromSeconds(10)	// give task manager time to start too
		};
 
		td.Triggers.Add(logonTrigger);
	}
 
	td.Actions.Add(execAction);
 
	// Register the task in the root folder
	folder.RegisterTaskDefinition(taskName, td);
}

That Damn Process.Start...

There was one issue that caused me almost to go bald: Process.Start failed me with an error message that made not much sense:
The Process object must have the UseShellExecute property set to false in order to use environment variables.

What the heck? I didn't set any environment variables to start the process. So why would I get this error message? A quick (and then more thorough) search through the internet brought up a few results, many people asking the same question. Then I looked into the .NET code and the original comment on the method gave me a hint:

C#
//
// Summary:
//     When the System.Diagnostics.ProcessStartInfo.UseShellExecute property is false,
//     gets or sets the working directory for the process to be started. 
//     When System.Diagnostics.ProcessStartInfo.UseShellExecute
//     is true, gets or sets the directory that contains the process to be started.
//
// Returns:
//     When System.Diagnostics.ProcessStartInfo.UseShellExecute is true, 
//     the fully qualified name of the directory that contains 
//     the process to be started. 
//     When the System.Diagnostics.ProcessStartInfo.UseShellExecute
//     property is false, the working directory for the process to be started. 
//     The default is an empty string ("").

Oh yeah, so the working directory has to do with it.

As soon as I stopped setting a working directory AND use the UseShellExecute property set to true, all was fine. So the recommendation is simple: don't touch the WorkingDirectory property (No, not even reading it) when you want to use the shell to execute your application, which you only need when you want to use any other verb than "run". When you start an app through the shell, the working directory is automatically set to the application directory.

All the Rest of It

It's all pretty straightforward WinForm code, not many specials to mention here. I would have done it in WPF but I don't like it and I don't know it. So WinForms it is.

3rd Party Libraries

The application also uses a slightly modified version of the ObjectListView (https://objectlistview.sourceforge.net/cs/index.html), I did add the ability to render multiple lines and images to the tree view, maybe I get time to push this back into the original repository.

For GIT support it uses LibGit2Sharp, currently only to get the status of the repository and show in the UI through an either red or green GIT icon.

The Task Scheduler is supported by the https://github.com/dahall/taskscheduler library

And for some Windows APIs, the https://github.com/Wagnerp/Windows-API-CodePack-NET library.

History

Not much history yet, the code is up on Github since a short time, improving as I get time to do it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Portugal Portugal
Software Smith, Blacksmith, Repeat Founder, Austrian, Asgardian.

Comments and Discussions

 
-- There are no messages in this forum --