I'm trying to find executable files for games; however some are nested (For example Ark: Survival Evolved) (A:\Steam Games\steamapps\common\ARK\ShooterGame\Binaries\Win64\ShooterGame.exe)
I've spent ages trying to find a way to only find the executables which are relevant.
This link shows games when we don't search recursively. It finds most, but not all .exe's
This link shows games searching recursively, but also shows a bunch of binaries/redist exes.
Initially I tried excluding "bin","binary","binaries","redist" folders but that then didn't give me Ark: Survival Evolved for example.
I also considered filtering the .exe's based on their size, but Aperture Tag has a QC_Eyes.exe at 3055KB, but Tomb Raider II.exe is 892KB.
Here's the method I'm using to find the steam installation directory, and check the libraryfolders.vdf file for where the library locations are. For now I'm just writing to console so that I can see what the outputs are.
If anyone has any tips on how I can find the right files for the right games it would be much appreciated. Thanks
public void SearchSteam()
{
steamGameDirs.Clear();
string steam32 = "SOFTWARE\\VALVE\\";
string steam64 = "SOFTWARE\\Wow6432Node\\Valve\\";
string steam32path;
string steam64path;
string config32path;
string config64path;
RegistryKey key32 = Registry.LocalMachine.OpenSubKey(steam32);
RegistryKey key64 = Registry.LocalMachine.OpenSubKey(steam64);
foreach(string k32subKey in key32.GetSubKeyNames())
{
using (RegistryKey subKey = key32.OpenSubKey(k32subKey))
{
steam32path = subKey.GetValue("InstallPath").ToString();
config32path = steam32path + "/steamapps/libraryfolders.vdf";
if (File.Exists(config32path))
{
string[] configLines = File.ReadAllLines(config32path);
foreach(var item in configLines)
{
Console.WriteLine("32: " + item);
}
}
}
}
foreach(string k64subKey in key64.GetSubKeyNames())
{
using (RegistryKey subKey = key64.OpenSubKey(k64subKey))
{
steam64path = subKey.GetValue("InstallPath").ToString();
config64path = steam64path + "/steamapps/libraryfolders.vdf";
string driveRegex = #"[A-Z]:\\";
if (File.Exists(config64path))
{
string[] configLines = File.ReadAllLines(config64path);
foreach (var item in configLines)
{
Console.WriteLine("64: " + item);
Match match = Regex.Match(item, driveRegex);
if(item != string.Empty && match.Success)
{
string matched = match.ToString();
string item2 = item.Substring(item.IndexOf(matched));
item2 = item2.Replace("\\\\", "\\");
steamGameDirs.Add(item2);
}
}
steamGameDirs.Add(steam64path + "\\steamapps\\common\\");
}
}
}
foreach(string item in steamGameDirs)
{
string GameTitle;
string[] Executables = new string[0];
string[] steamGames = Directory.GetDirectories(item);
foreach (var dir in steamGames)
{
string title = dir.Substring(dir.IndexOf("\\common\\"));
string[] titlex = title.Split('\\');
title = titlex[2].ToString();
GameTitle = title;
Console.WriteLine("Title: " + GameTitle);
Console.WriteLine("Directory: " + dir);
string[] executables = Directory.GetFiles(dir, "*.exe", SearchOption.AllDirectories);
int num = 0;
foreach (var ex in executables)
{
//add "ex" to Executables[] if poss
Console.WriteLine(ex);
num++;
}
}
}
}
I've managed to do what I can, so first I detect where steam is installed through the registry, then I check /steamapps/libraryfolders.vdf for where the users libraries are, then check those libraries for any "top level" executables. The program then lets the user select their own exe if one isn't found in the top directory.
Seems like the best solution for now as the steamfiles module isn't currently active.
public void SearchSteam()
{
steamGameDirs.Clear();
string steam32 = "SOFTWARE\\VALVE\\";
string steam64 = "SOFTWARE\\Wow6432Node\\Valve\\";
string steam32path;
string steam64path;
string config32path;
string config64path;
RegistryKey key32 = Registry.LocalMachine.OpenSubKey(steam32);
RegistryKey key64 = Registry.LocalMachine.OpenSubKey(steam64);
if (key64.ToString() == null || key64.ToString() == "")
{
foreach (string k32subKey in key32.GetSubKeyNames())
{
using (RegistryKey subKey = key32.OpenSubKey(k32subKey))
{
steam32path = subKey.GetValue("InstallPath").ToString();
config32path = steam32path + "/steamapps/libraryfolders.vdf";
string driveRegex = #"[A-Z]:\\";
if (File.Exists(config32path))
{
string[] configLines = File.ReadAllLines(config32path);
foreach (var item in configLines)
{
Console.WriteLine("32: " + item);
Match match = Regex.Match(item, driveRegex);
if (item != string.Empty && match.Success)
{
string matched = match.ToString();
string item2 = item.Substring(item.IndexOf(matched));
item2 = item2.Replace("\\\\", "\\");
item2 = item2.Replace("\"", "\\steamapps\\common\\");
steamGameDirs.Add(item2);
}
}
steamGameDirs.Add(steam32path + "\\steamapps\\common\\");
}
}
}
}
foreach(string k64subKey in key64.GetSubKeyNames())
{
using (RegistryKey subKey = key64.OpenSubKey(k64subKey))
{
steam64path = subKey.GetValue("InstallPath").ToString();
config64path = steam64path + "/steamapps/libraryfolders.vdf";
string driveRegex = #"[A-Z]:\\";
if (File.Exists(config64path))
{
string[] configLines = File.ReadAllLines(config64path);
foreach (var item in configLines)
{
Console.WriteLine("64: " + item);
Match match = Regex.Match(item, driveRegex);
if(item != string.Empty && match.Success)
{
string matched = match.ToString();
string item2 = item.Substring(item.IndexOf(matched));
item2 = item2.Replace("\\\\", "\\");
item2 = item2.Replace("\"", "\\steamapps\\common\\");
steamGameDirs.Add(item2);
}
}
steamGameDirs.Add(steam64path + "\\steamapps\\common\\");
}
}
}
}
Attached the code so others can see how I've done it. I know it's not the best but it's working
The keydata seems to be in the appinfo.vdf file. So a somewhat working code to get the correct EXE file for steam games is as follows. Where the game is an EA game with a pointer to an URL or there is no data in the appinfo.vdf a fallback to your previous solution could be used. If anyone got a good parser of the appinfo.vdf the code would be better and not as hacky as the "IndexOf" solution.
GetSteamPath() in the below code can be updated to fetch from registry.
I am going to use that code to fix those petchy steam links that goes back to that anonymous globe-url icon.
(Written in .Net 5.0 - seems IndexOf for .4.7.2 is quirky. doesnt like \x00 I think)
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace SteamAppsParser
{
class Program
{
static void Main(string[] args)
{
var libs = GetSteamLibs();
var apps = GetSteamApps(libs);
}
static List<AppInfo> GetSteamApps(List<string> steamLibs)
{
var apps = new List<AppInfo>();
foreach (var lib in steamLibs)
{
var appMetaDataPath = Path.Combine(lib, "SteamApps");
var files = Directory.GetFiles(appMetaDataPath, "*.acf");
foreach (var file in files)
{
var appInfo = GetAppInfo(file);
if (appInfo != null)
{
apps.Add(appInfo);
}
}
}
return apps;
}
static AppInfo GetAppInfo(string appMetaFile)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
{
var match = Regex.Match(line, #"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (match.Success)
{
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
}
}
AppInfo appInfo = null;
if (dic.Keys.Count > 0)
{
appInfo = new AppInfo();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.Manifest = appMetaFile;
appInfo.GameRoot = libGameRoot;
appInfo.InstallDir = installDir;
appInfo.SteamUrl = $"steam://runsteamid/{appId}";
//if (appInfo.Name.StartsWith("Sid Meier"))
appInfo.Executable = GetExecutable(appInfo);
}
return appInfo;
}
static string _appInfoText = null;
static string GetExecutable(AppInfo appInfo)
{
if (_appInfoText == null)
{
var appInfoFile = Path.Combine(GetSteamPath(), "appcache", "appinfo.vdf");
var bytes = File.ReadAllBytes(appInfoFile);
_appInfoText = Encoding.UTF8.GetString(bytes);
}
var startIndex = 0;
int maxTries = 50;
var fullName = "";
do
{
var startOfDataArea = _appInfoText.IndexOf($"\x00\x01name\x00{appInfo.Name}\x00", startIndex);
if (startOfDataArea < 0 && maxTries == 50) startOfDataArea = _appInfoText.IndexOf($"\x00\x01gamedir\x00{appInfo.Name}\x00", startIndex); //Alternative1
if (startOfDataArea < 0 && maxTries == 50) startOfDataArea = _appInfoText.IndexOf($"\x00\x01name\x00{appInfo.Name}\x00", startIndex); //Alternative2
if (startOfDataArea > 0)
{
startIndex = startOfDataArea + 10;
int nextLaunch = -1;
do
{
var executable = _appInfoText.IndexOf($"\x00\x01executable\x00", startOfDataArea);
if (executable>-1 && nextLaunch == -1)
{
nextLaunch = _appInfoText.IndexOf($"\x00\x01launch\x00", executable);
}
if ((nextLaunch <= 0 || executable < nextLaunch) && executable > 0)
{
if (executable > 0)
{
executable += 10;
string filename = "";
while (_appInfoText[executable] != '\x00')
{
filename += _appInfoText[executable];
executable++;
}
if (filename.Contains("://"))
{
//EA or other external
return filename; //Need to use other means to grab the EXE here.
}
fullName = Path.Combine(appInfo.GameRoot, filename);
startOfDataArea = executable + 1;
startIndex = startOfDataArea + 10;
}
}
else
{
break;
}
}
while (!File.Exists(fullName) && maxTries-- > 0);
}
else
{
return null;
}
} while (!File.Exists(fullName) && maxTries-- > 0);
if (File.Exists(fullName)) return fullName;
return null;
}
static List<string> GetSteamLibs()
{
var steamPath = GetSteamPath();
var libraries = new List<string>() { steamPath };
var listFile = Path.Combine(steamPath, #"steamapps\libraryfolders.vdf");
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var match = Regex.Match(line, #"""(?<path>\w:\\\\.*)""");
if (match.Success)
{
var path = match.Groups["path"].Value.Replace(#"\\", #"\");
if (Directory.Exists(path))
{
libraries.Add(path);
}
}
}
return libraries;
}
static string GetSteamPath()
{
return #"C:\Spill\Steam";
}
class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string SteamUrl { get; internal set; }
public string Manifest { get; internal set; }
public string GameRoot { get; internal set; }
public string Executable { get; internal set; }
public string InstallDir { get; internal set; }
public override string ToString()
{
return $"{Name} ({Id}) - {SteamUrl} - {Executable}";
}
}
}
}
Below My Code that gets info of Bug history from QC. I have problem with
AuditPropertyFactoryFilter which does not filter. AuditPropertyFactory has more than thousand rows.
If I comment
var changesList = auditPropertyFactory.NewList(changesHistoryFilter.Text); and uncomment
next line, auditPropertyFactory has several rows only but it isn't filtered as i need.
Can anyone get some advice?
public List<QCBugHistory> retrieveHistoryFromBug(string bugId)
{
List<QCBugHistory> history = new List<QCBugHistory>();
try
{
TDConnection qcConnection = new TDConnection();
qcConnection.InitConnectionEx(qcUrl);
qcConnection.ConnectProjectEx(qcDomain, qcProject, qcLogin);
if (qcConnection.Connected)
{
AuditRecordFactory auditFactory = qcConnection.AuditRecordFactory as AuditRecordFactory;
TDFilter historyFilter = auditFactory.Filter;
historyFilter["AU_ENTITY_TYPE"] = "BUG";
historyFilter["AU_ENTITY_ID"] = bugId;
historyFilter["AU_ACTION"] = "Update";
historyFilter.Order["AU_TIME"] = 1;
historyFilter.OrderDirection["AU_TIME"] = 1;
var auditRecordList = auditFactory.NewList(historyFilter.Text);
log.Info("кол-во в истории " + auditRecordList.Count);
if (auditRecordList.Count > 0)
{
foreach (AuditRecord audit in auditRecordList)
{
QCBugHistory bugHistory = new QCBugHistory();
bugHistory.actionType = audit["AU_ACTION"];
bugHistory.changeDate = audit["AU_TIME"];
AuditPropertyFactory auditPropertyFactory = audit.AuditPropertyFactory;
var changesHistoryFilter = auditPropertyFactory.Filter;
changesHistoryFilter["AP_PROPERTY_NAME"] = "Status";
var changesList = auditPropertyFactory.NewList(changesHistoryFilter.Text);
//var changesList = auditPropertyFactory.NewList("");
if (changesList.Count > 0)
{
foreach (AuditProperty prop in changesList)
{
//prop.EntityID
if (prop["AP_PROPERTY_NAME"] == "Status")
{
bugHistory.oldValue = prop["AP_OLD_VALUE"];
bugHistory.newValue = prop["AP_NEW_VALUE"];
history.Add(bugHistory);
}
}
}
}
}
}
}
catch (Exception e)
{
log.Error("Проблема соединения и получения данных из QC ", e);
}
return history;
}
Try this (in C#):
1) Having only BUG ID param (BUGID below):
BugFactory bgFact = TDCONNECTION.BugFactory;
TDFilter bgFilt = bgFact.Filter;
bgFilt["BG_BUG_ID"] = BUGID; // This is a STRING
List bugs = bgFilt.NewList();
Bug bug = bugs[1]; // is 1-based
bug.History;
2) Having the Bug object itself, just do:
bug.History;
How to go add content control in specific start and end range of footnote? I can add content control in document.range, but I am unable to add in footnote, please help to do this. If anyone reply quickly, I will be proved of you.
public void FindItalicFootnote(String FindText)
{
foreach (Word.Footnote footNote in Word.Document.Footnotes)
{
Word.Range RngFind = footNote.Range;
RngFind.Find.Forward = true;
if (RngFind.Find.Execute(FindText))
{
while (RngFind.Find.Found)
{
RngFind.Select();
object strtRange = Word.Selection.Range.Start;
object endRange = Word.Selection.Range.End;
string placeHolder = "";
bool findCase = false;
if (Word.Selection.Range.ParentContentControl == null && Word.Selection.Range.ContentControls.Count == 0)
{
RngFind.Select();
while (Word.Selection.Previous(Unit: Word.WdUnits.wdWord, Count: 1).Font.Italic == -1)
{
Word.Selection.Previous(Unit: Word.WdUnits.wdWord, Count: 1).Select();
strtRange = Word.Selection.Range.Start;
placeHolder = "{VerifiedBy='Italic'}";
findCase = true;
}
Word.ContentControl CC = RngFind.ContentControls.Add(Word.WdContentControlType.wdContentControlRichText,
footNote.range(strtRange, endRange));
//my query is, how to say footNote.range(start, end), in main part I wrote as Word.Document.range(startRange, endRange)
CC.Title = "Case Reference";
CC.Tag = Guid.NewGuid().ToString();
CC.SetPlaceholderText(Text: placeHolder);
}
RngFind.Find.Execute(FindText);
}
}
}
}
Regards,
Saran
I'm trying to add a clickable link to a rich text box that says something like "Start RDP" so when a user clicks on it, it will start windows remote desktop and use put the machine name in the box for you. Here is my code so far, It searchs Active Directory for a computer name that the user enters, pings the machine and if it is online, show its status in another textbox.
private void btnAd_Click(object sender, EventArgs e)
{
var adsb = new StringBuilder();
var mssb = new StringBuilder();
DirectoryEntry de = new DirectoryEntry();
de.Path = "LDAP://dc=Domain.org";
try
{
string wildcard = "*";
string adser = wildcard + txtAd.Text + wildcard;
DirectorySearcher ser = new DirectorySearcher();
ser.SizeLimit = System.Int32.MaxValue;
ser.PageSize = System.Int32.MaxValue;
ser.Filter = "(&(ObjectCategory=computer)(name=" + adser + "))"; //Only allows Computers to be returned in results.
SearchResultCollection results = ser.FindAll();
if (String.IsNullOrEmpty(txtcomputers.Text)) //if the textbox is empty, write ad search results to textbox
{
foreach (SearchResult res in results)
{
string[] temp = res.Path.Split(','); //temp[0] would contain the computer name ex: cn=computerName,..
adsb.AppendLine(temp[0].Substring(10));//returns everything after LDAP://CN= until end of temp[0].
if (Ping(temp[0].Substring(10)))
{
mssb.AppendLine(temp[0].Substring(10) + "....Online");
}
else
{
mssb.AppendLine(temp[0].Substring(10) + "....Offline");
}
}
rtbComputerstatus.Text = mssb.ToString();
txtcomputers.Text = adsb.ToString();
}
else //Add items to textbox if there are already items present.
{
txtcomputers.AppendText(Environment.NewLine);
rtbComputerstatus.AppendText(Environment.NewLine);
foreach (SearchResult res in results)
{
string[] temp = res.Path.Split(',');
adsb.AppendLine(temp[0].Substring(10));
txtcomputers.AppendText(adsb.ToString());
if (Ping(temp[0].Substring(10)))
{
mssb.AppendLine(temp[0].Substring(10) + "....Online......");
}
else
{
mssb.AppendLine(temp[0].Substring(10) + "....Offline");
}
rtbComputerstatus.AppendText(mssb.ToString());
}
}
//trims spaces
this.txtcomputers.Text = this.txtcomputers.Text.Trim();
txtcomputers.CharacterCasing = CharacterCasing.Upper;
//color items
HighlightPhrase(rtbComputerstatus, "Online",Color.Green);
HighlightPhrase(rtbComputerstatus, "Offline", Color.Red);
HighlightPhrase(rtbComputerstatus, "RDP", Color.Blue);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
de.Dispose();//Clean up resources
}
}
// color method
static void HighlightPhrase(RichTextBox box, string phrase, Color color)
{
int pos = box.SelectionStart;
string s = box.Text;
for (int ix = 0; ; )
{
int jx = s.IndexOf(phrase, ix, StringComparison.CurrentCultureIgnoreCase);
if (jx < 0) break;
box.SelectionStart = jx;
box.SelectionLength = phrase.Length;
box.SelectionColor = color;
ix = jx + 1;
}
box.SelectionStart = pos;
box.SelectionLength = 0;
}
//ping method
public static bool Ping(string hostName)
{
bool result = false;
try
{
Ping pingSender = new Ping(); PingOptions options = new PingOptions();
// Use the default Ttl value which is 128,
// but change the fragmentation behavior.
options.DontFragment = true;
// Create a buffer of 32 bytes of data to be transmitted.
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
PingReply reply = pingSender.Send(hostName, timeout, buffer, options); if (reply.Status == IPStatus.Success)
{
result = true;
}
else
{
result = false;
}
}
catch
{
result = false;
}
return result;
}
Any suggestions ? I've tried a few different things but can't seem to get it working.
Thanks !
There is an article published on Codeproject, describing the solution that you are looking for: link
Hope this will help. Rgds, AB
Right now I use this to list all the applications listed in the registry for 32bit & 64.
I have seen the other examples of how to check if an application is installed without any luck.
string registryKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
foreach (String a in key.GetSubKeyNames())
{
RegistryKey subkey = key.OpenSubKey(a);
Console.WriteLine(subkey.GetValue("DisplayName"));
}
}
registryKey = #"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
foreach (String a in key.GetSubKeyNames())
{
RegistryKey subkey = key.OpenSubKey(a);
Console.WriteLine(subkey.GetValue("DisplayName"));
}
}
So this snippet lists it all in the console window and what I am trying to do is
just find one program title out of the list of display names to see if it's installed.
The last thing I tried was
if (subkey.Name.Contains("OpenSSL"))
Console.Writeline("OpenSSL Found");
else
Console.Writeline("OpenSSL Not Found");
Anything I tried came back either false or a false positive. Is there anyone that can show me how to just grab a title out of the list?
Please don't post up the well-known private static void IsApplicationInstalled(p_name) function. It does not work for me at all.
After searching and troubleshooting, I got it to work this way:
public static bool checkInstalled (string c_name)
{
string displayName;
string registryKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
foreach (RegistryKey subkey in key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName)))
{
displayName = subkey.GetValue("DisplayName") as string;
if (displayName != null && displayName.Contains(c_name))
{
return true;
}
}
key.Close();
}
registryKey = #"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
foreach (RegistryKey subkey in key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName)))
{
displayName = subkey.GetValue("DisplayName") as string;
if (displayName != null && displayName.Contains(c_name))
{
return true;
}
}
key.Close();
}
return false;
}
And I simply just call it using
if(checkInstalled("Application Name"))
This is a clean way to do this without that much code.
private static bool IsSoftwareInstalled(string softwareName)
{
var key = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") ??
Registry.LocalMachine.OpenSubKey(
#"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");
if (key == null)
return false;
return key.GetSubKeyNames()
.Select(keyName => key.OpenSubKey(keyName))
.Select(subkey => subkey.GetValue("DisplayName") as string)
.Any(displayName => displayName != null && displayName.Contains(softwareName));
}
Call it with a if-statement:
if (IsSoftwareInstalled("OpenSSL"))
I have checked #Stellan Lindell's code and it doesn't work in all cases.
My version should work in all scenarios and checks the specific version of installed programs(x86, x64).
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
namespace Test
{
internal class Program
{
public enum ProgramVersion
{
x86,
x64
}
private static IEnumerable<string> GetRegisterSubkeys(RegistryKey registryKey)
{
return registryKey.GetSubKeyNames()
.Select(registryKey.OpenSubKey)
.Select(subkey => subkey.GetValue("DisplayName") as string);
}
private static bool CheckNode(RegistryKey registryKey, string applicationName, ProgramVersion? programVersion)
{
return GetRegisterSubkeys(registryKey).Any(displayName => displayName != null
&& displayName.Contains(applicationName)
&& displayName.Contains(programVersion.ToString()));
}
private static bool CheckApplication(string registryKey, string applicationName, ProgramVersion? programVersion)
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
if (CheckNode(key, applicationName, programVersion))
return true;
key.Close();
}
return false;
}
public static bool IsSoftwareInstalled(string applicationName, ProgramVersion? programVersion)
{
string[] registryKey = new [] {
#"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
#"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};
return registryKey.Any(key => CheckApplication(key, applicationName, programVersion));
}
private static void Main()
{
// Examples
Console.WriteLine("Notepad++: " + IsSoftwareInstalled("Notepad++", null));
Console.WriteLine("Notepad++(x86): " + IsSoftwareInstalled("Notepad++", ProgramVersion.x86));
Console.WriteLine("Notepad++(x64): " + IsSoftwareInstalled("Notepad++", ProgramVersion.x64));
Console.WriteLine("Microsoft Visual C++ 2009: " + IsSoftwareInstalled("Microsoft Visual C++ 2009", null));
Console.WriteLine("Microsoft Visual C-- 2009: " + IsSoftwareInstalled("Microsoft Visual C-- 2009", null));
Console.WriteLine("Microsoft Visual C++ 2013: " + IsSoftwareInstalled("Microsoft Visual C++ 2013", null));
Console.WriteLine("Microsoft Visual C++ 2012 Redistributable (x86): " + IsSoftwareInstalled("Microsoft Visual C++ 2013", ProgramVersion.x86));
Console.WriteLine("Microsoft Visual C++ 2012 Redistributable (x64): " + IsSoftwareInstalled("Microsoft Visual C++ 2013", ProgramVersion.x64));
Console.ReadKey();
}
}
}
The solution #Hyperion is ok but it has an error because for 32 bit configurations. No 64 bit registers are returned. To receive 64 bit registers, do the following:
string registryKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey key = key64.OpenSubKey(registryKey);
Solutions above are really good, but sometimes you have to check if product is installed also on another machine. So there is a version based on solutions above from #Stellan Lindell and #Mroczny Arturek
This method works OK for local machine and also remote machine...
public static bool IsSoftwareInstalled(string softwareName, string remoteMachine = null, StringComparison strComparison = StringComparison.Ordinal)
{
string uninstallRegKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryView[] enumValues = (RegistryView[])Enum.GetValues(typeof(RegistryView));
//Starts from 1, because first one is Default, so we dont need it...
for (int i = 1; i < enumValues.Length; i++)
{
//This one key is all what we need, because RegView will do the rest for us
using (RegistryKey key = (string.IsNullOrWhiteSpace(remoteMachine))
? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, enumValues[i]).OpenSubKey(uninstallRegKey)
: RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, remoteMachine, enumValues[i]).OpenSubKey(uninstallRegKey))
{
if (key != null)
{
if (key.GetSubKeyNames()
.Select(keyName => key.OpenSubKey(keyName))
.Select(subKey => subKey.GetValue("DisplayName") as string)
//SomeTimes we really need the case sensitive/insensitive option...
.Any(displayName => displayName != null && displayName.IndexOf(softwareName, strComparison) >= 0))
{ return true; }
}
}
}
return false;
}
The registry version is only one from two standard options.. Another option is using WMI, but the registry one is much better due to performance, so take WMI only as a alternative.
//This one does't have a case sensitive/insesitive option, but if you need it, just don't use LIKE %softwareName%
//and get all products (SELECT Name FROM Win32_Product). After that just go trough the result and compare...
public static bool IsSoftwareInstalledWMI(string softwareName, string remoteMachine = null)
{
string wmiPath = (!string.IsNullOrEmpty(remoteMachine))
? #"\\" + remoteMachine + #"\root\cimv2"
: #"\\" + Environment.MachineName + #"\root\cimv2";
SelectQuery select = new SelectQuery(string.Format("SELECT * FROM Win32_Product WHERE Name LIKE \"%{0}%\"", softwareName));
if (SelectStringsFromWMI(select, new ManagementScope(wmiPath)).Count > 0) { return true; }
return false;
}
There is my SelectStringsFromWMI method, but you can do this on your own, this is not important part of this solution. But if you are intersted, there it is...
public static List<Dictionary<string, string>> SelectStringsFromWMI(SelectQuery select, ManagementScope wmiScope)
{
List<Dictionary<string, string>> result = new List<Dictionary<string, string>>();
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiScope, select))
{
using (ManagementObjectCollection objectCollection = searcher.Get())
{
foreach (ManagementObject managementObject in objectCollection)
{
//With every new object we add new Dictionary
result.Add(new Dictionary<string, string>());
foreach (PropertyData property in managementObject.Properties)
{
//Always add data to newest Dictionary
result.Last().Add(property.Name, property.Value?.ToString());
}
}
return result;
}
}
}
!!UPDATE!!
Due to really bad performance, there is another improvement. Just get values async..
public static bool IsSoftwareInstalled(string softwareName, string remoteMachine = null, StringComparison strComparison = StringComparison.Ordinal)
{
string uninstallRegKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryView[] enumValues = (RegistryView[])Enum.GetValues(typeof(RegistryView));
//Starts from 1, because first one is Default, so we dont need it...
for (int i = 1; i < enumValues.Length; i++)
{
//This one key is all what we need, because RegView will do the rest for us
using (RegistryKey regKey = (string.IsNullOrWhiteSpace(remoteMachine))
? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, enumValues[i]).OpenSubKey(uninstallRegKey)
: RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, remoteMachine, enumValues[i]).OpenSubKey(uninstallRegKey))
{
if (SearchSubKeysForValue(regKey, "DisplayName", softwareName, strComparison).Result)
{ return true; }
}
}
return false;
}
And the SearchSubKeysForValue method (can be build as extension method):
public static async Task<bool> SearchSubKeysForValue(RegistryKey regKey, string valueName, string searchedValue, StringComparison strComparison = StringComparison.Ordinal)
{
bool result = false;
string[] subKeysNames = regKey.GetSubKeyNames();
List<Task<bool>> tasks = new List<Task<bool>>();
for (int i = 0; i < subKeysNames.Length - 1; i++)
{
//We have to save current value for i, because we cannot use it in async task due to changed values for it during foor loop
string subKeyName = subKeysNames[i];
tasks.Add(Task.Run(() =>
{
string value = regKey.OpenSubKey(subKeyName)?.GetValue(valueName)?.ToString() ?? null;
return (value != null && value.IndexOf(searchedValue, strComparison) >= 0);
}));
}
bool[] results = await Task.WhenAll(tasks).ConfigureAwait(false);
result = results.Contains(true);
return result;
}
I tried the solutions here, but they didnt work in some cases. The reason was, that my programm is 32 bit and runs on 64 bit Windows. With the solutions posted here a 32bit process can not check whether a 64 bit application is installed.
How to access 64 bit registry with a 32 bit process
RegistryKey.OpenBaseKey
I modifed the solutions here to get a working one for this issue:
Usage example
Console.WriteLine(IsSoftwareInstalled("Notepad++"));
Code
public static bool IsSoftwareInstalled(string softwareName)
{
var registryUninstallPath = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
var registryUninstallPathFor32BitOn64Bit = #"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
if (Is32BitWindows())
return IsSoftwareInstalled(softwareName, RegistryView.Registry32, registryUninstallPath);
var is64BitSoftwareInstalled = IsSoftwareInstalled(softwareName, RegistryView.Registry64, registryUninstallPath);
var is32BitSoftwareInstalled = IsSoftwareInstalled(softwareName, RegistryView.Registry64, registryUninstallPathFor32BitOn64Bit);
return is64BitSoftwareInstalled || is32BitSoftwareInstalled;
}
private static bool Is32BitWindows() => Environment.Is64BitOperatingSystem == false;
private static bool IsSoftwareInstalled(string softwareName, RegistryView registryView, string installedProgrammsPath)
{
var uninstallKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView)
.OpenSubKey(installedProgrammsPath);
if (uninstallKey == null)
return false;
return uninstallKey.GetSubKeyNames()
.Select(installedSoftwareString => uninstallKey.OpenSubKey(installedSoftwareString))
.Select(installedSoftwareKey => installedSoftwareKey.GetValue("DisplayName") as string)
.Any(installedSoftwareName => installedSoftwareName != null && installedSoftwareName.Contains(softwareName));
}
Here is my version for 64 bits
public static string[] checkInstalled(string findByName)
{
string[] info = new string[3];
string registryKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
//64 bits computer
RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey key = key64.OpenSubKey(registryKey);
if (key != null)
{
foreach (RegistryKey subkey in key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName)))
{
string displayName = subkey.GetValue("DisplayName") as string;
if (displayName != null && displayName.Contains(findByName))
{
info[0] = displayName;
info[1] = subkey.GetValue("InstallLocation").ToString();
info[2] = subkey.GetValue("Version").ToString();
}
}
key.Close();
}
return info;
}
you can call this method like this
string[] JavaVersion = Software.checkInstalled("Java(TM) SE Development Kit");
if the array is empty means no installation found. if it is not empty it will give you the original name, relative path, and location which in most cases that is all we are looking to get.