diff --git a/VSLauncherX/App.config b/VSLauncherX/App.config
index fe59ae3..915598a 100644
--- a/VSLauncherX/App.config
+++ b/VSLauncherX/App.config
@@ -40,6 +40,9 @@
True
+
+ False
+
\ No newline at end of file
diff --git a/VSLauncherX/Controls/VisualStudioCombobox.cs b/VSLauncherX/Controls/VisualStudioCombobox.cs
new file mode 100644
index 0000000..bf2be35
--- /dev/null
+++ b/VSLauncherX/Controls/VisualStudioCombobox.cs
@@ -0,0 +1,218 @@
+using VSLauncher.DataModel;
+
+namespace VSLauncher.Controls
+{
+ ///
+ /// The visual studio combobox.
+ ///
+ public class VisualStudioCombobox : ComboBox
+ {
+ private VisualStudioInstanceManager visualStudioVersions = new VisualStudioInstanceManager();
+ private bool showDefault;
+
+ public VisualStudioCombobox()
+ {
+ DrawMode = DrawMode.OwnerDrawFixed;
+ DropDownStyle = ComboBoxStyle.DropDownList;
+ IntegralHeight = false;
+ ItemHeight = 26;
+ DrawItem += CustomDrawItem;
+
+ Items.AddRange(visualStudioVersions.All.ToArray());
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show default.
+ ///
+ public bool ShowDefault
+ {
+ get => showDefault;
+ set
+ {
+ showDefault = value;
+
+ if (value)
+ {
+ Items.Insert(0, "");
+ }
+ else
+ {
+ if (Items.Count > 0 && Items[0] is string)
+ {
+ Items.RemoveAt(0);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets the versions.
+ ///
+ public List Versions { get { return visualStudioVersions.All; } }
+
+ ///
+ /// Gets the selected item.
+ ///
+ public new VisualStudioInstance? SelectedItem
+ {
+ get
+ {
+ if (ShowDefault)
+ {
+ return base.SelectedIndex > 0 ? (VisualStudioInstance)base.SelectedItem : null;
+ }
+
+ return (VisualStudioInstance)base.SelectedItem;
+ }
+ set
+ {
+ base.SelectedItem = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the selected index, accounting for the default item which will result in -1
+ ///
+ public new int SelectedIndex
+ {
+ get
+ {
+ return ShowDefault ? base.SelectedIndex - 1 : base.SelectedIndex;
+ }
+ set
+ {
+ base.SelectedIndex = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether default is selected.
+ ///
+ public bool IsDefaultSelected
+ {
+ get
+ {
+ return ShowDefault && base.SelectedIndex == 0;
+ }
+ }
+
+ ///
+ /// Selects the default item if existing
+ ///
+ public void SelectDefault()
+ {
+ if (ShowDefault)
+ {
+ SelectedIndex = 0;
+ }
+ }
+
+ protected override void OnDropDown(EventArgs e)
+ {
+ Items.Clear();
+
+ if (ShowDefault)
+ {
+ Items.Add("");
+ }
+
+ Items.AddRange(visualStudioVersions.All.ToArray());
+
+ base.OnDropDown(e);
+ }
+
+ ///
+ /// Selects the active item from a version string (exact or major version), a shortname or an identifier
+ ///
+ /// The v.
+ internal void SelectFromVersion(string? v)
+ {
+ if (string.IsNullOrWhiteSpace(v))
+ {
+ if (ShowDefault)
+ {
+ if (v != null) // v is "" so select the default
+ {
+ SelectDefault();
+ return;
+ }
+ }
+
+ // any NULL value will return the highest version
+ v = visualStudioVersions.HighestVersion().Version;
+ }
+
+ // find either by exact version or by short name containing the version
+ var i = visualStudioVersions.All.FindIndex(x => x.Version == v);
+ if (i >= 0)
+ {
+ SelectedIndex = ShowDefault ? 1 + i : i;
+ return;
+ }
+
+ var k = visualStudioVersions.All.FindIndex(x => x.Version.StartsWith(v));
+ if (k >= 0)
+ {
+ SelectedIndex = ShowDefault ? 1 + k : k;
+ return;
+ }
+
+ // by year
+ var n = visualStudioVersions.All.FindIndex(x => x.ShortName.Contains(v));
+ if (n >= 0)
+ {
+ SelectedIndex = ShowDefault ? 1 + n : n;
+ return;
+ }
+
+ // by identifier
+ var o = visualStudioVersions.All.FindIndex(x => x.Identifier == v);
+ if (o >= 0)
+ {
+ SelectedIndex = ShowDefault ? 1 + o : o;
+ return;
+ }
+
+ SelectedIndex = -1;
+ }
+
+ ///
+ /// Custom draws the item
+ ///
+ /// The sender.
+ /// The e.
+ private void CustomDrawItem(object sender, DrawItemEventArgs e)
+ {
+ // draw the selected item with the Visual Studio Icon and the version as text
+ if (e.Index >= 0 && e.Index <= visualStudioVersions.Count)
+ {
+ e.DrawBackground();
+
+ var height = 16;
+
+ Rectangle iconRect = new Rectangle(e.Bounds.Left + Margin.Left,
+ e.Bounds.Top + (ItemHeight - height) / 2,
+ height, height);
+ if (ShowDefault)
+ {
+ if (e.Index > 0)
+ {
+ e.Graphics.DrawIcon(visualStudioVersions[e.Index - 1].AppIcon, iconRect);
+ e.Graphics.DrawString(visualStudioVersions[e.Index - 1].Name, e.Font, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4);
+ }
+ else
+ {
+ e.Graphics.DrawIcon(Resources.AppLogo, iconRect);
+ e.Graphics.DrawString("", e.Font, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4);
+ }
+ }
+ else
+ {
+ e.Graphics.DrawIcon(visualStudioVersions[e.Index].AppIcon, iconRect);
+ e.Graphics.DrawString(visualStudioVersions[e.Index].Name, e.Font, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4);
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/VSLauncherX/Forms/dlgExecuteVisualStudio.Designer.cs b/VSLauncherX/Forms/dlgExecuteVisualStudio.Designer.cs
index 0a92ed1..5c0ca26 100644
--- a/VSLauncherX/Forms/dlgExecuteVisualStudio.Designer.cs
+++ b/VSLauncherX/Forms/dlgExecuteVisualStudio.Designer.cs
@@ -1,6 +1,8 @@
-namespace VSLauncher
+using VSLauncher.Controls;
+
+namespace VSLauncher
{
- partial class dlgExecuteVisualStudio
+ partial class dlgExecuteVisualStudio
{
///
/// Required designer variable.
diff --git a/VSLauncherX/Forms/dlgSettings.Designer.cs b/VSLauncherX/Forms/dlgSettings.Designer.cs
index ef51df5..19bba36 100644
--- a/VSLauncherX/Forms/dlgSettings.Designer.cs
+++ b/VSLauncherX/Forms/dlgSettings.Designer.cs
@@ -34,6 +34,7 @@ private void InitializeComponent()
chkAlwaysAdmin = new CheckBox();
chkSync = new CheckBox();
chkShowPath = new CheckBox();
+ chkAutostart = new CheckBox();
SuspendLayout();
//
// btnCancel
@@ -44,7 +45,7 @@ private void InitializeComponent()
btnCancel.Location = new Point(334, 261);
btnCancel.Name = "btnCancel";
btnCancel.Size = new Size(100, 40);
- btnCancel.TabIndex = 5;
+ btnCancel.TabIndex = 4;
btnCancel.Text = " Cancel";
btnCancel.TextImageRelation = TextImageRelation.ImageBeforeText;
btnCancel.UseVisualStyleBackColor = true;
@@ -57,7 +58,7 @@ private void InitializeComponent()
btnOk.Location = new Point(440, 261);
btnOk.Name = "btnOk";
btnOk.Size = new Size(100, 40);
- btnOk.TabIndex = 6;
+ btnOk.TabIndex = 5;
btnOk.Text = " Ok";
btnOk.TextImageRelation = TextImageRelation.ImageBeforeText;
btnOk.UseVisualStyleBackColor = true;
@@ -69,30 +70,40 @@ private void InitializeComponent()
chkAlwaysAdmin.Location = new Point(12, 12);
chkAlwaysAdmin.Name = "chkAlwaysAdmin";
chkAlwaysAdmin.Size = new Size(142, 19);
- chkAlwaysAdmin.TabIndex = 7;
+ chkAlwaysAdmin.TabIndex = 0;
chkAlwaysAdmin.Text = "Always start as Admin";
chkAlwaysAdmin.UseVisualStyleBackColor = true;
//
// chkSync
//
chkSync.AutoSize = true;
- chkSync.Location = new Point(12, 37);
+ chkSync.Location = new Point(12, 62);
chkSync.Name = "chkSync";
chkSync.Size = new Size(192, 19);
- chkSync.TabIndex = 7;
+ chkSync.TabIndex = 2;
chkSync.Text = "Synchronize with VS Recent List";
chkSync.UseVisualStyleBackColor = true;
//
// chkShowPath
//
chkShowPath.AutoSize = true;
- chkShowPath.Location = new Point(12, 62);
+ chkShowPath.Location = new Point(12, 87);
chkShowPath.Name = "chkShowPath";
chkShowPath.Size = new Size(219, 19);
- chkShowPath.TabIndex = 7;
+ chkShowPath.TabIndex = 3;
chkShowPath.Text = "Show path for solutions and projects";
chkShowPath.UseVisualStyleBackColor = true;
//
+ // chkAutostart
+ //
+ chkAutostart.AutoSize = true;
+ chkAutostart.Location = new Point(12, 37);
+ chkAutostart.Name = "chkAutostart";
+ chkAutostart.Size = new Size(126, 19);
+ chkAutostart.TabIndex = 1;
+ chkAutostart.Text = "Autostart on logon";
+ chkAutostart.UseVisualStyleBackColor = true;
+ //
// dlgSettings
//
this.AcceptButton = btnOk;
@@ -103,6 +114,7 @@ private void InitializeComponent()
this.ControlBox = false;
this.Controls.Add(chkShowPath);
this.Controls.Add(chkSync);
+ this.Controls.Add(chkAutostart);
this.Controls.Add(chkAlwaysAdmin);
this.Controls.Add(btnCancel);
this.Controls.Add(btnOk);
@@ -126,5 +138,6 @@ private void InitializeComponent()
private CheckBox chkAlwaysAdmin;
private CheckBox chkSync;
private CheckBox chkShowPath;
+ private CheckBox chkAutostart;
}
}
\ No newline at end of file
diff --git a/VSLauncherX/Forms/dlgSettings.cs b/VSLauncherX/Forms/dlgSettings.cs
index 28574bb..a3ba402 100644
--- a/VSLauncherX/Forms/dlgSettings.cs
+++ b/VSLauncherX/Forms/dlgSettings.cs
@@ -23,6 +23,7 @@ public dlgSettings()
private void btnOk_Click(object sender, EventArgs e)
{
Properties.Settings.Default.AlwaysAdmin = chkAlwaysAdmin.Checked;
+ Properties.Settings.Default.AutoStart = chkAutostart.Checked;
Properties.Settings.Default.SynchronizeVS = chkSync.Checked;
Properties.Settings.Default.ShowPathForSolutions = chkShowPath.Checked;
Properties.Settings.Default.Save();
@@ -30,6 +31,7 @@ private void btnOk_Click(object sender, EventArgs e)
private void dlgSettings_Load(object sender, EventArgs e)
{
+ chkAutostart.Checked = Properties.Settings.Default.AutoStart;
chkAlwaysAdmin.Checked = Properties.Settings.Default.AlwaysAdmin;
chkSync.Checked = Properties.Settings.Default.SynchronizeVS;
chkShowPath.Checked = Properties.Settings.Default.ShowPathForSolutions;
diff --git a/VSLauncherX/Forms/dlgSettings.resx b/VSLauncherX/Forms/dlgSettings.resx
index 2d622b3..f5307a3 100644
--- a/VSLauncherX/Forms/dlgSettings.resx
+++ b/VSLauncherX/Forms/dlgSettings.resx
@@ -18,7 +18,7 @@
System.Resources.ResXResourceReader, System.Windows.Forms, ...
System.Resources.ResXResourceWriter, System.Windows.Forms, ...
this is my long stringthis is a comment
- Blue
+ Blue
[base64 mime encoded serialized .NET Framework object]
diff --git a/VSLauncherX/Helpers/AdminInfo.cs b/VSLauncherX/Helpers/AdminInfo.cs
new file mode 100644
index 0000000..85b69e5
--- /dev/null
+++ b/VSLauncherX/Helpers/AdminInfo.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Text;
+using System.Threading.Tasks;
+
+using Newtonsoft.Json.Linq;
+using VSLauncher.Helpers.Tokens;
+using VSLauncherX.Helpers;
+
+namespace VSLauncher.Helpers
+{
+ internal class AdminInfo
+ {
+ private static WindowsIdentity _currentUserIdentity;
+
+ private static WindowsIdentity CachedOwner => _currentUserIdentity ?? (_currentUserIdentity = Owner);
+
+ public static WindowsIdentity Owner => WindowsIdentity.GetCurrent();
+
+ ///
+ /// Is the current user admin.
+ ///
+ /// A bool.
+ public static bool IsCurrentUserAdmin()
+ {
+ return SecurityHelper.IsAdministrator() | SecurityHelper.IsMemberOfLocalAdmins();
+ }
+
+ ///
+ /// Is the app elevated.
+ ///
+ /// A bool.
+ internal static bool IsElevated()
+ {
+ IntegrityLevel currentIntegrity = (IntegrityLevel)SecurityHelper.GetCurrentIntegrityLevel();
+
+ try
+ {
+ TokenProvider tp = TokenProvider.CreateFromCurrentProcessToken();
+ var et = tp.GetTokenElevationType();
+
+ return et == TokenProvider.TokenElevationType.Full;
+ }
+ catch
+ {
+ return currentIntegrity > IntegrityLevel.Medium;
+ }
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/AutoRun.cs b/VSLauncherX/Helpers/AutoRun.cs
new file mode 100644
index 0000000..047ff6f
--- /dev/null
+++ b/VSLauncherX/Helpers/AutoRun.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Security.Principal;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.Win32.TaskScheduler;
+
+namespace VSLauncher.Helpers
+{
+ ///
+ /// The auto run.
+ ///
+ public class AutoRun
+ {
+ private const string FolderName = "VSLauncherX";
+ private const string TaskName = "AppStart for ";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AutoRun()
+ {
+ }
+
+ ///
+ /// Setups the launcher task.
+ ///
+ /// If true, start elevated.
+ /// If true, start as autostart.
+ public static void SetupLauncherTask(bool bElevated, bool asAutostart)
+ {
+ // Get the service on the local machine
+ using (TaskService ts = new TaskService())
+ {
+ TaskFolder folder;
+ 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();
+
+ 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";
+
+ td.Principal.RunLevel = bElevated ? TaskRunLevel.Highest : TaskRunLevel.LUA;
+ td.Principal.LogonType = TaskLogonType.InteractiveToken;
+
+ 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;
+
+ 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);
+ }
+ }
+
+ ///
+ /// Gets a simple user name.
+ ///
+ /// The user.
+ /// A string.
+ private static string GetUserName(WindowsIdentity user)
+ {
+ return user.Name.Replace("\\", "_").Replace(".", "_");
+ }
+
+ ///
+ /// Runs the.
+ ///
+ internal static void Run()
+ {
+ // Get the service on the local machine
+ using (TaskService ts = new TaskService())
+ {
+ TaskFolder folder;
+ var bFolderExists = ts.RootFolder.SubFolders.Any(sf => sf.Name == FolderName);
+
+ if (!bFolderExists)
+ {
+ throw new Exception("Task Folder not found");
+ }
+ else
+ {
+ folder = ts.RootFolder.SubFolders.First(sf => sf.Name == FolderName);
+ }
+ var taskName = TaskName + GetUserName(System.Security.Principal.WindowsIdentity.GetCurrent());
+
+ if (folder.AllTasks.Any(t => t.Name == taskName))
+ {
+ folder.Tasks[taskName].Run();
+ }
+ }
+ }
+
+ ///
+ /// Removes the launcher task.
+ ///
+ internal static void RemoveLauncherTask()
+ {
+ using (TaskService ts = new TaskService())
+ {
+ TaskFolder folder;
+ var bFolderExists = ts.RootFolder.SubFolders.Any(sf => sf.Name == FolderName);
+
+ if (!bFolderExists)
+ {
+ return;
+ }
+
+ folder = ts.RootFolder.SubFolders.First(sf => sf.Name == FolderName);
+
+ var user = System.Security.Principal.WindowsIdentity.GetCurrent();
+ var taskName = TaskName + GetUserName(user);
+
+ // create the task if it doesn't exist
+ if (folder.AllTasks.Any(t => t.Name == taskName))
+ {
+ folder.DeleteTask(taskName);
+ }
+ }
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/ItemLauncher.cs b/VSLauncherX/Helpers/ItemLauncher.cs
new file mode 100644
index 0000000..b1e8010
--- /dev/null
+++ b/VSLauncherX/Helpers/ItemLauncher.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+using Newtonsoft.Json;
+
+using VSLauncher.DataModel;
+
+namespace VSLauncher.Helpers
+{
+ ///
+ /// The item launcher.
+ ///
+ public class ItemLauncher
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item.
+ /// The target.
+ public ItemLauncher(VsItem item, VisualStudioInstance target)
+ {
+ Target = target;
+ SingleItem = item;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item.
+ /// The target.
+ public ItemLauncher(VsFolder item, VisualStudioInstance target)
+ {
+ Solution = item;
+ Target = target;
+ }
+
+ ///
+ /// Gets the launch item.
+ ///
+ public VsFolder? Solution { get; }
+
+ ///
+ /// Gets the single item.
+ ///
+ public VsItem? SingleItem { get; }
+
+ ///
+ /// Gets the target.
+ ///
+ public VisualStudioInstance Target { get; }
+
+ ///
+ /// Gets the last exception.
+ ///
+ public Exception LastException { get; private set; }
+
+ ///
+ /// Launches the.
+ ///
+ /// If true, b force admin.
+ public Task Launch(bool bForceAdmin = false)
+ {
+ // Execute the item, run the before and after items if set, elevate to admin if required
+ return Task.Run(() =>
+ {
+ string s = CreateLaunchInfoString();
+
+ Debug.WriteLine(s);
+ if (Solution is null && SingleItem is null)
+ {
+ throw new NullReferenceException();
+ }
+
+ bool bRequireAdmin = bForceAdmin | (Solution is null ? SingleItem!.RunAsAdmin : Solution.RunAsAdmin);
+ var psi = new ProcessStartInfo
+ {
+ FileName = GetLauncherPath(),
+ Arguments = s,
+ ErrorDialog = false,
+ UseShellExecute = bRequireAdmin,
+ Verb = bRequireAdmin ? "runas" : "run",
+ WindowStyle = ProcessWindowStyle.Hidden,
+ };
+
+ try
+ {
+ var p = Process.Start(psi);
+ }
+ catch (Exception ex)
+ {
+ LastException = ex;
+ Debug.WriteLine(ex.ToString());
+ }
+ });
+ }
+
+ ///
+ /// Gets the launcher path.
+ ///
+ /// A string.
+ public string GetLauncherPath()
+ {
+ string env = Environment.CurrentDirectory;
+
+ // get working directory of current assembly
+ string current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? env;
+
+ return Path.Combine(current, "BackgroundLaunch.exe");
+ }
+
+ ///
+ /// Creates the launch info string.
+ ///
+ /// A string.
+ public string CreateLaunchInfoString()
+ {
+ var li = new LaunchInfo()
+ {
+ Solution = Solution ?? new VsFolder(SingleItem ?? throw new NullReferenceException()),
+ Target = Target.Location
+ };
+
+ JsonSerializerSettings settings = new JsonSerializerSettings()
+ {
+ Formatting = Formatting.None,
+ };
+
+ string s = JsonConvert.SerializeObject(li, settings);
+
+ s = s.Replace('\"', '«').Replace(' ', '»');
+
+ return s;
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/Native/NtDllApi.cs b/VSLauncherX/Helpers/Native/NtDllApi.cs
new file mode 100644
index 0000000..659ed56
--- /dev/null
+++ b/VSLauncherX/Helpers/Native/NtDllApi.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace VSLauncher.Helpers.Native
+{
+ internal static class NtDllApi
+ {
+ internal class NativeMethods
+ {
+ [DllImport("ntdll.dll", SetLastError = true)]
+ public static extern int NtSetInformationProcess(IntPtr hProcess, PROCESS_INFORMATION_CLASS processInformationClass, ref PROCESS_ACCESS_TOKEN processInformation, int processInformationLength);
+
+ [DllImport("ntdll.dll", SetLastError = true)]
+ public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref PROCESS_BASIC_INFORMATION processInformation, int processInformationLength, out int returnLength);
+ }
+
+ public enum PROCESS_INFORMATION_CLASS
+ {
+ ProcessBasicInformation,
+ ProcessQuotaLimits,
+ ProcessIoCounters,
+ ProcessVmCounters,
+ ProcessTimes,
+ ProcessBasePriority,
+ ProcessRaisePriority,
+ ProcessDebugPort,
+ ProcessExceptionPort,
+ ProcessAccessToken,
+ ProcessLdtInformation,
+ ProcessLdtSize,
+ ProcessDefaultHardErrorMode,
+ ProcessIoPortHandlers,
+ ProcessPooledUsageAndLimits,
+ ProcessWorkingSetWatch,
+ ProcessUserModeIOPL,
+ ProcessEnableAlignmentFaultFixup,
+ ProcessPriorityClass,
+ ProcessWx86Information,
+ ProcessHandleCount,
+ ProcessAffinityMask,
+ ProcessPriorityBoost,
+ MaxProcessInfoClass
+ }
+
+ internal struct PROCESS_BASIC_INFORMATION
+ {
+ public uint /*NtStatus*/ ExitStatus;
+ public IntPtr PebBaseAddress;
+ public UIntPtr AffinityMask;
+ public int BasePriority;
+ public UIntPtr UniqueProcessId;
+ public UIntPtr InheritedFromUniqueProcessId;
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct PROCESS_ACCESS_TOKEN
+ {
+ public IntPtr Token;
+ public IntPtr Thread;
+ }
+
+ }
+}
diff --git a/VSLauncherX/Helpers/Native/ProcessApi.cs b/VSLauncherX/Helpers/Native/ProcessApi.cs
new file mode 100644
index 0000000..41ffe41
--- /dev/null
+++ b/VSLauncherX/Helpers/Native/ProcessApi.cs
@@ -0,0 +1,237 @@
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Permissions;
+
+namespace VSLauncher.Helpers.Native
+{
+ ///
+ /// PInvoke signatures for win32 process api
+ ///
+ public static class ProcessApi
+ {
+ internal const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct STARTUPINFOEX
+ {
+ public STARTUPINFO StartupInfo;
+ public IntPtr lpAttributeList;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct STARTUPINFO
+ {
+ public int cb;
+ public string lpReserved;
+ public string lpDesktop;
+ public string lpTitle;
+ public int dwX;
+ public int dwY;
+ public int dwXSize;
+ public int dwYSize;
+ public int dwXCountChars;
+ public int dwYCountChars;
+ public int dwFillAttribute;
+ public int dwFlags;
+ public short wShowWindow;
+ public short cbReserved2;
+ public IntPtr lpReserved2;
+ public IntPtr hStdInput;
+ public IntPtr hStdOutput;
+ public IntPtr hStdError;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct PROCESS_INFORMATION
+ {
+ public IntPtr hProcess;
+ public IntPtr hThread;
+ public int dwProcessId;
+ public int dwThreadId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SECURITY_ATTRIBUTES
+ {
+ public int nLength;
+ public IntPtr lpSecurityDescriptor;
+ public int bInheritHandle;
+ }
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool InitializeProcThreadAttributeList(
+ IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool UpdateProcThreadAttribute(
+ IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue,
+ IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool CreateProcess(
+ string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
+ ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, CreateProcessFlags dwCreationFlags,
+ IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
+ out PROCESS_INFORMATION lpProcessInformation);
+
+ [Flags]
+ public enum CreateProcessFlags : uint
+ {
+ DEBUG_PROCESS = 0x00000001,
+ DEBUG_ONLY_THIS_PROCESS = 0x00000002,
+ CREATE_SUSPENDED = 0x00000004,
+ DETACHED_PROCESS = 0x00000008,
+ CREATE_NEW_CONSOLE = 0x00000010,
+ NORMAL_PRIORITY_CLASS = 0x00000020,
+ IDLE_PRIORITY_CLASS = 0x00000040,
+ HIGH_PRIORITY_CLASS = 0x00000080,
+ REALTIME_PRIORITY_CLASS = 0x00000100,
+ CREATE_NEW_PROCESS_GROUP = 0x00000200,
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400,
+ CREATE_SEPARATE_WOW_VDM = 0x00000800,
+ CREATE_SHARED_WOW_VDM = 0x00001000,
+ CREATE_FORCEDOS = 0x00002000,
+ BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
+ ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
+ INHERIT_PARENT_AFFINITY = 0x00010000,
+ INHERIT_CALLER_PRIORITY = 0x00020000,
+ CREATE_PROTECTED_PROCESS = 0x00040000,
+ EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
+ PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
+ PROCESS_MODE_BACKGROUND_END = 0x00200000,
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
+ CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
+ CREATE_DEFAULT_ERROR_MODE = 0x04000000,
+ CREATE_NO_WINDOW = 0x08000000,
+ PROFILE_USER = 0x10000000,
+ PROFILE_KERNEL = 0x20000000,
+ PROFILE_SERVER = 0x40000000,
+ CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
+ }
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern bool CloseHandle(IntPtr hObject);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ internal static extern bool GetExitCodeProcess(SafeProcessHandle processHandle, out int exitCode);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern bool GetNamedPipeClientProcessId(IntPtr Pipe, out uint ClientProcessId);
+
+ #region PID traversing
+ internal const int ERROR_NO_MORE_FILES = 0x12;
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern SafeSnapshotHandle CreateToolhelp32Snapshot(SnapshotFlags flags, uint id);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern bool Process32First(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern bool Process32Next(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe);
+
+ [Flags]
+ internal enum SnapshotFlags : uint
+ {
+ HeapList = 0x00000001,
+ Process = 0x00000002,
+ Thread = 0x00000004,
+ Module = 0x00000008,
+ Module32 = 0x00000010,
+ All = HeapList | Process | Thread | Module,
+ Inherit = 0x80000000,
+ NoHeaps = 0x40000000
+ }
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct PROCESSENTRY32
+ {
+ public uint dwSize;
+ public uint cntUsage;
+ public uint th32ProcessID;
+ public IntPtr th32DefaultHeapID;
+ public uint th32ModuleID;
+ public uint cntThreads;
+ public uint th32ParentProcessID;
+ public int pcPriClassBase;
+ public uint dwFlags;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szExeFile;
+ };
+
+ [SuppressUnmanagedCodeSecurity]
+ internal sealed class SafeSnapshotHandle : SafeHandleMinusOneIsInvalid
+ {
+ internal SafeSnapshotHandle() : base(true)
+ {
+ }
+
+ [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
+ internal SafeSnapshotHandle(IntPtr handle) : base(true)
+ {
+ SetHandle(handle);
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return CloseHandle(handle);
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
+ private static extern bool CloseHandle(IntPtr handle);
+ }
+ #endregion
+
+ #region Query Process Info
+ public const uint PROCESS_QUERY_INFORMATION = 0x0400;
+ public const uint PROCESS_SET_INFORMATION = 0x0200;
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern bool OpenProcessToken(IntPtr hProcess, uint dwDesiredAccess, out IntPtr hToken);
+ #endregion
+
+ /// Checks whether a process is being debugged.
+ ///
+ /// The "remote" in CheckRemoteDebuggerPresent does not imply that the debugger
+ /// necessarily resides on a different computer; instead, it indicates that the
+ /// debugger resides in a separate and parallel process.
+ ///
+ /// Use the IsDebuggerPresent function to detect whether the calling process
+ /// is running under the debugger.
+ ///
+ [DllImport("Kernel32.dll", SetLastError = true, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool CheckRemoteDebuggerPresent(
+ SafeHandle hProcess,
+ [MarshalAs(UnmanagedType.Bool)] ref bool isDebuggerPresent);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
+ internal static extern IntPtr GetCurrentProcess();
+
+ [DllImport("kernel32.dll")]
+ internal static extern int GetCurrentProcessId();
+
+
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SECURITY_ATTRIBUTES lpPipeAttributes, int nSize);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern uint ResumeThread(IntPtr hThread);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
+ }
+}
diff --git a/VSLauncherX/Helpers/Native/SafeTokenHandle.cs b/VSLauncherX/Helpers/Native/SafeTokenHandle.cs
new file mode 100644
index 0000000..2998b18
--- /dev/null
+++ b/VSLauncherX/Helpers/Native/SafeTokenHandle.cs
@@ -0,0 +1,24 @@
+using Microsoft.Win32.SafeHandles;
+using System;
+
+namespace VSLauncher.Helpers.Native
+{
+ internal class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ internal SafeTokenHandle(IntPtr handle)
+ : base(true)
+ {
+ SetHandle(handle);
+ }
+
+ private SafeTokenHandle()
+ : base(true)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return ProcessApi.CloseHandle(handle);
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/Native/TokensApi.cs b/VSLauncherX/Helpers/Native/TokensApi.cs
new file mode 100644
index 0000000..800b78e
--- /dev/null
+++ b/VSLauncherX/Helpers/Native/TokensApi.cs
@@ -0,0 +1,420 @@
+using System;
+using System.Runtime.InteropServices;
+using static VSLauncher.Helpers.Native.ProcessApi;
+
+namespace VSLauncher.Helpers.Native
+{
+ internal static class TokensApi
+ {
+ public enum LogonFlags
+ {
+ ///
+ /// Log on, then load the user's profile in the HKEY_USERS registry key. The function
+ /// returns after the profile has been loaded. Loading the profile can be time-consuming,
+ /// so it is best to use this value only if you must access the information in the
+ /// HKEY_CURRENT_USER registry key.
+ /// NOTE: Windows Server 2003: The profile is unloaded after the new process has been
+ /// terminated, regardless of whether it has created child processes.
+ ///
+ /// See LOGON_WITH_PROFILE
+ WithProfile = 1,
+ ///
+ /// Log on, but use the specified credentials on the network only. The new process uses the
+ /// same token as the caller, but the system creates a new logon session within LSA, and
+ /// the process uses the specified credentials as the default credentials.
+ /// This value can be used to create a process that uses a different set of credentials
+ /// locally than it does remotely. This is useful in inter-domain scenarios where there is
+ /// no trust relationship.
+ /// The system does not validate the specified credentials. Therefore, the process can start,
+ /// but it may not have access to network resources.
+ ///
+ /// See LOGON_NETCREDENTIALS_ONLY
+ NetCredentialsOnly
+ }
+
+ internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
+
+ internal const int ERROR_NOT_ALL_ASSIGNED = 1300;
+
+ internal const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
+ internal const uint STANDARD_RIGHTS_READ = 0x00020000;
+ internal const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
+ internal const uint TOKEN_DUPLICATE = 0x0002;
+ internal const uint TOKEN_IMPERSONATE = 0x0004;
+ internal const uint TOKEN_QUERY = 0x0008;
+ internal const uint TOKEN_QUERY_SOURCE = 0x0010;
+ internal const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
+ internal const uint TOKEN_ADJUST_GROUPS = 0x0040;
+ internal const uint TOKEN_ADJUST_DEFAULT = 0x0080;
+ internal const uint TOKEN_ADJUST_SESSIONID = 0x0100;
+ internal const uint TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;
+ internal const uint TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
+ TOKEN_ASSIGN_PRIMARY |
+ TOKEN_DUPLICATE |
+ TOKEN_IMPERSONATE |
+ TOKEN_QUERY |
+ TOKEN_QUERY_SOURCE |
+ TOKEN_ADJUST_PRIVILEGES |
+ TOKEN_ADJUST_GROUPS |
+ TOKEN_ADJUST_DEFAULT |
+ TOKEN_ADJUST_SESSIONID;
+
+ [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
+ public static extern bool CreateProcessWithTokenW(IntPtr hToken, LogonFlags dwLogonFlags, string lpApplicationName, string lpCommandLine, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
+
+ public enum SECURITY_IMPERSONATION_LEVEL
+ {
+ SecurityAnonymous = 0,
+ SecurityIdentification = 1,
+ SecurityImpersonation = 2,
+ SecurityDelegation = 3
+ }
+
+ public enum TOKEN_TYPE
+ {
+ TokenPrimary = 1,
+ TokenImpersonation
+ }
+
+ public const uint SE_GROUP_INTEGRITY = 0x00000020;
+
+ [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public extern static bool DuplicateTokenEx(
+ IntPtr hExistingToken,
+ uint dwDesiredAccess,
+ IntPtr lpTokenAttributes,
+ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
+ TOKEN_TYPE TokenType,
+ out SafeTokenHandle phNewToken);
+
+ /*
+ [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ public static extern bool CreateProcessWithLogonW(
+ String userName,
+ String domain,
+ String password,
+ LogonFlags logonFlags,
+ String applicationName,
+ String commandLine,
+ CreateProcessFlags creationFlags,
+ UInt32 environment,
+ String currentDirectory,
+ ref STARTUPINFO startupInfo,
+ out PROCESS_INFORMATION processInformation);
+ */
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool CreateProcessAsUser(
+ SafeTokenHandle hToken,
+ string applicationName,
+ string commandLine,
+ IntPtr pProcessAttributes,
+ IntPtr pThreadAttributes,
+ bool bInheritHandles,
+ uint dwCreationFlags,
+ IntPtr pEnvironment,
+ string currentDirectory,
+ ref STARTUPINFO startupInfo,
+ out PROCESS_INFORMATION processInformation);
+
+ /*
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern Boolean CreateProcessAsUserW(IntPtr hToken, string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, Boolean bInheritHandles, CreateProcessFlags dwCreationFlags, IntPtr lpEnvironment, IntPtr lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInfo);
+ */
+
+ // Integrity Levels
+ public enum TOKEN_INFORMATION_CLASS
+ {
+ ///
+ /// The buffer receives a TOKEN_USER structure that contains the user account of the token.
+ ///
+ TokenUser = 1,
+
+ ///
+ /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token.
+ ///
+ TokenGroups,
+
+ ///
+ /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token.
+ ///
+ TokenPrivileges,
+
+ ///
+ /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects.
+ ///
+ TokenOwner,
+
+ ///
+ /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects.
+ ///
+ TokenPrimaryGroup,
+
+ ///
+ /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects.
+ ///
+ TokenDefaultDacl,
+
+ ///
+ /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information.
+ ///
+ TokenSource,
+
+ ///
+ /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token.
+ ///
+ TokenType,
+
+ ///
+ /// The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails.
+ ///
+ TokenImpersonationLevel,
+
+ ///
+ /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics.
+ ///
+ TokenStatistics,
+
+ ///
+ /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token.
+ ///
+ TokenRestrictedSids,
+
+ ///
+ /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token.
+ ///
+ TokenSessionId,
+
+ ///
+ /// The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token.
+ ///
+ TokenGroupsAndPrivileges,
+
+ ///
+ /// Reserved.
+ ///
+ TokenSessionReference,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag.
+ ///
+ TokenSandBoxInert,
+
+ ///
+ /// Reserved.
+ ///
+ TokenAuditPolicy,
+
+ ///
+ /// The buffer receives a TOKEN_ORIGIN value.
+ ///
+ TokenOrigin,
+
+ ///
+ /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token.
+ ///
+ TokenElevationType,
+
+ ///
+ /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token.
+ ///
+ TokenLinkedToken,
+
+ ///
+ /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated.
+ ///
+ TokenElevation,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered.
+ ///
+ TokenHasRestrictions,
+
+ ///
+ /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token.
+ ///
+ TokenAccessInformation,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token.
+ ///
+ TokenVirtualizationAllowed,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token.
+ ///
+ TokenVirtualizationEnabled,
+
+ ///
+ /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level.
+ ///
+ TokenIntegrityLevel,
+
+ ///
+ /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set.
+ ///
+ TokenUIAccess,
+
+ ///
+ /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy.
+ ///
+ TokenMandatoryPolicy,
+
+ ///
+ /// The buffer receives the token's logon security identifier (SID).
+ ///
+ TokenLogonSid,
+
+ ///
+ /// The maximum value for this enumeration
+ ///
+ MaxTokenInfoClass
+ }
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern bool GetTokenInformation(
+ IntPtr TokenHandle,
+ TOKEN_INFORMATION_CLASS TokenInformationClass,
+ IntPtr TokenInformation,
+ int TokenInformationLength,
+ out int ReturnLength);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TOKEN_MANDATORY_LABEL
+ {
+ public SID_AND_ATTRIBUTES Label;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TOKEN_LINKED_TOKEN
+ {
+ public IntPtr LinkedToken;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SID_AND_ATTRIBUTES
+ {
+ public IntPtr Sid;
+ public uint Attributes;
+ }
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern IntPtr GetSidSubAuthority(IntPtr pSid, int nSubAuthority);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern IntPtr GetSidSubAuthorityCount(IntPtr pSid);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ public static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
+ IntPtr TokenInformation, uint TokenInformationLength);
+
+ [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ public static extern bool ConvertStringSidToSid(
+ string StringSid,
+ out IntPtr ptrSid
+ );
+
+ [DllImport("advapi32.dll")]
+ public static extern int GetLengthSid(IntPtr pSid);
+
+ #region Safer
+
+ [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
+ public static extern bool SaferCreateLevel(
+ SaferScopes dwScopeId,
+ SaferLevels dwLevelId,
+ int OpenFlags,
+ out IntPtr pLevelHandle,
+ IntPtr lpReserved);
+
+ [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
+ public static extern bool SaferCloseLevel(
+ IntPtr pLevelHandle);
+
+ [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
+ public static extern bool SaferComputeTokenFromLevel(
+ IntPtr levelHandle,
+ IntPtr inAccessToken,
+ out SafeTokenHandle outAccessToken,
+ SaferComputeTokenFlags dwFlags,
+ IntPtr lpReserved
+ );
+
+ [Flags]
+ public enum SaferLevels : uint
+ {
+ Disallowed = 0,
+ Untrusted = 0x1000,
+ Constrained = 0x10000,
+ NormalUser = 0x20000,
+ FullyTrusted = 0x40000
+ }
+
+ [Flags]
+ public enum SaferComputeTokenFlags : uint
+ {
+ None = 0x0,
+ NullIfEqual = 0x1,
+ CompareOnly = 0x2,
+ MakeIntert = 0x4,
+ WantFlags = 0x8
+ }
+
+ [Flags]
+ public enum SaferScopes : uint
+ {
+ Machine = 1,
+ User = 2
+ }
+ #endregion
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool CreateRestrictedToken(
+ SafeTokenHandle ExistingTokenHandle,
+ uint Flags,
+ uint DisableSidCount,
+ IntPtr SidsToDisable,
+ uint DeletePrivilegeCount,
+ IntPtr PrivilegesToDelete,
+ uint RestrictedSidCount,
+ IntPtr SidsToRestrict,
+ out SafeTokenHandle NewTokenHandle
+ );
+
+ #region AdjustToken
+ /*
+ const int ANYSIZE_ARRAY = 1;
+ public struct TOKEN_PRIVILEGES
+ {
+ public int PrivilegeCount;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = ANYSIZE_ARRAY)]
+ public LUID_AND_ATTRIBUTES[] Privileges;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ public struct LUID_AND_ATTRIBUTES
+ {
+ public LUID Luid;
+ public UInt32 Attributes;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LUID
+ {
+ public UInt32 LowPart;
+ public Int32 HighPart;
+ }
+
+ [DllImport("advapi32.dll")]
+ public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
+ */
+ #endregion
+
+ [DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ internal static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string StringSecurityDescriptor, uint StringSDRevision, out IntPtr SecurityDescriptor, out UIntPtr SecurityDescriptorSize);
+
+ }
+}
diff --git a/VSLauncherX/Helpers/ProcessHelper.cs b/VSLauncherX/Helpers/ProcessHelper.cs
new file mode 100644
index 0000000..1006cbc
--- /dev/null
+++ b/VSLauncherX/Helpers/ProcessHelper.cs
@@ -0,0 +1,141 @@
+using static VSLauncher.Helpers.Native.ProcessApi;
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using System.Threading;
+using VSLauncher.Helpers.Native;
+
+namespace VSLauncherX.Helpers
+{
+ ///
+ /// Borrowed from gsudo
+ ///
+ internal static class ProcessHelper
+ {
+ private static string _cacheOwnExeName;
+
+ public static WindowsIdentity GetProcessUser(this Process process)
+ {
+ IntPtr processHandle = IntPtr.Zero;
+ try
+ {
+ OpenProcessToken(process.Handle, 8, out processHandle);
+ WindowsIdentity wi = new WindowsIdentity(processHandle);
+ return wi;
+ }
+ catch
+ {
+ return null;
+ }
+ finally
+ {
+ if (processHandle != IntPtr.Zero)
+ {
+ CloseHandle(processHandle);
+ }
+ }
+ }
+
+ static internal int GetProcessIntegrityLevel(IntPtr processHandle)
+ {
+ /*
+ * https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/bb625963(v=msdn.10)?redirectedfrom=MSDN
+ * https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
+ S-1-16-0 Untrusted Mandatory Level An untrusted integrity level.
+ S-1-16-4096 Low Mandatory Level A low integrity level.
+ S-1-16-8192 Medium Mandatory Level A medium integrity level.
+ S-1-16-8448 Medium Plus Mandatory Level A medium plus integrity level.
+ S-1-16-12288 High Mandatory Level A high integrity level.
+ S-1-16-16384 System Mandatory Level A system integrity level.
+ S-1-16-20480 Protected Process Mandatory Level A protected-process integrity level.
+ S-1-16-28672 Secure Process Mandatory Level A secure process integrity level.
+ */
+ int IL = -1;
+ //SafeWaitHandle hToken = null;
+ IntPtr hToken = IntPtr.Zero;
+
+ int cbTokenIL = 0;
+ IntPtr pTokenIL = IntPtr.Zero;
+
+ try
+ {
+ // Open the access token of the current process with TOKEN_QUERY.
+ if (!OpenProcessToken(processHandle,
+ TokensApi.TOKEN_QUERY, out hToken))
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ // Then we must query the size of the integrity level information
+ // associated with the token. Note that we expect GetTokenInformation
+ // to return false with the ERROR_INSUFFICIENT_BUFFER error code
+ // because we've given it a null buffer. On exit cbTokenIL will tell
+ // the size of the group information.
+ if (!TokensApi.GetTokenInformation(hToken,
+ TokensApi.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, IntPtr.Zero, 0,
+ out cbTokenIL))
+ {
+ int error = Marshal.GetLastWin32Error();
+ const int ERROR_INSUFFICIENT_BUFFER = 0x7a;
+ if (error != ERROR_INSUFFICIENT_BUFFER)
+ {
+ // When the process is run on operating systems prior to
+ // Windows Vista, GetTokenInformation returns false with the
+ // ERROR_INVALID_PARAMETER error code because
+ // TokenIntegrityLevel is not supported on those OS's.
+ throw new Win32Exception(error);
+ }
+ }
+
+ // Now we allocate a buffer for the integrity level information.
+ pTokenIL = Marshal.AllocHGlobal(cbTokenIL);
+ if (pTokenIL == IntPtr.Zero)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ // Now we ask for the integrity level information again. This may fail
+ // if an administrator has added this account to an additional group
+ // between our first call to GetTokenInformation and this one.
+ if (!TokensApi.GetTokenInformation(hToken,
+ TokensApi.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pTokenIL, cbTokenIL,
+ out cbTokenIL))
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ // Marshal the TOKEN_MANDATORY_LABEL struct from native to .NET object.
+ TokensApi.TOKEN_MANDATORY_LABEL tokenIL = (TokensApi.TOKEN_MANDATORY_LABEL)
+ Marshal.PtrToStructure(pTokenIL, typeof(TokensApi.TOKEN_MANDATORY_LABEL));
+
+ IntPtr pIL = TokensApi.GetSidSubAuthority(tokenIL.Label.Sid, 0);
+ IL = Marshal.ReadInt32(pIL);
+ }
+ finally
+ {
+ // Centralized cleanup for all allocated resources. Clean up only
+ // those which were allocated, and clean them up in the right order.
+
+ if (hToken != IntPtr.Zero)
+ {
+ CloseHandle(hToken);
+ // Marshal.FreeHGlobal(hToken);
+ // hToken.Close();
+ // hToken = null;
+ }
+
+ if (pTokenIL != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(pTokenIL);
+ pTokenIL = IntPtr.Zero;
+ cbTokenIL = 0;
+ }
+ }
+
+ return IL;
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/SecurityHelper.cs b/VSLauncherX/Helpers/SecurityHelper.cs
new file mode 100644
index 0000000..12ac371
--- /dev/null
+++ b/VSLauncherX/Helpers/SecurityHelper.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Linq;
+using System.Security.Principal;
+using VSLauncher.Helpers.Tokens;
+using VSLauncher.Helpers.Native;
+
+namespace VSLauncherX.Helpers
+{
+ ///
+ /// Borrowed from gsudo
+ ///
+ internal static class SecurityHelper
+ {
+ public static bool IsMemberOfLocalAdmins()
+ {
+ var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
+ var claims = principal.Claims;
+ return claims.Any(c => c.Value == "S-1-5-32-544");
+ }
+
+ private static int? _cacheGetCurrentIntegrityLevelCache;
+ public static bool IsHighIntegrity()
+ {
+ return GetCurrentIntegrityLevel() >= (int)IntegrityLevel.High;
+ }
+
+ ///
+ /// The function gets the integrity level of the current process.
+ ///
+ ///
+ /// Returns the integrity level of the current process. It is usually one of
+ /// these values:
+ ///
+ /// SECURITY_MANDATORY_UNTRUSTED_RID - means untrusted level
+ /// SECURITY_MANDATORY_LOW_RID - means low integrity level.
+ /// SECURITY_MANDATORY_MEDIUM_RID - means medium integrity level.
+ /// SECURITY_MANDATORY_HIGH_RID - means high integrity level.
+ /// SECURITY_MANDATORY_SYSTEM_RID - means system integrity level.
+ ///
+ ///
+ ///
+ /// When any native Windows API call fails, the function throws a Win32Exception
+ /// with the last error code.
+ ///
+ static internal int GetCurrentIntegrityLevel()
+ {
+ if (_cacheGetCurrentIntegrityLevelCache.HasValue)
+ {
+ return _cacheGetCurrentIntegrityLevelCache.Value;
+ }
+
+ _cacheGetCurrentIntegrityLevelCache = ProcessHelper.GetProcessIntegrityLevel(ProcessApi.GetCurrentProcess());
+
+ return _cacheGetCurrentIntegrityLevelCache.Value;
+ }
+
+ private static bool? _cacheIsAdmin;
+
+ public static bool IsAdministrator()
+ {
+ if (_cacheIsAdmin.HasValue) return _cacheIsAdmin.Value;
+
+ try
+ {
+ WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ _cacheIsAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
+
+ return _cacheIsAdmin.Value;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/Tokens/IntegrityLevel.cs b/VSLauncherX/Helpers/Tokens/IntegrityLevel.cs
new file mode 100644
index 0000000..30b4978
--- /dev/null
+++ b/VSLauncherX/Helpers/Tokens/IntegrityLevel.cs
@@ -0,0 +1,31 @@
+using static VSLauncher.Helpers.Native.TokensApi;
+
+namespace VSLauncher.Helpers.Tokens
+{
+ public enum IntegrityLevel
+ {
+ Untrusted = 0,
+ Low = 4096,
+ Medium = 8192,
+ MediumRestricted = 8193, // Experimental, like medium but without the split token. It can call RunAs but won't elevate.
+ MediumPlus = 8448,
+ High = 12288,
+ System = 16384,
+ Protected = 20480,
+ Secure = 28672
+ }
+
+ static class IntegrityLevelExtensions
+ {
+ public static SaferLevels ToSaferLevel(this IntegrityLevel integrityLevel)
+ {
+ if (integrityLevel >= IntegrityLevel.High)
+ return SaferLevels.FullyTrusted;
+ if (integrityLevel >= IntegrityLevel.Medium)
+ return SaferLevels.NormalUser;
+ if (integrityLevel >= IntegrityLevel.Low)
+ return SaferLevels.Constrained;
+ return SaferLevels.Untrusted;
+ }
+ }
+}
\ No newline at end of file
diff --git a/VSLauncherX/Helpers/Tokens/NativeMethods.cs b/VSLauncherX/Helpers/Tokens/NativeMethods.cs
new file mode 100644
index 0000000..6239e24
--- /dev/null
+++ b/VSLauncherX/Helpers/Tokens/NativeMethods.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using static VSLauncher.Helpers.Native.TokensApi;
+using VSLauncher.Helpers.Native;
+
+namespace VSLauncher.Helpers.Tokens
+{
+ internal static partial class NativeMethods
+ {
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint Bufferlength, IntPtr PreviousState, IntPtr ReturnLength);
+
+ internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
+ internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
+ internal const int ERROR_NOT_ALL_ASSIGNED = 0x00000514;
+
+ internal const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
+ internal const uint STANDARD_RIGHTS_READ = 0x00020000;
+ internal const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
+ internal const uint TOKEN_DUPLICATE = 0x0002;
+ internal const uint TOKEN_IMPERSONATE = 0x0004;
+ internal const uint TOKEN_QUERY = 0x0008;
+ internal const uint TOKEN_QUERY_SOURCE = 0x0010;
+ internal const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
+ internal const uint TOKEN_ADJUST_GROUPS = 0x0040;
+ internal const uint TOKEN_ADJUST_DEFAULT = 0x0080;
+ internal const uint TOKEN_ADJUST_SESSIONID = 0x0100;
+ internal const uint TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;
+ internal const uint TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
+ TOKEN_ASSIGN_PRIMARY |
+ TOKEN_DUPLICATE |
+ TOKEN_IMPERSONATE |
+ TOKEN_QUERY |
+ TOKEN_QUERY_SOURCE |
+ TOKEN_ADJUST_PRIVILEGES |
+ TOKEN_ADJUST_GROUPS |
+ TOKEN_ADJUST_DEFAULT |
+ TOKEN_ADJUST_SESSIONID;
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ internal static extern IntPtr GetCurrentProcess();
+
+ [DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool OpenProcessToken(IntPtr processHandle,
+ uint desiredAccesss,
+ out IntPtr tokenHandle);
+
+ [DllImport("Advapi32.dll", SetLastError = true)]
+ internal static extern bool OpenThreadToken(IntPtr ThreadToken, TokenAccessLevels DesiredAccess, bool OpenAsSelf, out SafeTokenHandle TokenHandle);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern IntPtr GetCurrentThread();
+
+ [DllImport("Advapi32.dll", SetLastError = true)]
+ public static extern bool SetThreadToken(IntPtr Thread, SafeTokenHandle Token);
+
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool CloseHandle(IntPtr hObject);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LUID
+ {
+ private uint lowPart;
+ private int highPart;
+
+ public uint LowPart { get => lowPart; set => lowPart = value; }
+
+ public int HighPart { get => highPart; set => highPart = value; }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LUID_AND_ATTRIBUTES
+ {
+ private LUID luid;
+ private uint attributes;
+
+ public LUID Luid { get => luid; set => luid = value; }
+
+ public uint Attributes { get => attributes; set => attributes = value; }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TOKEN_PRIVILEGES
+ {
+ private uint privilegeCount;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
+ private LUID_AND_ATTRIBUTES[] privileges;
+
+ public uint PrivilegeCount { get => privilegeCount; set => privilegeCount = value; }
+
+ public LUID_AND_ATTRIBUTES[] Privileges { get => privileges; set => privileges = value; }
+ }
+ }
+}
diff --git a/VSLauncherX/Helpers/Tokens/Privilege.cs b/VSLauncherX/Helpers/Tokens/Privilege.cs
new file mode 100644
index 0000000..fb6c689
--- /dev/null
+++ b/VSLauncherX/Helpers/Tokens/Privilege.cs
@@ -0,0 +1,41 @@
+namespace VSLauncher.Helpers.Tokens
+{
+ public enum Privilege
+ {
+ SeAssignPrimaryTokenPrivilege,
+ SeAuditPrivilege,
+ SeBackupPrivilege,
+ SeChangeNotifyPrivilege,
+ SeCreateGlobalPrivilege,
+ SeCreatePagefilePrivilege,
+ SeCreatePermanentPrivilege,
+ SeCreateSymbolicLinkPrivilege,
+ SeCreateTokenPrivilege,
+ SeDebugPrivilege,
+ SeEnableDelegationPrivilege,
+ SeImpersonatePrivilege,
+ SeIncreaseBasePriorityPrivilege,
+ SeIncreaseQuotaPrivilege,
+ SeIncreaseWorkingSetPrivilege,
+ SeLoadDriverPrivilege,
+ SeLockMemoryPrivilege,
+ SeMachineAccountPrivilege,
+ SeManageVolumePrivilege,
+ SeProfileSingleProcessPrivilege,
+ SeRelabelPrivilege,
+ SeRemoteShutdownPrivilege,
+ SeRestorePrivilege,
+ SeSecurityPrivilege,
+ SeShutdownPrivilege,
+ SeSyncAgentPrivilege,
+ SeSystemEnvironmentPrivilege,
+ SeSystemProfilePrivilege,
+ SeSystemtimePrivilege,
+ SeTakeOwnershipPrivilege,
+ SeTcbPrivilege,
+ SeTimeZonePrivilege,
+ SeTrustedCredManAccessPrivilege,
+ SeUndockPrivilege,
+ SeDelegateSessionUserImpersonatePrivilege,
+ }
+}
diff --git a/VSLauncherX/Helpers/Tokens/PrivilegeManager.cs b/VSLauncherX/Helpers/Tokens/PrivilegeManager.cs
new file mode 100644
index 0000000..8849671
--- /dev/null
+++ b/VSLauncherX/Helpers/Tokens/PrivilegeManager.cs
@@ -0,0 +1,75 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using static VSLauncher.Helpers.Native.TokensApi;
+using static VSLauncher.Helpers.Tokens.NativeMethods;
+
+namespace VSLauncher.Helpers.Tokens
+{
+ //Enable a privilege in the current thread by implementing the following line in your code:
+ //PrivilegeManager.EnablePrivilege(SecurityEntity.SE_SHUTDOWN_NAME);
+
+ public static class PrivilegeManager
+ {
+ public static void DisableAllPrivileges(IntPtr tokenHandle)
+ {
+ var TOKEN_PRIVILEGES = new TOKEN_PRIVILEGES();
+
+ if (!AdjustTokenPrivileges(tokenHandle, true, ref TOKEN_PRIVILEGES, 0, IntPtr.Zero, IntPtr.Zero))
+ throw new Win32Exception();
+ }
+
+ public static void SetPrivilegeState(Privilege securityEntity, bool enabled)
+ {
+ const int ERROR_NO_TOKEN = 0x3f0;
+
+ var locallyUniqueIdentifier = new LUID();
+
+ if (!LookupPrivilegeValue(null, securityEntity.ToString(), ref locallyUniqueIdentifier))
+ throw new Win32Exception();
+
+ if (!OpenThreadToken(GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, out var token))
+ {
+ var error = Marshal.GetLastWin32Error();
+ if (error != ERROR_NO_TOKEN)
+ {
+ throw new Win32Exception(error);
+ }
+
+ // No token is on the thread, copy from process
+ if (!OpenProcessToken(GetCurrentProcess(), (uint)TokenAccessLevels.Duplicate, out var processToken))
+ {
+ throw new Win32Exception();
+ }
+
+ if (!DuplicateTokenEx(processToken, (uint)(TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges),
+ IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out token))
+ {
+ throw new Win32Exception();
+ }
+
+ if (!SetThreadToken(IntPtr.Zero, token))
+ {
+ throw new Win32Exception();
+ }
+ }
+
+ var tp = new TOKEN_PRIVILEGES();
+ tp.PrivilegeCount = 1;
+
+ tp.Privileges = new LUID_AND_ATTRIBUTES[1];
+ tp.Privileges[0].Attributes = (uint)(enabled ? NativeMethods.SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_DISABLED);
+ tp.Privileges[0].Luid = locallyUniqueIdentifier;
+
+ if (!AdjustTokenPrivileges(token.DangerousGetHandle(), false, ref tp, (uint)Marshal.SizeOf(tp),
+ IntPtr.Zero, IntPtr.Zero))
+ throw new Win32Exception();
+
+ Debug.WriteLine($"Privilege {securityEntity} was {(enabled ? "ENABLED" : "DISABLED")}");
+ token.Close();
+ // todo: proper close.
+ }
+ }
+}
\ No newline at end of file
diff --git a/VSLauncherX/Helpers/Tokens/TokenProvider.cs b/VSLauncherX/Helpers/Tokens/TokenProvider.cs
new file mode 100644
index 0000000..c1c1134
--- /dev/null
+++ b/VSLauncherX/Helpers/Tokens/TokenProvider.cs
@@ -0,0 +1,433 @@
+using System;
+using static VSLauncher.Helpers.Native.ProcessApi;
+using static VSLauncher.Helpers.Native.TokensApi;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Linq;
+using System.Security.Principal;
+using VSLauncherX.Helpers;
+using VSLauncher.Helpers.Native;
+
+namespace VSLauncher.Helpers.Tokens
+{
+ internal class TokenProvider : IDisposable
+ {
+ public const uint MAXIMUM_ALLOWED = 0x02000000;
+
+ private SafeTokenHandle Token;
+ public SafeTokenHandle GetToken() => Token;
+
+ private TokenProvider()
+ {
+ }
+
+ public static TokenProvider CreateFromSystemAccount()
+ {
+ var winlogon = Process.GetProcesses().Where(p => p.ProcessName == "winlogon").FirstOrDefault();
+ return CreateFromProcessToken(winlogon.Id);
+ }
+
+ public static TokenProvider CreateFromProcessToken(int pidWithToken, uint tokenAccess = MAXIMUM_ALLOWED)
+ {
+ IntPtr existingProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION, true, (uint)pidWithToken);
+ if (existingProcessHandle == IntPtr.Zero)
+ {
+ throw new Win32Exception();
+ }
+
+ IntPtr existingProcessToken;
+ try
+ {
+ if (!OpenProcessToken(existingProcessHandle,
+ MAXIMUM_ALLOWED
+ //| TOKEN_ALL_ACCESS,
+ //| TOKEN_DUPLICATE
+ //| TokensApi.TOKEN_ADJUST_DEFAULT |
+ //| TokensApi.TOKEN_QUERY
+ //| TokensApi.TOKEN_ASSIGN_PRIMARY
+ //| TOKEN_QUERY_SOURCE
+ //| TOKEN_IMPERSONATE
+ //| TOKEN_READ
+ //| TOKEN_ALL_ACCESS ==> access denied
+ //| STANDARD_RIGHTS_REQUIRED ==> access denied.
+ ,
+ out existingProcessToken))
+ {
+ throw new Win32Exception();
+ }
+ }
+ finally
+ {
+ CloseHandle(existingProcessHandle);
+ }
+
+ if (existingProcessToken == IntPtr.Zero) return null;
+
+ var sa = new SECURITY_ATTRIBUTES();
+ sa.nLength = 0;
+ uint desiredAccess = MAXIMUM_ALLOWED;
+
+ SafeTokenHandle newToken;
+
+ if (!DuplicateTokenEx(existingProcessToken, desiredAccess, IntPtr.Zero,
+ SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out newToken))
+ {
+ CloseHandle(existingProcessToken);
+ throw new Win32Exception();
+ }
+
+ CloseHandle(existingProcessToken);
+
+ return new TokenProvider()
+ {
+ Token = newToken
+ };
+ }
+
+ public static TokenProvider CreateFromSaferApi(SaferLevels saferLevel)
+ {
+ IntPtr hSaferLevel;
+ if (!SaferCreateLevel(SaferScopes.User, saferLevel, 1, out hSaferLevel, IntPtr.Zero))
+ throw new Win32Exception();
+
+ SafeTokenHandle hToken;
+ try
+ {
+ if (!SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken,
+ SaferComputeTokenFlags.None, IntPtr.Zero))
+ throw new Win32Exception();
+ }
+ finally
+ {
+ SaferCloseLevel(hSaferLevel);
+ }
+
+ return new TokenProvider() { Token = hToken };
+ }
+
+ public static TokenProvider CreateFromCurrentProcessToken(uint access =
+ TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT |
+ TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY
+ | TOKEN_ADJUST_PRIVILEGES
+ | TOKEN_ADJUST_GROUPS)
+ {
+ var tm = OpenCurrentProcessToken(access);
+ return tm.Duplicate(MAXIMUM_ALLOWED);
+ }
+
+ public TokenProvider Duplicate(uint desiredAccess = 0x02000000)
+ {
+ if (!DuplicateTokenEx(Token.DangerousGetHandle(), desiredAccess, IntPtr.Zero,
+ SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out var newToken))
+ throw new Win32Exception();
+
+ Token.Close();
+ Token = newToken;
+ return this;
+ }
+
+ public static TokenProvider OpenCurrentProcessToken(uint access =
+ TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT |
+ TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY
+ | TOKEN_ADJUST_PRIVILEGES
+ | TOKEN_ADJUST_GROUPS)
+ {
+ IntPtr existingProcessToken;
+
+ if (!OpenProcessToken(Process.GetCurrentProcess().Handle,
+ access
+ // internal const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
+ ,
+ out existingProcessToken))
+ throw new Win32Exception();
+
+ if (existingProcessToken == IntPtr.Zero)
+ throw new Win32Exception();
+
+ return new TokenProvider() { Token = new SafeTokenHandle(existingProcessToken) };
+ }
+
+ public TokenProvider GetLinkedToken(uint desiredAccess = MAXIMUM_ALLOWED)
+ {
+ // Now we allocate a buffer for the integrity level information.
+ int cb = Marshal.SizeOf();
+ var pLinkedToken = Marshal.AllocHGlobal(cb);
+ if (pLinkedToken == IntPtr.Zero)
+ {
+ throw new Win32Exception();
+ }
+
+ try
+ {
+ // Now we ask for the integrity level information again. This may fail
+ // if an administrator has added this account to an additional group
+ // between our first call to GetTokenInformation and this one.
+ if (!GetTokenInformation(Token.DangerousGetHandle(),
+ TOKEN_INFORMATION_CLASS.TokenLinkedToken, pLinkedToken, cb,
+ out cb))
+ {
+ throw new Win32Exception();
+ }
+
+ // Marshal the TOKEN_MANDATORY_LABEL struct from native to .NET object.
+ TOKEN_LINKED_TOKEN linkedTokenStruct = (TOKEN_LINKED_TOKEN)
+ Marshal.PtrToStructure(pLinkedToken, typeof(TOKEN_LINKED_TOKEN));
+
+ var sa = new SECURITY_ATTRIBUTES();
+ sa.nLength = 0;
+
+ SafeTokenHandle newToken;
+
+ if (!DuplicateTokenEx(linkedTokenStruct.LinkedToken, desiredAccess, IntPtr.Zero,
+ SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out newToken))
+ {
+ throw new Win32Exception();
+ }
+
+ CloseHandle(linkedTokenStruct.LinkedToken);
+ Token.Close();
+ Token = newToken;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pLinkedToken);
+ }
+
+ return this;
+ }
+
+ internal static TokenProvider CreateUnelevated(IntegrityLevel level)
+ {
+ if (SecurityHelper.IsAdministrator())
+ {
+ // Have you impersonated system first?
+ if (!WindowsIdentity.GetCurrent().IsSystem)
+ {
+ try
+ {
+ return
+ CreateFromSystemAccount()
+ .EnablePrivilege(Privilege.SeAssignPrimaryTokenPrivilege, true)
+ .Impersonate(() =>
+ {
+ return CreateFromCurrentProcessToken().GetLinkedToken().Duplicate()
+ .SetIntegrity(level);
+ });
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine("Unable to get unelevated token, will try SaferApi Token. " + e.ToString());
+ return CreateFromSaferApi(SaferLevels.NormalUser);
+ }
+ }
+ else
+ {
+ // IntPtr hwnd = ConsoleApi.GetShellWindow();
+ // _ = ConsoleApi.GetWindowThreadProcessId(hwnd, out uint pid);
+ return CreateFromProcessToken(0);
+ }
+ }
+ else
+ {
+ return CreateFromCurrentProcessToken();
+ }
+ }
+
+ public TokenProvider SetIntegrity(IntegrityLevel integrityLevel)
+ {
+ return SetIntegrity((int)integrityLevel);
+ }
+
+ public TokenProvider SetIntegrity(int integrityLevel)
+ {
+ string integritySid =
+ "S-1-16-" + integrityLevel.ToString(System.Globalization.CultureInfo.InvariantCulture);
+ IntPtr pIntegritySid;
+ if (!ConvertStringSidToSid(integritySid, out pIntegritySid))
+ throw new Win32Exception();
+
+ TOKEN_MANDATORY_LABEL TIL = new TOKEN_MANDATORY_LABEL();
+ TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
+ TIL.Label.Sid = pIntegritySid;
+
+ var pTIL = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(TIL, pTIL, false);
+
+ if (!SetTokenInformation(Token.DangerousGetHandle(),
+ TOKEN_INFORMATION_CLASS.TokenIntegrityLevel,
+ pTIL,
+ (uint)(Marshal.SizeOf() + GetLengthSid(pIntegritySid))))
+ throw new Win32Exception();
+
+ return this;
+ }
+
+ ///
+ /// Use this only to restrict a non-elevated token.
+ /// This method is useless if you are already elevated because it can't
+ /// remove you from Admin's group. (You are still administrator able to write in C:\windows).
+ ///
+ ///
+ public TokenProvider RestrictTokenMaxPrivilege(bool ignoreErrors = false)
+ {
+ uint DISABLE_MAX_PRIVILEGE = 0x1;
+ uint LUA_TOKEN = 0x4;
+ SafeTokenHandle result;
+
+ if (!CreateRestrictedToken(
+ Token,
+ LUA_TOKEN | DISABLE_MAX_PRIVILEGE,
+ //WRITE_RESTRICTED,
+ 0,
+ IntPtr.Zero, //pSA,
+ 0,
+ IntPtr.Zero,
+ 0,
+ IntPtr.Zero,
+ out result) && !ignoreErrors)
+ throw new Win32Exception();
+
+ Token.Close();
+ Token = result;
+ return this;
+ }
+
+ public TokenProvider Impersonate(Action ActionImpersonated)
+ {
+ WindowsIdentity.RunImpersonated(new Microsoft.Win32.SafeHandles.SafeAccessTokenHandle(GetToken().DangerousGetHandle()), ActionImpersonated);
+
+ /* using (var ctx = System.Security.Principal.WindowsIdentity.Impersonate(GetToken().DangerousGetHandle()))
+ {
+ try
+ {
+ ActionImpersonated();
+ }
+ finally
+ {
+ ctx.Undo();
+ }
+ }
+
+ */
+ return this;
+ }
+
+ public T Impersonate(Func ActionImpersonated)
+
+ {
+ T res = default;
+ WindowsIdentity.RunImpersonated(new Microsoft.Win32.SafeHandles.SafeAccessTokenHandle(GetToken().DangerousGetHandle()), () => { res = ActionImpersonated(); });
+ return res;
+ }
+
+ public TokenProvider EnablePrivileges(bool throwOnFailure, params Privilege[] priviledgesList)
+ {
+ // todo: rewrite to use just 1 api call, handle exceptions,
+ foreach (var priv in priviledgesList)
+ EnablePrivilege(priv, throwOnFailure);
+
+ return this;
+ }
+
+ public TokenProvider EnablePrivilege(Privilege securityEntity, bool throwOnFailure)
+ {
+ // todo: rewrite to use just 1 api call, handle exceptions,
+ var locallyUniqueIdentifier = new NativeMethods.LUID();
+
+ if (!NativeMethods.LookupPrivilegeValue(null, securityEntity.ToString(), ref locallyUniqueIdentifier))
+ throw new Win32Exception();
+
+ var tp = new NativeMethods.TOKEN_PRIVILEGES();
+ tp.PrivilegeCount = 1;
+ tp.Privileges = new NativeMethods.LUID_AND_ATTRIBUTES[1];
+ tp.Privileges[0].Attributes = NativeMethods.SE_PRIVILEGE_ENABLED;
+ tp.Privileges[0].Luid = locallyUniqueIdentifier;
+
+ if (!NativeMethods.AdjustTokenPrivileges(Token.DangerousGetHandle(), false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
+ if (throwOnFailure)
+ throw new Win32Exception();
+
+ // if (throwOnFailure && Microsoft.Win32.GetLastError() != 0)
+ // throw new Win32Exception(); //throw new Exception("The token does not have the specified privilege. \n");
+
+ return this;
+ }
+
+ public TokenProvider SetSessionId(int sessionId)
+ {
+ int size = Marshal.SizeOf();
+ IntPtr pValue = Marshal.AllocHGlobal(size);
+ try
+ {
+ Marshal.WriteInt32(pValue, sessionId);
+
+ if (!SetTokenInformation(
+ Token.DangerousGetHandle(), TOKEN_INFORMATION_CLASS.TokenSessionId,
+ pValue, (uint)size))
+ throw new Win32Exception();
+
+ return this;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pValue);
+ }
+ }
+
+ public static TokenElevationType GetTokenElevationType(IntPtr token)
+ {
+ int size = Marshal.SizeOf();
+ IntPtr pValue = Marshal.AllocHGlobal(size);
+ if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenElevationType, pValue, size, out size))
+ {
+ throw new Win32Exception();
+ }
+
+ return (TokenElevationType)Marshal.ReadInt32(pValue);
+ }
+
+ public TokenElevationType GetTokenElevationType()
+ {
+ int size = Marshal.SizeOf();
+ IntPtr pValue = Marshal.AllocHGlobal(size);
+ if (!GetTokenInformation(Token.DangerousGetHandle(), TOKEN_INFORMATION_CLASS.TokenElevationType, pValue, size, out size))
+ {
+ throw new Win32Exception();
+ }
+
+ return (TokenElevationType)Marshal.ReadInt32(pValue);
+ }
+
+ public int GetSessionId()
+ {
+ int size = Marshal.SizeOf();
+ IntPtr pValue = Marshal.AllocHGlobal(size);
+ try
+ {
+ if (!GetTokenInformation(Token.DangerousGetHandle(), TOKEN_INFORMATION_CLASS.TokenSessionId, pValue, size, out size))
+ {
+ throw new Win32Exception();
+ }
+
+ return Marshal.ReadInt32(pValue);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pValue);
+ }
+ }
+
+ public void Dispose()
+ {
+ }
+
+ internal enum TokenElevationType : uint
+ {
+ Default = 1u,
+ Full,
+ Limited
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/VSLauncherX/ItemLauncher.cs b/VSLauncherX/ItemLauncher.cs
deleted file mode 100644
index 7964160..0000000
--- a/VSLauncherX/ItemLauncher.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-
-using Newtonsoft.Json;
-
-using VSLauncher.DataModel;
-
-namespace VSLauncher
-{
- ///
- /// The item launcher.
- ///
- public class ItemLauncher
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The item.
- /// The target.
- public ItemLauncher(VsItem item, VisualStudioInstance target)
- {
- this.Target = target;
- this.SingleItem = item;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The item.
- /// The target.
- public ItemLauncher(VsFolder item, VisualStudioInstance target)
- {
- this.Solution = item;
- this.Target = target;
- }
-
- ///
- /// Gets the launch item.
- ///
- public VsFolder? Solution { get; }
-
- ///
- /// Gets the single item.
- ///
- public VsItem? SingleItem { get; }
-
- ///
- /// Gets the target.
- ///
- public VisualStudioInstance Target { get; }
-
- ///
- /// Gets the last exception.
- ///
- public Exception LastException { get; private set; }
-
- ///
- /// Launches the.
- ///
- /// If true, b force admin.
- public Task Launch(bool bForceAdmin = false)
- {
- // Execute the item, run the before and after items if set, elevate to admin if required
- return Task.Run(() =>
- {
- string s = CreateLaunchInfoString();
-
- Debug.WriteLine(s);
- if(this.Solution is null && this.SingleItem is null)
- {
- throw new NullReferenceException();
- }
-
- bool bRequireAdmin = bForceAdmin | (this.Solution is null ? this.SingleItem!.RunAsAdmin : this.Solution.RunAsAdmin);
- var psi = new System.Diagnostics.ProcessStartInfo
- {
- FileName = this.GetLauncherPath(),
- Arguments = s,
- ErrorDialog = false,
- UseShellExecute = bRequireAdmin,
- Verb = bRequireAdmin ? "runas" : "run",
- WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
- };
-
- try
- {
- var p = System.Diagnostics.Process.Start(psi);
- }
- catch (Exception ex)
- {
- this.LastException = ex;
- Debug.WriteLine(ex.ToString());
- }
- });
- }
-
- ///
- /// Gets the launcher path.
- ///
- /// A string.
- public string GetLauncherPath()
- {
- string env = Environment.CurrentDirectory;
-
- // get working directory of current assembly
- string current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? env;
-
- return Path.Combine(current, "BackgroundLaunch.exe");
- }
-
- ///
- /// Creates the launch info string.
- ///
- /// A string.
- public string CreateLaunchInfoString()
- {
- var li = new LaunchInfo()
- {
- Solution = this.Solution ?? new VsFolder(this.SingleItem ?? throw new NullReferenceException()),
- Target = this.Target.Location
- };
-
- JsonSerializerSettings settings = new JsonSerializerSettings()
- {
- Formatting = Formatting.None,
- };
-
- string s = JsonConvert.SerializeObject(li, settings);
-
- s = s.Replace('\"', '«').Replace(' ', '»');
-
- return s;
- }
- }
-}
diff --git a/VSLauncherX/MainDialog.Designer.cs b/VSLauncherX/MainDialog.Designer.cs
index e0f3943..8345117 100644
--- a/VSLauncherX/MainDialog.Designer.cs
+++ b/VSLauncherX/MainDialog.Designer.cs
@@ -4,7 +4,7 @@
namespace VSLauncher
{
- partial class MainDialog
+ partial class MainDialog
{
///
/// Required designer variable.
diff --git a/VSLauncherX/MainDialog.cs b/VSLauncherX/MainDialog.cs
index 30bb812..850081b 100644
--- a/VSLauncherX/MainDialog.cs
+++ b/VSLauncherX/MainDialog.cs
@@ -15,13 +15,14 @@
using LibGit2Sharp;
using Windows.Devices.Geolocation;
using static System.Windows.Forms.AxHost;
+using System.Runtime.InteropServices;
namespace VSLauncher
{
- ///
- /// The main dialog.
- ///
- public partial class MainDialog : Form
+ ///
+ /// The main dialog.
+ ///
+ public partial class MainDialog : Form
{
///
/// used to indicate that some internal update is going on
@@ -188,17 +189,31 @@ private void InitializeListview()
// this.olvFiles.SetObjects(list);
}
+ [DllImport("shell32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool IsUserAnAdmin();
+
///
/// Initializes a new instance of the class.
///
public MainDialog()
{
-
InitializeComponent();
InitializeListview();
SetupDragAndDrop();
- // BuildTestData();
+ bool bAdmin = AdminInfo.IsCurrentUserAdmin();
+ bool bElevated = AdminInfo.IsElevated();
+
+ if(bAdmin || bElevated)
+ {
+ if(bAdmin && bElevated)
+ this.Text += $" (Administrator, Elevated)";
+ else if(bAdmin)
+ this.Text += $" (Administrator)";
+ else
+ this.Text += $" (Elevated)";
+ }
this.bInUpdate = true;
@@ -573,9 +588,43 @@ private void mainSettings_Click(object sender, EventArgs e)
if (dlg.ShowDialog() == DialogResult.OK)
{
+ if(Properties.Settings.Default.AlwaysAdmin || Properties.Settings.Default.AutoStart)
+ {
+ if (Properties.Settings.Default.AlwaysAdmin)
+ {
+ RestartOurselves();
+ Application.Exit();
+ }
+ else
+ {
+ Program.UpdateTaskScheduler();
+ }
+ }
+ else
+ {
+ Program.RemoveTaskScheduler();
+ }
+
_ = SolutionData_OnChanged(true);
}
}
+
+ ///
+ /// Restarts the application to register it with admin rights.
+ ///
+ private void RestartOurselves()
+ {
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = Assembly.GetExecutingAssembly().Location.Replace(".dll", ".exe"),
+ Arguments = "register",
+ Verb = "runas",
+ UseShellExecute = true
+ };
+
+ Process.Start(startInfo);
+ }
+
#endregion
#region ListView event handling
diff --git a/VSLauncherX/Program.cs b/VSLauncherX/Program.cs
index 7af985c..cf9d603 100644
--- a/VSLauncherX/Program.cs
+++ b/VSLauncherX/Program.cs
@@ -1,5 +1,9 @@
+using System.Diagnostics;
+
using VSLauncher;
using VSLauncher.DataModel;
+using VSLauncher.Helpers;
+using VSLauncher.Properties;
namespace VSLauncher
{
@@ -24,14 +28,42 @@ static void Main()
VisualStudioFileIcons16.GetIcon("Solution");
VisualStudioFileIcons32.GetIcon("Solution");
- Application.Run(new MainDialog());
+ // add logic to start either normal, or as part of autostart, with or without elevation
+ var args = Environment.GetCommandLineArgs();
+
+ if((args.Length > 1 && args[1] == "autostart") || Debugger.IsAttached)
+ {
+ // we have command line arguments, so we are started as part of autostart
+ Application.Run(new MainDialog());
+ }
+ else
+ {
+ if(args.Length > 1 && args[1] == "register")
+ {
+ // at this point the app was started with admin privs, so we can register the task
+ UpdateTaskScheduler();
+ }
+
+ if (!Settings.Default.AlwaysAdmin)
+ {
+ Application.Run(new MainDialog());
+ }
+ else
+ {
+ // we are started normally
+ AutoRun.Run();
+ }
+ }
}
- ///
- /// Tests the.
- ///
- private static void Test()
+ internal static void UpdateTaskScheduler()
+ {
+ AutoRun.SetupLauncherTask(Settings.Default.AlwaysAdmin, Settings.Default.AutoStart);
+ }
+
+ internal static void RemoveTaskScheduler()
{
+ AutoRun.RemoveLauncherTask();
}
}
}
\ No newline at end of file
diff --git a/VSLauncherX/Properties/Settings.Designer.cs b/VSLauncherX/Properties/Settings.Designer.cs
index 8ad31d9..32d7074 100644
--- a/VSLauncherX/Properties/Settings.Designer.cs
+++ b/VSLauncherX/Properties/Settings.Designer.cs
@@ -154,5 +154,17 @@ public bool ColumnOptionsVisible {
this["ColumnOptionsVisible"] = value;
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("False")]
+ public bool AutoStart {
+ get {
+ return ((bool)(this["AutoStart"]));
+ }
+ set {
+ this["AutoStart"] = value;
+ }
+ }
}
}
diff --git a/VSLauncherX/Properties/Settings.settings b/VSLauncherX/Properties/Settings.settings
index d08b45c..7332391 100644
--- a/VSLauncherX/Properties/Settings.settings
+++ b/VSLauncherX/Properties/Settings.settings
@@ -35,5 +35,8 @@
True
+
+ False
+
\ No newline at end of file
diff --git a/VSLauncherX/VSLauncherX.csproj b/VSLauncherX/VSLauncherX.csproj
index 0411b4b..d0ee4ec 100644
--- a/VSLauncherX/VSLauncherX.csproj
+++ b/VSLauncherX/VSLauncherX.csproj
@@ -51,8 +51,11 @@
+
+
+
diff --git a/VSLauncherX/VisualStudioCombobox.cs b/VSLauncherX/VisualStudioCombobox.cs
deleted file mode 100644
index 01dd86d..0000000
--- a/VSLauncherX/VisualStudioCombobox.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using VSLauncher.DataModel;
-
-namespace VSLauncher
-{
- ///
- /// The visual studio combobox.
- ///
- public class VisualStudioCombobox : ComboBox
- {
- private VisualStudioInstanceManager visualStudioVersions = new VisualStudioInstanceManager();
- private bool showDefault;
-
- public VisualStudioCombobox()
- {
- this.DrawMode = DrawMode.OwnerDrawFixed;
- this.DropDownStyle = ComboBoxStyle.DropDownList;
- this.IntegralHeight = false;
- this.ItemHeight = 26;
- this.DrawItem += CustomDrawItem;
-
- this.Items.AddRange(this.visualStudioVersions.All.ToArray());
- }
-
- ///
- /// Gets or sets a value indicating whether to show default.
- ///
- public bool ShowDefault
- {
- get => showDefault;
- set
- {
- showDefault = value;
-
- if (value)
- {
- this.Items.Insert(0, "");
- }
- else
- {
- if (this.Items.Count > 0 && this.Items[0] is string)
- {
- this.Items.RemoveAt(0);
- }
- }
- }
- }
-
- ///
- /// Gets the versions.
- ///
- public List Versions { get { return visualStudioVersions.All; } }
-
- ///
- /// Gets the selected item.
- ///
- public new VisualStudioInstance? SelectedItem
- {
- get
- {
- if(this.ShowDefault)
- {
- return base.SelectedIndex > 0 ? (VisualStudioInstance)base.SelectedItem : null;
- }
-
- return (VisualStudioInstance)base.SelectedItem;
- }
- set
- {
- base.SelectedItem = value;
- }
- }
-
- ///
- /// Gets or sets the selected index, accounting for the default item which will result in -1
- ///
- public new int SelectedIndex
- {
- get
- {
- return this.ShowDefault ? base.SelectedIndex - 1 : base.SelectedIndex;
- }
- set
- {
- base.SelectedIndex = value;
- }
- }
-
- ///
- /// Gets a value indicating whether default is selected.
- ///
- public bool IsDefaultSelected
- {
- get
- {
- return this.ShowDefault && base.SelectedIndex == 0;
- }
- }
-
- ///
- /// Selects the default item if existing
- ///
- public void SelectDefault()
- {
- if(ShowDefault)
- {
- this.SelectedIndex = 0;
- }
- }
-
- protected override void OnDropDown(EventArgs e)
- {
- this.Items.Clear();
-
- if (ShowDefault)
- {
- this.Items.Add("");
- }
-
- this.Items.AddRange(this.visualStudioVersions.All.ToArray());
-
- base.OnDropDown(e);
- }
-
- ///
- /// Selects the active item from a version string (exact or major version), a shortname or an identifier
- ///
- /// The v.
- internal void SelectFromVersion(string? v)
- {
- if(string.IsNullOrWhiteSpace(v))
- {
- if (ShowDefault)
- {
- if (v != null) // v is "" so select the default
- {
- SelectDefault();
- return;
- }
- }
-
- // any NULL value will return the highest version
- v = this.visualStudioVersions.HighestVersion().Version;
- }
-
- // find either by exact version or by short name containing the version
- var i = this.visualStudioVersions.All.FindIndex(x => x.Version == v);
- if (i >= 0)
- {
- this.SelectedIndex = ShowDefault ? 1 + i : i;
- return;
- }
-
- var k = this.visualStudioVersions.All.FindIndex(x => x.Version.StartsWith(v));
- if (k >= 0)
- {
- this.SelectedIndex = ShowDefault ? 1 + k : k;
- return;
- }
-
- // by year
- var n = this.visualStudioVersions.All.FindIndex(x => x.ShortName.Contains(v));
- if (n >= 0)
- {
- this.SelectedIndex = ShowDefault ? 1 + n : n;
- return;
- }
-
- // by identifier
- var o = this.visualStudioVersions.All.FindIndex(x => x.Identifier == v);
- if (o >= 0)
- {
- this.SelectedIndex = ShowDefault ? 1 + o : o;
- return;
- }
-
- this.SelectedIndex = -1;
- }
-
- ///
- /// Custom draws the item
- ///
- /// The sender.
- /// The e.
- private void CustomDrawItem(object sender, DrawItemEventArgs e)
- {
- // draw the selected item with the Visual Studio Icon and the version as text
- if (e.Index >= 0 && e.Index <= this.visualStudioVersions.Count)
- {
- e.DrawBackground();
-
- var height = 16;
-
- Rectangle iconRect = new Rectangle(e.Bounds.Left + this.Margin.Left,
- e.Bounds.Top + ((this.ItemHeight - height) / 2),
- height, height);
- if(ShowDefault)
- {
- if(e.Index > 0)
- {
- e.Graphics.DrawIcon(visualStudioVersions[e.Index - 1].AppIcon, iconRect);
- e.Graphics.DrawString(visualStudioVersions[e.Index - 1].Name, e.Font, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4);
- }
- else
- {
- e.Graphics.DrawIcon(Resources.AppLogo, iconRect);
- e.Graphics.DrawString("", e.Font, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4);
- }
- }
- else
- {
- e.Graphics.DrawIcon(visualStudioVersions[e.Index].AppIcon, iconRect);
- e.Graphics.DrawString(visualStudioVersions[e.Index].Name, e.Font, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4);
- }
- }
- }
-
- }
-}
\ No newline at end of file