External Tools Visual Studio - c#

I created an application to run, but build an External Tool from the application for Visual Studio. Up until 15.4.0 the application worked fine, but they modified the registry. Based on Microsoft's changes, the External Tools are saved under this sub key:
SOFTWARE\Microsoft\VisualStudio\14.0_Config\External Tools
When you observe the key, you clearly see folders (sub keys) of tools, Guid, Error Lookup, and a couple others that are included with Visual Studio. The issue though, if you manually create the tool in Visual Studio the key information is dumped directly to the root External Tool key.
So if I do either of these approaches:
var user = RegistryKey.OpenBaseKey(RegistryHive.CurrenUser, RegistryView.Default);
var tools = user.OpenSubKey(#"SOFTWARE\Microsoft\VisualStudio\14.0_Config\External Tools", true);
var path = Environment.GetCommandLineArgs()[0];
tools.SetValue("ToolArg", "$(ItemPath)");
tools.SetValue("ToolCmd", path);
tools.SetValue("ToolDir", String.Empty);
tools.SetValue("ToolOpt", 0x12);
tools.SetValue("ToolsSourceKey", String.Empty);
tools.SetValue("ToolTitle", "My Tool");
var user = RegistryKey.OpenBaseKey(RegistryHive.CurrenUser, RegistryView.Default);
var tools = user.OpenSubKey(#"SOFTWARE\Microsoft\VisualStudio\14.0_Config\External Tools", true);
var sub = tools.CreateSubKey("MyTool");
var path = Environment.GetCommandLineArgs()[0];
sub.SetValue("ToolArg", "$(ItemPath)");
sub.SetValue("ToolCmd", path);
sub.SetValue("ToolDir", String.Empty);
sub.SetValue("ToolOpt", 0x12);
sub.SetValue("ToolsSourceKey", String.Empty);
sub.SetValue("ToolTitle", "My Tool");
The tool doesn't appear in the list, or toolbar. Is there something different for Visual Studio 2017 15.5.* that makes this no longer work from code? To make matters worst, the key doesn't always appear when created in Visual Studio 2017 manually.

In Visual Studio 2017, External Tools are stored in a private registry hive in the user's local application data folder. If you run Sysinternals Process Monitor tool, you'll see Visual Studio reading/writing to a key that starts with \REGISTRY\A\ - that's how you know it's a private registry hive. To update them, you will need to load that registry hive by P/Invoking RegLoadAppKey and attaching to the resulting handle. An example of that can be found here:
RegLoadAppKey working fine on 32-bit OS, failing on 64-bit OS, even if both processes are 32-bit
The title may seem misleading at first, but the example given in the question shows exactly how to call RegLoadAppKey and open a sub key underneath.
The next thing you'll have to contend with is finding the private registry hive. Visual Studio stores the private registry hive in a sub folder of the user's local application data folder. The sub folder name will start with Microsoft\VisualStudio\15.0_ and will then be followed by a 32-bit hexadecimal value. I'm not really sure what that value is, or how to gracefully discover it. It's different for each user. My approach was to select the newest folder that starts with "15.0" and assume it's correct. If someone has a better way to identify this folder, I would love to see it.
I've dubbed the combination of version number and hexadecimal string a "version tag". You'll need to keep track of it, because the version tag will be used again as a sub key in the private registry hive.
Putting it all together, I created a VisualStudioContext class that locates the private registry hive and loads the root key.
public class VisualStudioContext : IDisposable
{
public string VersionTag { get; }
public string UserFolder { get; }
public string PrivateRegistryPath { get; }
public SafeRegistryHandle RegistryHandle { get; }
public RegistryKey RootKey { get; }
private static readonly Lazy<VisualStudioContext> LazyInstance = new Lazy<VisualStudioContext>(() => new VisualStudioContext());
public static VisualStudioContext Instance => LazyInstance.Value;
private VisualStudioContext()
{
try
{
string localAppDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string vsFolder = $"{localAppDataFolder}\\Microsoft\\VisualStudio";
var vsFolderInfo = new DirectoryInfo(vsFolder);
DateTime lastDateTime = DateTime.MinValue;
foreach (DirectoryInfo dirInfo in vsFolderInfo.GetDirectories("15.0_*"))
{
if (dirInfo.CreationTime <= lastDateTime)
continue;
UserFolder = dirInfo.FullName;
lastDateTime = dirInfo.CreationTime;
}
if (UserFolder == null)
throw new Exception($"No Visual Studio folders found in \"{vsFolder}\"");
}
catch (Exception ex)
{
throw new Exception("Unable to open Visual Studio folder", ex);
}
VersionTag = Path.GetFileName(UserFolder);
PrivateRegistryPath = $"{UserFolder}\\privateregistry.bin";
int handle = RegistryNativeMethods.RegLoadAppKey(PrivateRegistryPath);
RegistryHandle = new SafeRegistryHandle(new IntPtr(handle), true);
RootKey = RegistryKey.FromHandle(RegistryHandle);
}
public void Dispose()
{
RootKey?.Close();
RegistryHandle?.Dispose();
}
public class Exception : ApplicationException
{
public Exception(string message) : base(message)
{
}
public Exception(string message, Exception innerException) : base(message, innerException)
{
}
}
internal static class RegistryNativeMethods
{
[Flags]
public enum RegSAM
{
AllAccess = 0x000f003f
}
private const int REG_PROCESS_APPKEY = 0x00000001;
// approximated from pinvoke.net's RegLoadKey and RegOpenKey
// NOTE: changed return from long to int so we could do Win32Exception on it
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int RegLoadAppKey(String hiveFile, out int hKey, RegSAM samDesired, int options, int reserved);
public static int RegLoadAppKey(String hiveFile)
{
int hKey;
int rc = RegLoadAppKey(hiveFile, out hKey, RegSAM.AllAccess, REG_PROCESS_APPKEY, 0);
if (rc != 0)
{
throw new Win32Exception(rc, "Failed during RegLoadAppKey of file " + hiveFile);
}
return hKey;
}
}
}
You can use it open the External Tools key like this:
using (var context = VisualStudioContext.Instance)
{
RegistryKey keyExternalTools =
context.RootKey.OpenSubKey($"Software\\Microsoft\\VisualStudio\\{context.VersionTag}\\External Tools", true);
// Do something interesting here
}

It seems you can register external tools in VS 2017 and 2019 by adding a <UserCreatedTool> in the <ExternalTools> section of the Settings/CurrentSettings.vssettings file in the local %AppData% directory of the respective VS version.
Simply create an external tool in VS manually, and search for the entry you created.
This describes how I found out ;)

Related

How to programmatically get information about a running/installed application in windows [duplicate]

How to get the applications installed in the system using c# code?
Iterating through the registry key "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" seems to give a comprehensive list of installed applications.
Aside from the example below, you can find a similar version to what I've done here.
This is a rough example, you'll probaby want to do something to strip out blank rows like in the 2nd link provided.
string registry_key = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using(Microsoft.Win32.RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key))
{
foreach(string subkey_name in key.GetSubKeyNames())
{
using(RegistryKey subkey = key.OpenSubKey(subkey_name))
{
Console.WriteLine(subkey.GetValue("DisplayName"));
}
}
}
Alternatively, you can use WMI as has been mentioned:
ManagementObjectSearcher mos = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
foreach(ManagementObject mo in mos.Get())
{
Console.WriteLine(mo["Name"]);
}
But this is rather slower to execute, and I've heard it may only list programs installed under "ALLUSERS", though that may be incorrect. It also ignores the Windows components & updates, which may be handy for you.
I wanted to be able to extract a list of apps just as they appear in the start menu. Using the registry, I was getting entries that do not show up in the start menu.
I also wanted to find the exe path and to extract an icon to eventually make a nice looking launcher. Unfortunately, with the registry method this is kind of a hit and miss since my observations are that this information isn't reliably available.
My alternative is based around the shell:AppsFolder which you can access by running explorer.exe shell:appsFolder and which lists all apps, including store apps, currently installed and available through the start menu. The issue is that this is a virtual folder that can't be accessed with System.IO.Directory. Instead, you would have to use native shell32 commands. Fortunately, Microsoft published the Microsoft.WindowsAPICodePack-Shell on Nuget which is a wrapper for the aforementioned commands. Enough said, here's the code:
// GUID taken from https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);
foreach (var app in (IKnownFolder)appsFolder)
{
// The friendly app name
string name = app.Name;
// The ParsingName property is the AppUserModelID
string appUserModelID = app.ParsingName; // or app.Properties.System.AppUserModel.ID
// You can even get the Jumbo icon in one shot
ImageSource icon = app.Thumbnail.ExtraLargeBitmapSource;
}
And that's all there is to it. You can also start the apps using
System.Diagnostics.Process.Start("explorer.exe", #" shell:appsFolder\" + appModelUserID);
This works for regular Win32 apps and UWP store apps. How about them apples.
Since you are interested in listing all installed apps, it is reasonable to expect that you might want to monitor for new apps or uninstalled apps as well, which you can do using the ShellObjectWatcher:
ShellObjectWatcher sow = new ShellObjectWatcher(appsFolder, false);
sow.AllEvents += (s, e) => DoWhatever();
sow.Start();
Edit: One might also be interested in knowing that the AppUserMoedlID mentioned above is the unique ID Windows uses to group windows in the taskbar.
2022: Tested in Windows 11 and still works great. Windows 11 also seems to cache apps that aren't installed per se, portable apps that don't need installing, for example. They appear in the start menu search results and can also be retrieved from shell:appsFolder as well.
I agree that enumerating through the registry key is the best way.
Note, however, that the key given, #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", will list all applications in a 32-bit Windows installation, and 64-bit applications in a Windows 64-bit installation.
In order to also see 32-bit applications installed on a Windows 64-bit installation, you would also need to enumeration the key #"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall".
You can take a look at this article. It makes use of registry to read the list of installed applications.
public void GetInstalledApps()
{
string uninstallKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(uninstallKey))
{
foreach (string skName in rk.GetSubKeyNames())
{
using (RegistryKey sk = rk.OpenSubKey(skName))
{
try
{
lstInstalled.Items.Add(sk.GetValue("DisplayName"));
}
catch (Exception ex)
{ }
}
}
}
}
While the accepted solution works, it is not complete. By far.
If you want to get all the keys, you need to take into consideration 2 more things:
x86 & x64 applications do not have access to the same registry.
Basically x86 cannot normally access x64 registry. And some
applications only register to the x64 registry.
and
some applications actually install into the CurrentUser registry instead of the LocalMachine
With that in mind, I managed to get ALL installed applications using the following code, WITHOUT using WMI
Here is the code:
List<string> installs = new List<string>();
List<string> keys = new List<string>() {
#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
#"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};
// The RegistryView.Registry64 forces the application to open the registry as x64 even if the application is compiled as x86
FindInstalls(RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64), keys, installs);
FindInstalls(RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64), keys, installs);
installs = installs.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList();
installs.Sort(); // The list of ALL installed applications
private void FindInstalls(RegistryKey regKey, List<string> keys, List<string> installed)
{
foreach (string key in keys)
{
using (RegistryKey rk = regKey.OpenSubKey(key))
{
if (rk == null)
{
continue;
}
foreach (string skName in rk.GetSubKeyNames())
{
using (RegistryKey sk = rk.OpenSubKey(skName))
{
try
{
installed.Add(Convert.ToString(sk.GetValue("DisplayName")));
}
catch (Exception ex)
{ }
}
}
}
}
}
it's worth noting that the Win32_Product WMI class represents products as they are installed by Windows Installer. not every application use windows installer
however "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" represents applications for 32 bit. For 64 bit you also need to traverse "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" and since not every software has a 64 bit version the total applications installed are a union of keys on both locations that have "UninstallString" Value with them.
but the best options remains the same .traverse registry keys is a better approach since every application have an entry in registry[including the ones in Windows Installer].however the registry method is insecure as if anyone removes the corresponding key then you will not know the Application entry.On the contrary Altering the HKEY_Classes_ROOT\Installers is more tricky as it is linked with licensing issues such as Microsoft office or other products.
for more robust solution you can always combine registry alternative with the WMI.
string[] registryKeys = new string[] {
#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
#"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" };
public class InstalledApp
{
public string DisplayName { get; set; }
public string DisplayIcon { get; set; }
public string Version { get; set; }
public string InstallLocation { get; set; }
}
private void AddInstalledAppToResultView(RegistryHive hive, RegistryView view, string registryKey,Dictionary<string,InstalledApp> resultView)
{
using (var key = RegistryKey.OpenBaseKey(hive, view).OpenSubKey(registryKey))
{
foreach (string subKeyName in key.GetSubKeyNames())
{
using (RegistryKey subkey = key.OpenSubKey(subKeyName))
{
var displayName = subkey.GetValue("DisplayName");
var displayIcon = subkey.GetValue("DisplayIcon");
if (displayName == null || displayIcon == null)
continue;
var app = new InstalledApp
{
DisplayName = (string)displayName,
DisplayIcon = (string)displayIcon,
InstallLocation = (string)subkey.GetValue("InstallLocation"),
Version = (string)subkey.GetValue("DisplayVersion")
};
if(!resultView.ContainsKey(app.DisplayName))
{
resultView.Add(app.DisplayName,app);
}
}
}
}
}
void Main()
{
var result = new Dictionary<string,InstalledApp>();
var view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32;
AddInstalledAppToResultView(RegistryHive.LocalMachine, view, registryKeys[0],result);
AddInstalledAppToResultView(RegistryHive.CurrentUser, view, registryKeys[0],result);
AddInstalledAppToResultView(RegistryHive.LocalMachine, RegistryView.Registry64, registryKeys[1],result);
Console.WriteLine("==============" + result.Count + "=================");
result.Values.ToList().ForEach(item => Console.WriteLine(item));
}
Iterate through "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" keys and check their "DisplayName" values.
Use Windows Installer API!
It allows to make reliable enumeration of all programs. Registry is not reliable, but WMI is heavyweight.
The object for the list:
public class InstalledProgram
{
public string DisplayName { get; set; }
public string Version { get; set; }
public string InstalledDate { get; set; }
public string Publisher { get; set; }
public string UnninstallCommand { get; set; }
public string ModifyPath { get; set; }
}
The call for creating the list:
List<InstalledProgram> installedprograms = new List<InstalledProgram>();
string registry_key = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key))
{
foreach (string subkey_name in key.GetSubKeyNames())
{
using (RegistryKey subkey = key.OpenSubKey(subkey_name))
{
if (subkey.GetValue("DisplayName") != null)
{
installedprograms.Add(new InstalledProgram
{
DisplayName = (string)subkey.GetValue("DisplayName"),
Version = (string)subkey.GetValue("DisplayVersion"),
InstalledDate = (string)subkey.GetValue("InstallDate"),
Publisher = (string)subkey.GetValue("Publisher"),
UnninstallCommand = (string)subkey.GetValue("UninstallString"),
ModifyPath = (string)subkey.GetValue("ModifyPath")
});
}
}
}
}
As others have pointed out, the accepted answer does not return both x86 and x64 installs. Below is my solution for that. It creates a StringBuilder, appends the registry values to it (with formatting), and writes its output to a text file:
const string FORMAT = "{0,-100} {1,-20} {2,-30} {3,-8}\n";
private void LogInstalledSoftware()
{
var line = string.Format(FORMAT, "DisplayName", "Version", "Publisher", "InstallDate");
line += string.Format(FORMAT, "-----------", "-------", "---------", "-----------");
var sb = new StringBuilder(line, 100000);
ReadRegistryUninstall(ref sb, RegistryView.Registry32);
sb.Append($"\n[64 bit section]\n\n{line}");
ReadRegistryUninstall(ref sb, RegistryView.Registry64);
File.WriteAllText(#"c:\temp\log.txt", sb.ToString());
}
private static void ReadRegistryUninstall(ref StringBuilder sb, RegistryView view)
{
const string REGISTRY_KEY = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view);
using var subKey = baseKey.OpenSubKey(REGISTRY_KEY);
foreach (string subkey_name in subKey.GetSubKeyNames())
{
using RegistryKey key = subKey.OpenSubKey(subkey_name);
if (!string.IsNullOrEmpty(key.GetValue("DisplayName") as string))
{
var line = string.Format(FORMAT,
key.GetValue("DisplayName"),
key.GetValue("DisplayVersion"),
key.GetValue("Publisher"),
key.GetValue("InstallDate"));
sb.Append(line);
}
key.Close();
}
subKey.Close();
baseKey.Close();
}
Your best bet is to use WMI. Specifically the Win32_Product class.
Might I suggest you take a look at WMI (Windows Management Instrumentation).
If you add the System.Management reference to your C# project, you'll gain access to the class `ManagementObjectSearcher', which you will probably find useful.
There are various WMI Classes for Installed Applications, but if it was installed with Windows Installer, then the Win32_Product class is probably best suited to you.
ManagementObjectSearcher s = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
I used Nicks approach - I needed to check whether the Remote Tools for Visual Studio are installed or not, it seems a bit slow, but in a seperate thread this is fine for me. - here my extended code:
private bool isRdInstalled() {
ManagementObjectSearcher p = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
foreach (ManagementObject program in p.Get()) {
if (program != null && program.GetPropertyValue("Name") != null && program.GetPropertyValue("Name").ToString().Contains("Microsoft Visual Studio 2012 Remote Debugger")) {
return true;
}
if (program != null && program.GetPropertyValue("Name") != null) {
Trace.WriteLine(program.GetPropertyValue("Name"));
}
}
return false;
}
My requirement is to check if specific software is installed in my system. This solution works as expected. It might help you. I used a windows application in c# with visual studio 2015.
private void Form1_Load(object sender, EventArgs e)
{
object line;
string softwareinstallpath = string.Empty;
string registry_key = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (var baseKey = Microsoft.Win32.RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
{
using (var key = baseKey.OpenSubKey(registry_key))
{
foreach (string subkey_name in key.GetSubKeyNames())
{
using (var subKey = key.OpenSubKey(subkey_name))
{
line = subKey.GetValue("DisplayName");
if (line != null && (line.ToString().ToUpper().Contains("SPARK")))
{
softwareinstallpath = subKey.GetValue("InstallLocation").ToString();
listBox1.Items.Add(subKey.GetValue("InstallLocation"));
break;
}
}
}
}
}
if(softwareinstallpath.Equals(string.Empty))
{
MessageBox.Show("The Mirth connect software not installed in this system.")
}
string targetPath = softwareinstallpath + #"\custom-lib\";
string[] files = System.IO.Directory.GetFiles(#"D:\BaseFiles");
// Copy the files and overwrite destination files if they already exist.
foreach (var item in files)
{
string srcfilepath = item;
string fileName = System.IO.Path.GetFileName(item);
System.IO.File.Copy(srcfilepath, targetPath + fileName, true);
}
return;
}

Cross-App Keychain Access, where to configure?

I am currently developing multiple cross-plattform apps which (under iOS) use some shared keychain entries. My current project started on android and after we had a working version I continued working on the iOS version. I imported our keychain access code from earlier projects to access our shared login-data. Only this time the queries always return SecStatusCode.ItemNotFound.
I compared the provisioning profile and the entitlements and all looks the same. After some time it drove me nuts and I created a new empty app with only the keychain-code, the same bundle identifier, provisioning profiles and entitlements file as the currently-not-working-app and it works just fine and returns my data.
To my question, where else are things to be configured, that could possibly interfere with my access to keychain entries besides entitlements.plist and provisioning profile? Since the project is somewhat bigger, I don't want to copy ALL code to fresh project. I tried both Visual Studio 2017 for Windows and VS for Mac 2019. It is an internal/enterprise app, it that is of any concern...
Keychain call:
KeyChain kc = new KeyChain("USER_DATA", "de.rlp.myCompany.Shared");
var data = kc.Find("LOGIN_DATA");
Keychain-Class:
public class KeyChain
{
public string ServiceName { get; set; }
public string GroupName { get; set; }
public KeyChain(string serviceName, string groupName = null)
{
ServiceName = serviceName;
GroupName = groupName;
}
public byte[] Find(string key)
{
SecStatusCode res;
var rec = PrepareDictionary(key);
var match = SecKeyChain.QueryAsRecord(rec, out res);
if (res == SecStatusCode.Success) // ItemNotFound return-code here
{
return match.ValueData.ToArray();
}
else
{
System.Diagnostics.Debug.Write(res.ToString());
}
return null;
}
private SecRecord PrepareDictionary(string key)
{
var sr = new SecRecord(SecKind.GenericPassword)
{
Service = this.ServiceName,
Generic = NSData.FromString (key),
Account = key,
Accessible = SecAccessible.AlwaysThisDeviceOnly
};
if (string.IsNullOrEmpty(GroupName))
{
sr.AccessGroup = GroupName;
}
return sr;
}
}
Entitlements-Entry
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)de.rlp.myCompany.Shared</string>
</array>
Have you added both your applications to the same App Group/ Keychain Group from entitlements and enabled it.
VS might be buggy. Just check entitlements for both the apps on apple developer site.
That might be the problem.
Documentation

Find And Return Visual Studio Versions

All right all I am stuck, and I think I need some help. I have multiple versions of Visual Studio Installed. When launching the app, I want it to be able to find all the visual studio versions, return them, so the user can select what version they want to use. In earlier versions, you could do this with the path and the registry key to check what versions that are installed.
Now VS17 is more lightweight and is installed differently so you cannot use the registry key anymore. I am looking at https://code.msdn.microsoft.com/windowsdesktop/Visual-Studio-Setup-0cedd331#content, and https://github.com/Microsoft/vswhere but I cannot seem to get them to work.
The idea is that the user would click the solutions file in the internal application and we would launch the latest version of Visual Studio.
Here is a snippet of how the current code works to find previous versions:
private class VsVersion
{
public static VsVersion TryResolveInstalledVersion()
{
// Note: Newest version first
// Note: This search will fail on 32-bit systems
var versions = new[] {
new {
ExecutablePath = #"%PROGRAMFILES(x86)%\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe",
RegistrySubKey = #"Software\Microsoft\VisualStudio\14.0"
},
new {
ExecutablePath = #"%PROGRAMFILES(x86)%\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe",
RegistrySubKey = #"Software\Microsoft\VisualStudio\12.0"
},
new {
ExecutablePath = #"%PROGRAMFILES(x86)%\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe",
RegistrySubKey = #"Software\Microsoft\VisualStudio\11.0"
}
};
foreach (var version in versions)
{
var executablePath = Environment.ExpandEnvironmentVariables(version.ExecutablePath);
if (File.Exists(executablePath))
{
return new VsVersion(executablePath, version.RegistrySubKey);
}
}
return null;
}
private VsVersion(string executablePath, string registrySubKey)
{
ExecutablePath = executablePath;
RegistrySubKey = registrySubKey;
}
public string ExecutablePath { get; }
public string RegistrySubKey { get; }
}

c# default application - how to get the file content? [duplicate]

I'd like to to associate a file extension to the current executable in C#.
This way when the user clicks on the file afterwards in explorer, it'll run my executable with the given file as the first argument.
Ideally it'd also set the icon for the given file extensions to the icon for my executable.
Thanks all.
There doesn't appear to be a .Net API for directly managing file associations but you can use the Registry classes for reading and writing the keys you need to.
You'll need to create a key under HKEY_CLASSES_ROOT with the name set to your file extension (eg: ".txt"). Set the default value of this key to a unique name for your file type, such as "Acme.TextFile". Then create another key under HKEY_CLASSES_ROOT with the name set to "Acme.TextFile". Add a subkey called "DefaultIcon" and set the default value of the key to the file containing the icon you wish to use for this file type. Add another sibling called "shell". Under the "shell" key, add a key for each action you wish to have available via the Explorer context menu, setting the default value for each key to the path to your executable followed by a space and "%1" to represent the path to the file selected.
For instance, here's a sample registry file to create an association between .txt files and EmEditor:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.txt]
#="emeditor.txt"
[HKEY_CLASSES_ROOT\emeditor.txt]
#="Text Document"
[HKEY_CLASSES_ROOT\emeditor.txt\DefaultIcon]
#="%SystemRoot%\\SysWow64\\imageres.dll,-102"
[HKEY_CLASSES_ROOT\emeditor.txt\shell]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\open]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\open\command]
#="\"C:\\Program Files\\EmEditor\\EMEDITOR.EXE\" \"%1\""
[HKEY_CLASSES_ROOT\emeditor.txt\shell\print]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\print\command]
#="\"C:\\Program Files\\EmEditor\\EMEDITOR.EXE\" /p \"%1\""
Also, if you decide to go the registry way, keep in mind that current user associations are under HKEY_CURRENT_USER\Software\Classes. It might be better to add your application there instead of local machine classes.
If your program will be run by limited users, you won't be able to modify CLASSES_ROOT anyway.
If you use ClickOnce deployment, this is all handled for you (at least, in VS2008 SP1); simply:
Project Properties
Publish
Options
File Associatons
(add whatever you need)
(note that it must be full-trust, target .NET 3.5, and be set for offline usage)
See also MSDN: How to: Create File Associations For a ClickOnce Application
Here's a complete example:
public class FileAssociation
{
public string Extension { get; set; }
public string ProgId { get; set; }
public string FileTypeDescription { get; set; }
public string ExecutableFilePath { get; set; }
}
public class FileAssociations
{
// needed so that Explorer windows get refreshed after the registry is updated
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
private const int SHCNE_ASSOCCHANGED = 0x8000000;
private const int SHCNF_FLUSH = 0x1000;
public static void EnsureAssociationsSet()
{
var filePath = Process.GetCurrentProcess().MainModule.FileName;
EnsureAssociationsSet(
new FileAssociation
{
Extension = ".binlog",
ProgId = "MSBuildBinaryLog",
FileTypeDescription = "MSBuild Binary Log",
ExecutableFilePath = filePath
},
new FileAssociation
{
Extension = ".buildlog",
ProgId = "MSBuildStructuredLog",
FileTypeDescription = "MSBuild Structured Log",
ExecutableFilePath = filePath
});
}
public static void EnsureAssociationsSet(params FileAssociation[] associations)
{
bool madeChanges = false;
foreach (var association in associations)
{
madeChanges |= SetAssociation(
association.Extension,
association.ProgId,
association.FileTypeDescription,
association.ExecutableFilePath);
}
if (madeChanges)
{
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
}
}
public static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath)
{
bool madeChanges = false;
madeChanges |= SetKeyDefaultValue(#"Software\Classes\" + extension, progId);
madeChanges |= SetKeyDefaultValue(#"Software\Classes\" + progId, fileTypeDescription);
madeChanges |= SetKeyDefaultValue($#"Software\Classes\{progId}\shell\open\command", "\"" + applicationFilePath + "\" \"%1\"");
return madeChanges;
}
private static bool SetKeyDefaultValue(string keyPath, string value)
{
using (var key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key.GetValue(null) as string != value)
{
key.SetValue(null, value);
return true;
}
}
return false;
}
There may be specific reasons why you choose not to use an install package for your project but an install package is a great place to easily perform application configuration tasks such registering file extensions, adding desktop shortcuts, etc.
Here's how to create file extension association using the built-in Visual Studio Install tools:
Within your existing C# solution, add a new project and select project type as Other Project Types -> Setup and Deployment -> Setup Project (or try the Setup Wizard)
Configure your installer (plenty of existing docs for this if you need help)
Right-click the setup project in the Solution explorer, select View -> File Types, and then add the extension that you want to register along with the program to run it.
This method has the added benefit of cleaning up after itself if a user runs the uninstall for your application.
To be specific about the "Windows Registry" way:
I create keys under HKEY_CURRENT_USER\Software\Classes (like Ishmaeel said)
and follow the instruction answered by X-Cubed.
The sample code looks like:
private void Create_abc_FileAssociation()
{
/***********************************/
/**** Key1: Create ".abc" entry ****/
/***********************************/
Microsoft.Win32.RegistryKey key1 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key1.CreateSubKey("Classes");
key1 = key1.OpenSubKey("Classes", true);
key1.CreateSubKey(".abc");
key1 = key1.OpenSubKey(".abc", true);
key1.SetValue("", "DemoKeyValue"); // Set default key value
key1.Close();
/*******************************************************/
/**** Key2: Create "DemoKeyValue\DefaultIcon" entry ****/
/*******************************************************/
Microsoft.Win32.RegistryKey key2 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key2.CreateSubKey("Classes");
key2 = key2.OpenSubKey("Classes", true);
key2.CreateSubKey("DemoKeyValue");
key2 = key2.OpenSubKey("DemoKeyValue", true);
key2.CreateSubKey("DefaultIcon");
key2 = key2.OpenSubKey("DefaultIcon", true);
key2.SetValue("", "\"" + "(The icon path you desire)" + "\""); // Set default key value
key2.Close();
/**************************************************************/
/**** Key3: Create "DemoKeyValue\shell\open\command" entry ****/
/**************************************************************/
Microsoft.Win32.RegistryKey key3 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key3.CreateSubKey("Classes");
key3 = key3.OpenSubKey("Classes", true);
key3.CreateSubKey("DemoKeyValue");
key3 = key3.OpenSubKey("DemoKeyValue", true);
key3.CreateSubKey("shell");
key3 = key3.OpenSubKey("shell", true);
key3.CreateSubKey("open");
key3 = key3.OpenSubKey("open", true);
key3.CreateSubKey("command");
key3 = key3.OpenSubKey("command", true);
key3.SetValue("", "\"" + "(The application path you desire)" + "\"" + " \"%1\""); // Set default key value
key3.Close();
}
Just show you guys a quick demo, very easy to understand. You could modify those key values and everything is good to go.
The code below is a function the should work, it adds the required values in the windows registry. Usually i run SelfCreateAssociation(".abc") in my executable. (form constructor or onload or onshown) It will update the registy entry for the current user, everytime the executable is executed. (good for debugging, if you have some changes).
If you need detailed information about the registry keys involved check out this MSDN link.
https://msdn.microsoft.com/en-us/library/windows/desktop/dd758090(v=vs.85).aspx
To get more information about the general ClassesRoot registry key. See this MSDN article.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724475(v=vs.85).aspx
public enum KeyHiveSmall
{
ClassesRoot,
CurrentUser,
LocalMachine,
}
/// <summary>
/// Create an associaten for a file extension in the windows registry
/// CreateAssociation(#"vendor.application",".tmf","Tool file",#"C:\Windows\SYSWOW64\notepad.exe",#"%SystemRoot%\SYSWOW64\notepad.exe,0");
/// </summary>
/// <param name="ProgID">e.g. vendor.application</param>
/// <param name="extension">e.g. .tmf</param>
/// <param name="description">e.g. Tool file</param>
/// <param name="application">e.g. #"C:\Windows\SYSWOW64\notepad.exe"</param>
/// <param name="icon">#"%SystemRoot%\SYSWOW64\notepad.exe,0"</param>
/// <param name="hive">e.g. The user-specific settings have priority over the computer settings. KeyHive.LocalMachine need admin rights</param>
public static void CreateAssociation(string ProgID, string extension, string description, string application, string icon, KeyHiveSmall hive = KeyHiveSmall.CurrentUser)
{
RegistryKey selectedKey = null;
switch (hive)
{
case KeyHiveSmall.ClassesRoot:
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(ProgID);
break;
case KeyHiveSmall.CurrentUser:
Microsoft.Win32.Registry.CurrentUser.CreateSubKey(#"Software\Classes\" + extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(#"Software\Classes\" + ProgID);
break;
case KeyHiveSmall.LocalMachine:
Microsoft.Win32.Registry.LocalMachine.CreateSubKey(#"Software\Classes\" + extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(#"Software\Classes\" + ProgID);
break;
}
if (selectedKey != null)
{
if (description != null)
{
selectedKey.SetValue("", description);
}
if (icon != null)
{
selectedKey.CreateSubKey("DefaultIcon").SetValue("", icon, RegistryValueKind.ExpandString);
selectedKey.CreateSubKey(#"Shell\Open").SetValue("icon", icon, RegistryValueKind.ExpandString);
}
if (application != null)
{
selectedKey.CreateSubKey(#"Shell\Open\command").SetValue("", "\"" + application + "\"" + " \"%1\"", RegistryValueKind.ExpandString);
}
}
selectedKey.Flush();
selectedKey.Close();
}
/// <summary>
/// Creates a association for current running executable
/// </summary>
/// <param name="extension">e.g. .tmf</param>
/// <param name="hive">e.g. KeyHive.LocalMachine need admin rights</param>
/// <param name="description">e.g. Tool file. Displayed in explorer</param>
public static void SelfCreateAssociation(string extension, KeyHiveSmall hive = KeyHiveSmall.CurrentUser, string description = "")
{
string ProgID = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.FullName;
string FileLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
CreateAssociation(ProgID, extension, description, FileLocation, FileLocation + ",0", hive);
}
The file associations are defined in the registry under HKEY_CLASSES_ROOT.
There's a VB.NET example here that I'm you can port easily to C#.
There are two cmd tools that have been around since Windows 7 which make it very easy to create simple file associations. They are assoc and ftype. Here's a basic explanation of each command.
Assoc - associates a file extension (like '.txt') with a "file type."
FType - defines an executable to run when the user opens a given "file type."
Note that these are cmd tools and not executable files (exe). This means that they can only be run in a cmd window, or by using ShellExecute with "cmd /c assoc." You can learn more about them at the links or by typing "assoc /?" and "ftype /?" at a cmd prompt.
So to associate an application with a .bob extension, you could open a cmd window (WindowKey+R, type cmd, press enter) and run the following:
assoc .bob=BobFile
ftype BobFile=c:\temp\BobView.exe "%1"
This is much simpler than messing with the registry and it is more likely to work in future windows version.
Wrapping it up, here is a C# function to create a file association:
public static int setFileAssociation(string[] extensions, string fileType, string openCommandString) {
int v = execute("cmd", "/c ftype " + fileType + "=" + openCommandString);
foreach (string ext in extensions) {
v = execute("cmd", "/c assoc " + ext + "=" + fileType);
if (v != 0) return v;
}
return v;
}
public static int execute(string exeFilename, string arguments) {
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = true;
startInfo.FileName = exeFilename;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = arguments;
try {
using (Process exeProcess = Process.Start(startInfo)) {
exeProcess.WaitForExit();
return exeProcess.ExitCode;
}
} catch {
return 1;
}
}

How to find all after effect installed version in c#?

I have problem for detecting the all after effects installed version in c# i used this code for getting the name
static string[] GetAllInstalledSoftware(string strPrefix)
{
const string strUNINSTALL_KEY = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
List<string> listMatches = new List<string>();
// Enumerate all sub keys found under the "Uninstall" key, each sub key represents a installed software
foreach (string strSubKey in Registry.LocalMachine.OpenSubKey(strUNINSTALL_KEY).GetSubKeyNames())
{
// try to get the "DisplayName" for the installed software
object objValue = Registry.LocalMachine.OpenSubKey(strUNINSTALL_KEY + #"\" + strSubKey).GetValue("DisplayName");
if (objValue != null)
{
string strDisplayName = objValue.ToString();
// If display name starts with the desired prefix
if (strDisplayName.StartsWith(strPrefix))
{
// -> add it to the result list
listMatches.Add(strDisplayName);
}
}
}
return listMatches.ToArray();
}
But it only give me only one latest installed name for after effect but i have installed 3 version of after effects CS5, CS6, CC-2014,
so anybody have solution how to find all installed version of after effects?

Categories