I'm looking for a properly abstract way to get a list of ODBC data sources from the system in C#. I've tried the "Poking-around-in-the-registry" trick, which I've found works fine in English:
RegistryKey reg = (Registry.CurrentUser).OpenSubKey("Software");
reg = reg.OpenSubKey("ODBC");
reg = reg.OpenSubKey("ODBC.INI");
reg = reg.OpenSubKey("ODBC Data Sources");
and then, of course, iterating over reg.GetValueNames()
Only problem is that I've discovered on at least one Spanish machine that their Registry keys are, well, in Spanish, so clearly violating this abstraction (if it exists) has already gotten me into trouble.
Is there a library function to do this?
You could call the SQLDataSources-function in ODBC32.DLL:
using System.Runtime.InteropServices;
public static class OdbcWrapper
{
[DllImport("odbc32.dll")]
public static extern int SQLDataSources(int EnvHandle, int Direction, StringBuilder ServerName, int ServerNameBufferLenIn,
ref int ServerNameBufferLenOut, StringBuilder Driver, int DriverBufferLenIn, ref int DriverBufferLenOut);
[DllImport("odbc32.dll")]
public static extern int SQLAllocEnv(ref int EnvHandle);
}
Example that lists the Data Sources:
public void ListODBCsources()
{
int envHandle=0;
const int SQL_FETCH_NEXT = 1;
const int SQL_FETCH_FIRST_SYSTEM = 32;
if (OdbcWrapper.SQLAllocEnv(ref envHandle) != -1)
{
int ret;
StringBuilder serverName = new StringBuilder(1024);
StringBuilder driverName = new StringBuilder(1024);
int snLen = 0;
int driverLen = 0;
ret = OdbcWrapper.SQLDataSources(envHandle, SQL_FETCH_FIRST_SYSTEM, serverName, serverName.Capacity, ref snLen,
driverName, driverName.Capacity, ref driverLen);
while (ret == 0)
{
System.Windows.Forms.MessageBox.Show(serverName + System.Environment.NewLine + driverName);
ret = OdbcWrapper.SQLDataSources(envHandle, SQL_FETCH_NEXT, serverName, serverName.Capacity, ref snLen,
driverName, driverName.Capacity, ref driverLen);
}
}
}
The first call to SQLDataSources with SQL_FETCH_FIRST_SYSTEM tells the function to start the listing with the System-DSNs. If you simply started with SQL_FETCH_NEXT it would first list the drivers. Link to the function ref on Microsofts site
Edit:
Everybody seems to know it but I just found out yesterday when I used this code in a new poject: if you are compiling this with VS on a 64 Bit Windows you have to set the "Target Platform" to "x86" or the code won't run.
I use the following code to retrieve the DSNs from the registry :
private List<string> EnumDsn()
{
List<string> list = new List<string>();
list.AddRange(EnumDsn(Registry.CurrentUser));
list.AddRange(EnumDsn(Registry.LocalMachine));
return list;
}
private IEnumerable<string> EnumDsn(RegistryKey rootKey)
{
RegistryKey regKey = rootKey.OpenSubKey(#"Software\ODBC\ODBC.INI\ODBC Data Sources");
if (regKey != null)
{
foreach (string name in regKey.GetValueNames())
{
string value = regKey.GetValue(name, "").ToString();
yield return name;
}
}
}
It's strange that you have non-english name for the "ODBC Data Sources" key... I have a French version of Windows, and the name is still in English
I don't think there is anything in .NET, and a quick check of the (native) ODBC API shows some functions that might be of help:
SQLBrowseConnec
SQLDrivers
Given the way buffers are used in the ODBC API, careful pinning of character arrays will be needed.
I know this is an old post but for what it's worth at windows 10 I found the ODBC connection located in the LocalMachine node and not the current user, could be a 64bit thing.
RegistryKey reg = (Registry.LocalMachine).OpenSubKey("Software");
reg = reg.OpenSubKey("ODBC");
reg = reg.OpenSubKey("ODBC.INI");
reg = reg.OpenSubKey("ODBC Data Sources");
string instance = "";
foreach (string item in reg.GetValueNames())
{
instance = item;
}
If you are using a Windows Forms Application (not a Web environment), you could use the Visual Studio's "Choose Data Source" dialog.
It's included in an assembly and can be easily used.
The article where I found this info:
http://www.mztools.com/articles/2007/MZ2007011.aspx
In any case, I'm from Spain and I also use the Registry solution (specially in Web apps). I've never found a machine with those entries in a language different from English.
Related
I have tried this code it works fine for unpinning application from taskbar in Windows 10 but it is not working for pinning application into taskbar.
public static void PinUnpinTaskbar(bool pin)
{
string l_strFilePath = System.Reflection.Assembly.GetEntryAssembly().Location;
if (!File.Exists(l_strFilePath)) throw new FileNotFoundException(l_strFilePath);
int MAX_PATH = 255;
var actionIndex = pin ? 5386 : 5387; // 5386 is the DLL index for"Pin to Tas&kbar", ref. http://www.win7dll.info/shell32_dll.html
//uncomment the following line to pin to start instead
//actionIndex = pin ? 51201 : 51394;
StringBuilder szPinToStartLocalized = new StringBuilder(MAX_PATH);
IntPtr hShell32 = LoadLibrary("Shell32.dll");
LoadString(hShell32, (uint)actionIndex, szPinToStartLocalized, MAX_PATH);
string localizedVerb = szPinToStartLocalized.ToString();
string path = Path.GetDirectoryName(l_strFilePath);
string fileName = Path.GetFileName(l_strFilePath);
// create the shell application object
dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
dynamic directory = shellApplication.NameSpace(path);
dynamic link = directory.ParseName(fileName);
dynamic verbs = link.Verbs();
for (int i = 0; i < verbs.Count(); i++)
{
dynamic verb = verbs.Item(i);
if (verb.Name.Equals(localizedVerb))
{
verb.DoIt();
return;
}
}
return;
}
Cannot see anything wrong in your solution, also tried it several times but the verb does no longer exist. After some research I found this:
Update KB3093266 removes shell.Application object 'taskbarpin' verb
Update KB3093266 removes shell.Application object 'taskbarpin' verb
for adding taskbar pin item pins
It is most likely broken by one of the updates that KB3093266
supersedes
And this (Powershell but same library): Pin to Taskbar fails in Windows 10
I am writing a VS extension which will need to communicate with a server and identify the user, and I figured that if possible, since this extension will be the only client connecting to the server, using Visual Studio's built-in support for Microsoft accounts would make more sense than implementing my own account management infrastructure.
At first, because of the variety of useful APIs available to a Visual Studio developer, one might think that getting info on the current user would be easy. However, there actually don't seem to be any obvious APIs that can be used to access accounts; I checked here and there weren't any related services listed (the "profile" services just allow you to read/write settings that are stored for the current user).
Does anyone know of a (relatively) simple way to access the Microsoft account from a Visual Studio extension?
EDIT
I tried Hadi Brais's suggestion, and at first it appeared to work (I successfully retrieved the information); however, each time, Visual Studio would crash about 30s afterward. I commented out the lines that interacted with the registry and replaced them with static values for the variables, and the crashes stopped. Clearly, accessing Visual Studio's registry keys was causing it to crash. I even tried using statements and other safeguards, however there doesn't seem to be a way to safely access the Visual Studio registry keys from an extension. So does anyone know of any official APIs that can be used to retrieve this information without crashing Visual Studio?
For Visual Studio 2015 (Version 14.0), this is how to get information about the user that is currently signed in in Visual Studio. You need to add using Microsoft.Win32;.
private static string GetUserEmailAddressVS14()
{
// It's a good practice to request explicit permission from
// the user that you want to use his email address and any
// other information. This enables the user to be in control
// of his/her privacy.
// Assuming permission is granted, we obtain the email address.
const string SubKey = "Software\\Microsoft\\VSCommon\\ConnectedUser\\IdeUser\\Cache";
const string EmailAddressKeyName = "EmailAddress";
const string UserNameKeyName = "DisplayName";
RegistryKey root = Registry.CurrentUser;
RegistryKey sk = root.OpenSubKey(SubKey);
if (sk == null)
{
// The user is currently not signed in.
return null;
}
else
{
// Get user email address.
return (string)sk.GetValue(EmailAddressKeyName);
// You can also get user name like this.
// return (string)sk.GetValue(UserNameKeyName);
}
}
There are now multiple versions of the IdeUser key. I've reimplemented the algorithm from the other answer this way:
public static string GetUserEmailAddressFromVisualStudioRegistry()
{
try
{
const string ConnectedUserSubKey = #"Software\Microsoft\VSCommon\ConnectedUser";
const string EmailAddressKeyName = "EmailAddress";
RegistryKey connectedUserSubKey = Registry.CurrentUser.OpenSubKey( ConnectedUserSubKey );
string[] subKeyNames = connectedUserSubKey?.GetSubKeyNames();
if ( subKeyNames == null || subKeyNames.Length == 0 )
{
return null;
}
int[] subKeysOrder = new int[subKeyNames.Length];
for ( int i = 0; i < subKeyNames.Length; i++ )
{
Match match = Regex.Match( subKeyNames[i], #"^IdeUser(?:V(?<version>\d+))?$" );
if ( !match.Success )
{
subKeysOrder[i] = -1;
continue;
}
string versionString = match.Groups["version"]?.Value;
if ( string.IsNullOrEmpty( versionString ) )
{
subKeysOrder[i] = 0;
}
else if ( !int.TryParse( versionString, out subKeysOrder[i] ) )
{
subKeysOrder[i] = -1;
}
}
Array.Sort( subKeysOrder, subKeyNames );
for ( int i = subKeyNames.Length - 1; i >= 0; i++ )
{
string cacheSubKeyName = $#"{subKeyNames[i]}\Cache";
RegistryKey cacheKey = connectedUserSubKey.OpenSubKey( cacheSubKeyName );
string emailAddress = cacheKey?.GetValue( EmailAddressKeyName ) as string;
if ( !string.IsNullOrWhiteSpace( emailAddress ) )
{
return emailAddress;
}
}
}
catch
{
// Handle exceptions here if it's wanted.
}
return null;
}
This algorithm tries all versions from the newest one and then all other siblings. It returns null in case of a failure.
My WPF application needs to list the localized names of all Metro/WinRT applications installed for the user. I created a repo to store a working sample for the code presented: https://github.com/luisrigoni/metro-apps-list
1) Using PackageManager.FindPackagesForUser() method
var userSecurityId = WindowsIdentity.GetCurrent().User.Value;
var packages = packageManager.FindPackagesForUser(userSecurityId);
foreach (var package in packages)
Debug.WriteLine(package.Id.Name);
}
// output:
// Microsoft.BingFinance
// Microsoft.BingMaps
// Microsoft.BingSports
// Microsoft.BingTravel
// Microsoft.BingWeather
// Microsoft.Bing
// Microsoft.Camera
// microsoft.microsoftskydrive
// microsoft.windowscommunicationsapps
// microsoft.windowsphotos
// Microsoft.XboxLIVEGames
// Microsoft.ZuneMusic
// Microsoft.ZuneVideo
These outputs don't seems too friendly to show to the user...
2) Reading the AppxManifest.xml of each of these apps
var userSecurityId = WindowsIdentity.GetCurrent().User.Value;
var packages = packageManager.FindPackagesForUser(userSecurityId);
foreach (var package in packages)
{
var dir = package.InstalledLocation.Path;
var file = Path.Combine(dir, "AppxManifest.xml");
var obj = SerializationExtensions.DeSerializeObject<Package>(file);
if (obj.Applications != null)
{
foreach (var application in obj.Applications)
{
Debug.WriteLine(application.VisualElements.DisplayName);
}
}
}
// output:
// ms-resource:AppTitle
// ms-resource:AppDisplayName
// ms-resource:BingSports
// ms-resource:AppTitle
// ms-resource:AppTitle
// ms-resource:app_name
// ms-resource:manifestDisplayName
// ms-resource:ShortProductName
// ms-resource:mailAppTitle
// ms-resource:chatAppTitle
// ms-resource:///resources/residTitle
// ms-resource:///strings/peopleAppName
// ms-resource:///photo/residAppName
// ms-resource:34150
// ms-resource:33273
// ms-resource:33270
Definitely not friendly...
Update 1) Increasing above item (2) with SHLoadIndirectString funcion (hint by Erik F)
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
private static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
static internal string ExtractStringFromPRIFile(string pathToPRI, string resourceKey)
{
string sWin8ManifestString = string.Format("#{{{0}? {1}}}", pathToPRI, resourceKey);
var outBuff = new StringBuilder(1024);
int result = SHLoadIndirectString(sWin8ManifestString, outBuff, outBuff.Capacity, IntPtr.Zero);
return outBuff.ToString();
}
[...]
foreach (var application in obj.Applications)
{
Uri uri = new Uri(application.VisualElements.DisplayName);
var resourceKey = string.Format("ms-resource://{0}/resources/{1}", package.Id.Name, uri.Segments.Last());
Debug.WriteLine(ExtractStringFromPRIFile("<path/to/pri>", resourceKey));
}
[...]
// output:
// Finance
// Maps
// Sports
// Travel
// Weather
// Bing
// Camera
// SkyDrive
// Mail
// Messaging
// Calendar
// People
// Photos
// Games
// Music
// Video
Much, much better. We already have english labels. But how to extract other language resources?
I'm expecting retrieve the same label that is shown on Start Screen for each app, something like "Finanças", "Esportes", "Clima" if my language is pt-BR; "Finances", "Sports", "Weather" if my language is en-US.
[Q] Is there another way to get the application names? Maybe native/Win32 (DISM API/...)? Is possible to load the .pri file of each app to get the localized name?
As said, an updated working sample is here: https://github.com/luisrigoni/metro-apps-list
Using SHLoadIndirectString, you should be able to construct a fully-qualified reference for Package name and resource ID of the form #{PackageFullName?resource-id}
Documented here:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb759919(v=vs.85).aspx
You'll have to transform the manifest string into the proper form, though. It should be:
ms-resource://PackageName/Resources/Id
PackageName is the name rather than the full name. Resources isn't strictly required but it's the default and it's usually there. I'd try to look up the resource without inserting resources and then try again if that fails.
For example, the camera app has "ms-resource:manifestDisplayName" in the manifest, so first you should try(*):
#{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/manifestAppDescription}
When that fails, insert "resources" and try:
#{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/resources/manifestAppDescription}
That should work. You'll want to try both forms because blindly inserting "resources" will break apps like skydrive, communications and photos which insert the first part of the path directly.
Still a bit of a pain, but better than dumping and parsing gigantic XML files.
(*) "Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe" is taken from an example - you'll obviously want the FullName of the one that's actually present on your system.
Looks like you're stuck with makepri.exe dump /if <prifile>.pri /of <outfile>.xml. Then all you have to do is parse/deserialize the XML file.
In addition to what Erik F told above along with updated question from Luis Rigoni (OP) here are further tips:
I found that path to PRI is better solution that giving package name. Many a times SHLoadIndirectString doesn't resolve the resource when just package name is given. Path to PRI is the package's install location + resources.pri . Example: C:\Program Files\WindowsApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\Resources.pri.
The VisualElements/DisplayName may contain the full url to the resource. If so, you don't have to further format it using package name and 'resources' folder like ms-resource://{0}/resources/{1}. If the DisplayName contains the package name itself, then you can assume that it is a full url.
Like Erik F pointed out, when SHLoadIndirectString fails, try again without the /resources/ folder.
Also sometimes the resources folder itself will be part of VisualElements/DisplayName. Example: ms-resource:///MSWifiResources/AppDisplayName. Also, notice the three ///. Yes, you will have to take care of that. You just have to take MSWifiResources/AppDisplayName and suffix it to ms-resource:///MSWifiResources/AppDisplayName
.
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
//If VisualElements/DisplayName contains ms-resource: then call the below
//function. identity is nothing but package name that can be retrieved from
//Identity/Name element in AppxManifest.xml.
private static string GetName(string installPath, string name, string identity) {
StringBuilder sb = new StringBuilder();
int result;
//if name itself contains the package name then assume full url else
//format the resource url
var resourceKey = (name.ToLower().Contains(identity.ToLower())) ? name : string.Format("ms-resource://{0}/resources/{1}", identity, name.Split(':')[[1]].TrimStart('/'));
string source = string.Format("#{{{0}? {1}}}", Path.Combine(installPath, "resources.pri"), resourceKey);
result = SHLoadIndirectString(source, sb, -1, IntPtr.Zero);
if (result == 0)
return sb.ToString();
//if the above fails then we try the url without /resources/ folder
//because some apps do not place the resources in that resources folder
resourceKey = string.Format("ms-resource://{0}/{1}", identity, name.Split(':')[[1]].TrimStart('/'));
source = string.Format("#{{{0}? {1}}}", Path.Combine(installPath, "resources.pri"), resourceKey);
result = SHLoadIndirectString(source, sb, -1, IntPtr.Zero);
if (result == 0)
return sb.ToString();
return string.Empty;
}
Actually, you can do better than makepri - check out the ResourceIndexer:
http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.resources.management.resourceindexer.aspx
You should be able to give IndexFileContentsAsync a PRI file and get back all of the resource candidates in the file. You'll have to reassemble and reinterpret them, but it will get you all of the possible resource values.
For Windows 8 apps, at least.
For apps which take advantage of resource packages (introduced in Windows 8.1), the resources.pri in the package contains only the defaults. To get the resources for the any other installed languages (or scale factors) you'll need to also index the PRI files from the additional resource packages.
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;
}
I am using a file stream to write out a file.
I was hoping to be able to write the file to the desktop.
If I have something like
tw = new StreamWriter("NameOflog file.txt");
I would like to be able to have some sort of #desktop identified in front of the file name that would automatically insert the path to the desktop.
Does this exist in C#? Or do I have to look for desktop paths on a computer by computer (or OS by OS) basis?
Quick google search reveals this one:
string strPath = Environment.GetFolderPath(
System.Environment.SpecialFolder.DesktopDirectory);
EDIT: This will work for Windows, but Mono supports it, too.
You want to use Environment.GetFolderPath, passing in SpecialFolder.DesktopDirectory.
There's also SpecialFolder.Desktop which represents the logical desktop location - it's not clear what the difference between the two is though.
Something like:
string logPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"NameOflog file.txt");
tw = new StreamWriter(logPath);
You want Environment.SpecialFolder
string fileName = "NameOflog file.txt";
path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), fileName);
tw = new StreamWriter(path);
yep.
you can use environmental variables.
like
tw = new StreamWriter("%USERPROFILE%\Desktop\mylogfile.txt");
but i would not recommend to automatically write a log file to the users desktop.
you should add the link to the file to your start menu folder.
or even populate them in the event log. (much better)
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory))
I also use the method mentioned above.
But here are a couple different options that work too (just to have a more comprehensive list):
using System;
using System.Runtime.InteropServices;
using System.Text;
class Program
{
// 1st way
private const int MAX_PATH = 260;
private const int CSIDL_DESKTOP = 0x0000;
private const int CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019; // Can get to All Users desktop even on .NET 2.0,
// where Environment.SpecialFolder.CommonDesktopDirectory is not available
[DllImport("shell32.dll")]
private static extern bool SHGetSpecialFolderPath(IntPtr hwndOwner, StringBuilder lpszPath, int nFolder, bool fCreate);
static string GetDesktopPath()
{
StringBuilder currentUserDesktop = new StringBuilder(MAX_PATH);
SHGetSpecialFolderPath((IntPtr)0, currentUserDesktop, CSIDL_DESKTOP, false);
return currentUserDesktop.ToString();
}
// 2nd way
static string YetAnotherGetDesktopPath()
{
Guid PublicDesktop = new Guid("C4AA340D-F20F-4863-AFEF-F87EF2E6BA25");
IntPtr pPath;
if (SHGetKnownFolderPath(PublicDesktop, 0, IntPtr.Zero, out pPath) == 0)
{
return System.Runtime.InteropServices.Marshal.PtrToStringUni(pPath);
}
return string.Empty;
}
}
Then:
string fileName = Path.Combine(GetDesktopPath(), "NameOfLogFile.txt");