I'm building an asset tracker of sorts. I'm already searching the registry to get a list of all software titles, publishers, install dates and it's working great. However, programs installed with ClickOnce don't store the install date in the registry(at least not that I can find).
I know I should eb able to use WMI to get the install date, but this is very slow. Also, per this post: Get installed applications in a system
"using the WMI Win32_Product class is a bad idea if you plan to run this query repeatedly"
So, without using WMI, how can I get the install date of ClickOnce programs? I know the information is available somehow because the date is inside of Add/Remove programs.
As ClickOnce application installs per user, you can find uninstall information (what app wizard shows you) by follwing path in registry:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall\5f7eb300e2ea4ebf
This 'Uninstall' has unique hash subkeys, to find your app you can iterate through these keys and filter for example by DisplayName like this:
private RegistryKey GetUninstallRegistryKeyByProductName(string productName)
{
var subKey = Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\Windows\CurrentVersion\Uninstall");
if (subKey == null)
return null;
foreach (var name in subKey.GetSubKeyNames())
{
var application = subKey.OpenSubKey(name, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.QueryValues | RegistryRights.ReadKey | RegistryRights.SetValue);
if (application == null)
continue;
foreach (var appKey in application.GetValueNames().Where(appKey => appKey.Equals("DisplayName")))
{
if (application.GetValue(appKey).Equals(productName))
return application;
break;
}
}
return null;
}
This method returns RegistryKey, then you can get 'DisplayVersion' key value:
var key = GetUninstallRegistryKeyByProductName("myApp");
var version = key.GetValue("DisplayVersion");
Update
Regarding Install date, Try getting last write time of registry key (Getting last write time of "DisplayVersion" is what you need). It looks like there's no managed wrapper for getting this, so use P/Invoke. You need to call RegQueryInfoKey.
Related
My application writes user settings to the registry for easy retrieval but this is causing a little headache because it's writing them as version specific.
So each time I update my application it creates a new subkey pertaining to the new version number.
Settings are written to the registry as:
Application.UserAppDataRegistry.SetValue
I would like to find utilize a simple method to delete all the VERSION subkeys that do not match the current Application version.
At the moment and because my application is relatively new I have resorted to this, which does work but is rather untidy:
Registry.CurrentUser.DeleteSubKey("SOFTWARE\\COMPANY\\PRODUCT\\1.0.0.0", false);
Registry.CurrentUser.DeleteSubKey("SOFTWARE\\COMPANY\\PRODUCT\\1.0.10.0", false);
in the above code I have removed the COMPANY & PRODUCT info deliberately & my application is currently on version 1.0.20.0 so at app start the DeleteSubKey lines are run and they are every time the app is run.
I am thinking that something along the lines of using:
If(PreviousVersion != Application.ProductVersion)
{
Registry.CurrentUser.DeleteSubKey(PreviousVersion);
}
but I am having trouble fleshing this out.
Plus it would be nice to have it only run the one time and not at every startup.
You can list subkeys and check if they're different from current version:
RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\COMPANY\\PRODUCT");
var keys = rk.GetSubKeyNames();
foreach (string s in keys)
{
if (s != "Current_verion_number")
{
Registry.CurrentUser.DeleteSubKeyTree("SOFTWARE\\COMPANY\\PRODUCT\\" + s);
}
}
I'm trying to fetch some data on the Office Click-To-Run currently installed.
I've read these posts : Can't Read Registry Key or OpenSubKey() returns null for a registry key that I can see in regedit.exe
I'm on Windows 10 64bit. My application is an Outlook add-in, so I can't change the target platform (32/64bit), it's the Host that determine if I'm running in 32bit or 64bit. So I've to handle both cases.
So I've this (test) code :
using (var hklmTest64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
{
var test = hklmTest64.GetValue(#"SOFTWARE\Microsoft\Office\ClickToRun\Configuration\AudienceData");
if (test == null)
{
using (var hklmTest32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
{
test = hklmTest32.GetValue(#"SOFTWARE\Microsoft\Office\ClickToRun\Configuration\AudienceData");
}
}
}
So I first fetch the registry key using the 64bit view, then if null I retry using the 32bit view.
Problem: test is always NULL.
Of course the key exists and I can see it using regedit:
Of course I first tried simple code that until now always worked for me, but doesn't work to fetch this key:
test = Registry.LocalMachine.GetValue(#"SOFTWARE\Microsoft\Office\ClickToRun\Configuration\AudienceData");
test = Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration", "AudienceData", null);
And more strange thing, it's that I had exactly the same problem for fetching the windows build number. This code was not working :
var wBuild = Registry.LocalMachine.GetValue(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ReleaseId");
But this one works perfectly for windows build (not for outlook key):
var wBuild = Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId", null);
Any idea how to fetch this registry key ?
UPDATE 1
See the post C# get Office ClickToRun Registry Key returns null
But reading the post, my code using RegistryView.RegistryXX should work...
UPDATE 2
I've reprod the problem using LinqPad, so this is not due to the Outlook host / add-in. I tried to reprod in a .NET Fiddle but indeed the code is not allowed to access the registry :)
I can get the correct values using P/Invoke like explained in this article. So for me it's clearly a 32/64bit (wow6432node) problem.
you dont need
RegistryKey.OpenBaseKey
just use Registry.GetValue(keyName, valueName, defualt value)
this is working example
string InstallPath = (string)Registry.GetValue(#"HKEY_LOCAL_MACHINE\SOFTWARE\" + path, "ActivationCode", null);
I can get the correct values using P/Invoke like explained in this article. So for me it's clearly a 32/64bit (wow6432node) problem.
The problem from
https://stackoverflow.com/a/37859812/4878558
I need to set Registry value for current user, who launch the install up. Since install going for system mode - I don't know anything about current user
Also my code giving 'System.UnauthorizedAccessException'
SecurityIdentifier sID = WindowsIdentity.GetCurrent().User;
var subKey = Registry.Users.OpenSubKey(sID + "\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
subKey.SetValue("test", "test");
enter code here
As Ripple and I have both commented, there's no need for code. Go to the Registry view in the setup project, right-click on Software under HKEY_CURRENT_USER and add the key Microsoft, then Windows, the CurrentVersion, then Run, adding each key.
Then in the Run key view, right-click in the Name, View pane on the right and add new string value, the name being your name. The value, I assume, is the path to your exe, and (assuming it's in the Application folder) make the value [TARGETDIR]my.exe.
If your install is an "Everyone" install then there is a perfectly good reason why it cannot work. This is nothing to do with the code. In an Everyone install that custom action code is running with the System account (NOT the installing user) so you are trying to create a run key for the system account.
Here is how to write autostartup options:
const string AutorunRegistryKey = #"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run";
Registry.SetValue(AutorunRegistryKey, <AppName>, <PathToApplication>);
If you want to remove it from autostartup:
const string AutorunRelativePath = #"Software\Microsoft\Windows\CurrentVersion\Run\";
var key = Registry.CurrentUser.OpenSubKey(AutorunRelativePath, true);
if (key != null)
{
key.DeleteValue(<AppName>, false);
key.Close();
}
I am making a software in C# and MATLAB that calls another software (CMG) to do some processing. My problem is that the address of the software I have put in my program is only correct on my personal computer and not on the customers' computers (I don't know what would be the path to CMG software on their computer).
How can I provide a general form of the address in order to make it work on every computer?
The following is the path I call from my MATLAB software:
C:\Program Files (x86)\CMG\STARS\2011.10\Win_x64\EXE\st201110.exe
As you see it is in drive C and the version is 2011.10. So if customer's version is something else and it is installed on other drives, this path makes no sense.
Method 1
The registry keys SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall provides a list of where most applications are installed:
Note: It doesn't list all EXE applications on the PC as some dont require installation.
In your case I am pretty sure that CMG STARS will be listed and you will be able to search for it by iterating over all subkeys looking at the DisplayName value and fetching the InstallLocation.
Also note that this Uninstall registry key exists in 3 places in the registry:
1. SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall inside CurrentUser
2. SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall inside LocalMachine
3. SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall in LocalMachine
Here is an class that returns the installed location of an application:
using Microsoft.Win32;
public static class InstalledApplications
{
public static string GetApplictionInstallPath(string nameOfAppToFind)
{
string installedPath;
string keyName;
// search in: CurrentUser
keyName = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
installedPath = ExistsInSubKey(Registry.CurrentUser, keyName, "DisplayName", nameOfAppToFind);
if (!string.IsNullOrEmpty(installedPath))
{
return installedPath;
}
// search in: LocalMachine_32
keyName = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
installedPath = ExistsInSubKey(Registry.LocalMachine, keyName, "DisplayName", nameOfAppToFind);
if (!string.IsNullOrEmpty(installedPath))
{
return installedPath;
}
// search in: LocalMachine_64
keyName = #"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
installedPath = ExistsInSubKey(Registry.LocalMachine, keyName, "DisplayName", nameOfAppToFind);
if (!string.IsNullOrEmpty(installedPath))
{
return installedPath;
}
return string.Empty;
}
private static string ExistsInSubKey(RegistryKey root, string subKeyName, string attributeName, string nameOfAppToFind)
{
RegistryKey subkey;
string displayName;
using (RegistryKey key = root.OpenSubKey(subKeyName))
{
if (key != null)
{
foreach (string kn in key.GetSubKeyNames())
{
using (subkey = key.OpenSubKey(kn))
{
displayName = subkey.GetValue(attributeName) as string;
if (nameOfAppToFind.Equals(displayName, StringComparison.OrdinalIgnoreCase) == true)
{
return subkey.GetValue("InstallLocation") as string;
}
}
}
}
}
return string.Empty;
}
}
Here is how you call it:
string installPath = InstalledApplications.GetApplictionInstallPath(nameOfAppToFind);
To get the nameOfAppToFind you'll need to look in the registry at the DisplayName:
REF: I modified the above code from here to return the install path.
Method 2
You can also use the System Management .Net DLL to get the InstallLocation although it is heaps slower and creates "Windows Installer reconfigured the product" event log messages for every installed product on your system.
using System.Management;
ManagementObjectSearcher mos = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
foreach (ManagementObject mo in mos.Get())
{
Debug.Print(mo["Name"].ToString() + "," + mo["InstallLocation"].ToString() + Environment.NewLine);
}
Getting the EXE's name
Neither of the above methods tell you the name of the executable, however it is quite easy to work out by iterating over all the files in the install path and using a technique I discuss here to look at file properties to detect the EXE with the correct File Description, eg:
private string GetFileExeNameByFileDescription(string fileDescriptionToFind, string installPath)
{
string exeName = string.Empty;
foreach (string filePath in Directory.GetFiles(installPath, "*.exe"))
{
string fileDescription = GetSpecificFileProperties(filePath, 34).Replace(Environment.NewLine, string.Empty);
if (fileDescription == fileDescriptionToFind)
{
exeName = GetSpecificFileProperties(filePath, 0).Replace(Environment.NewLine, string.Empty);
break;
}
}
return exeName;
}
Either method (1 or 2) you use I recommend that you save the location of exe name so you only do this operation once. In my opinion its better to use Method 1 as its faster and doesn't create all the "Windows Installer reconfigured the product." event logs.
Alternate Method using an Installer
If your application is being installed you could find out where CMG STARS is located during installation Using Windows Installer to Inventory Products and Patches:
Enumerating Products
Use the MsiEnumProductsEx function to enumerate Windows Installer applications that are installed in the
system. This function can find all the per-machine installations and
per-user installations of applications (managed and unmanaged) for the
current user and other users in the system. Use the dwContext
parameter to specify the installation context to be found. You can
specify any one or any combination of the possible installation
contexts. Use the szUserSid parameter to specify the user context of
applications to be found.
During installation you would find the exe path to CMG STARS and save a registry key with the value.
I discuss using this approach of saving an EXE's install path in the registry for updating applications here.
Tip
As mentioned in the comments, it is worthwhile you do a search in the registry for the EXE's name st201110.exe and see if the authors of the CMG STAR application already provide this information in a registry key you can access directly.
Plan B
If all else fails present the user with a FileOpenDialog and get them to specify the exe's path manually.
What if the 3rd party application is uninstalled or upgraded?
I mentioned to store the install path and exe name in the registry (or database, config file, etc) and you should always check the exe file exists before making any external calls to it, eg:
if (!File.Exists(installPath + exeName))
{
//Run through the process to establish where the 3rd party application is installed
}
I'm currently building a registry explorer, mostly because I want to support some much better searching operations. Like 'find all' and regular expressions. One problem I'm facing is that some keys throw a security exception when opening. I HAVE TRIED running the app with administrator priviledges, my user account is an administrator also. I embedded a manifest with "requireAdministrator" requested priviledges. I have also tried setting the ClickOnce security settings to Full trust, which is incompatible with requireAdministrator, or so Visual Studio tells me.... Nothing seems to help with avoiding this exception.
I would just like to iterate over all of the keys. I do not wish to add/delete keys. If a user wishes to delete a key and does not have permission to do so, it would display an error message. I just want to be able to have unrestricted READ access. Is this possible?
FTR: I'm on Win7 x64 and using Vs2010u and project is written in C# on .net 4.0. If regedit is capable of reading all keys even if it doesn't let you edit some of them. It would seem appropriate that we too can make an app to do the same thing. Though I'm finding it very difficult, and there doesn't seem to be any real help on the www. Only link-link circles, yay.
[EDIT]
Here's the code that reads the keys:
private void IterateSubKeys(RegistryKeyModel key) {
var subKeys = key.Key.GetSubKeyNames();
var values = key.Key.GetValueNames();
foreach (var valuename in values) {
try {
var valueKind = key.Key.GetValueKind(valuename);
var value = key.Key.GetValue(valuename);
key.Values.Add(new RegistryValueModel(valuename, value, valueKind));
}
catch { }
}
foreach (var keyname in subKeys) {
try {
var subkey = key.Key.OpenSubKey(
keyname,
RegistryKeyPermissionCheck.ReadSubTree,
RegistryRights.ReadKey);
key.SubKeys.Add(new RegistryKeyModel(subkey));
}
catch { Console.WriteLine("Error reading key: {0}", keyname); }
}
}
This is by design. There are lots of security related keys that can only accessible to the System account. You can't use that account. Regedit can't read these keys either, they are just not visible. Avoiding the expensive exception is going to require pinvoke.