How can one get the corresponding process name of the installed programs in Windows (10)? For now, I'm using this
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))
{
//returns installed programs
}
}
}
to return the installed software. Despite not every installed program being shown, how can I get the name of the process, like it would be shown in Task Manager, that the program would start if it was started?
I want to make an application blacklist. If an application gets started it compares its process with the blacklist. If the process matches with an entry in the list, the process gets killed.
Use static method GetProcesses of Process class to create component for each running process on the local computer.
You can get their names like this:
var processNames = Process.GetProcesses().Select(x => x.ProcessName).ToList();
More about Process class here:
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=net-6.0
You should consider to use the Windows integrated feature to block applications via the registry. You can create such entries programmatically.
However, you can implement your own, but you must know that you can't prevent applications from starting using your approach. You can only kill it after it was started and after it has allocated resources.
Create your blacklist first: collect all installed application paths and let the user pick the application to blacklist (see CreateInstalledApplicationIndex method).
Use WMI to observe any process starts by registering a corresponding event handler.
In the event handler retrieve the started Process and compare its filename to your blacklisted filenames to identify and handle a forbidden process.
private List<FileInfo> InstallationInfos { get; } = new List<FileInfo>();
private List<FileInfo> BlacklistedExecutables { get; } = new List<FileInfo>();
public void ApplyBlacklist()
{
CreateInstalledApplicationIndex();
WatchProcessStarts();
}
private void CreateInstalledApplicationIndex()
{
string uninstallKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(uninstallKey);
foreach (string subKeyName in registryKey.GetSubKeyNames())
{
using RegistryKey subKey = registryKey.OpenSubKey(subKeyName);
var installationPath = subKey.GetValue("InstallLocation") as string;
if (string.IsNullOrWhiteSpace(installationPath))
{
continue;
}
IEnumerable<FileInfo> fileInfos = Enumerable.Empty<FileInfo>();
try
{
var installationDirectoryInfo = new DirectoryInfo(installationPath);
fileInfos = installationDirectoryInfo.EnumerateFiles("*.exe", new EnumerationOptions());
}
catch (IOException)
{
continue;
}
foreach (FileInfo fileInfo in fileInfos)
{
this.InstallationInfos.Add(fileInfo);
// For demo, all executables are blacklisted.
// TODO::Let user fill Blacklisted collection.
this.BlacklistedExecutables.Add(fileInfo);
}
}
}
private void WatchProcessStarts()
{
WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
watcher.EventArrived += OnProcessStarted;
// Start listening for process start events
watcher.Start();
// Stop listening for process start events
//watcher.Stop();
}
private void OnProcessStarted(object sender, EventArrivedEventArgs e)
{
uint startedProcessId = (uint)e.NewEvent["ProcessID"];
// Note: Convert.ToInt32 will throw an OverflowException
// in case uint does not fit into an int.
// You must decide whether to handle this particular exception or to let it crash your application.
// Since it is very very unlikely that a machine runs Int32.MaxValue processes,
// I recommend not to handle this exception.
Process startedProcess = Process.GetProcessById(Convert.ToInt32(startedProcessId));
bool isProcessBlacklisted = this.BlacklistedExecutables
.Select(fileInfo => fileInfo.FullName)
.Contains(startedProcess.MainModule.FileName);
// TODO::Handle blacklisted process e.g., by killing it
if (isProcessBlacklisted)
{
startedProcess.Kill(entireProcessTree: true);
}
}
It is possible that you have to run your application as administrator in order to observe process starts and to kill them. In this case ensure to prompt the user to elevate your application's rights by restarting it with administrator permissions.
I got a solution which looks like this:
First I get all installed programs based on this
public static void LoadInstalledPrograms()
{
var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);
foreach (var app in (IKnownFolder)appsFolder)
{
//regular installed programs
if (app.Properties.System.Link.TargetParsingPath.Value != null)
{
AddToInstalledProgramsList(app.Name, app.Properties.System.Link.TargetParsingPath.Value, "reg");
}
//Windows apps/Microsoft store apps
/*else
{
AddToInstalledProgramsList(app.Name, app.Properties.GetProperty("System.AppUserModel.PackageInstallPath").ValueAsObject.ToString(), "win");
}*/
}
}
and then write them to a dictionary which is observed by a BackgroundWorker who kills every process from the list
static Dictionary<String, String> programs = new Dictionary<String, String>();
public static void AddToInstalledProgramsList(string programName, string programPath, string programType)
{
string processName = "";
if (programType == "reg")
{
programPath = programPath.Replace("/", "\\");
processName = programPath.Split("\\").Last();
if (!programs.ContainsKey(programName))
{
programs.Add(programName, processName);
}
else
{
AddDuplicateEntry(programName, processName, 1);
}
}
else if (programType == "win")
{
//...
}
Debug.WriteLine(programName + ": " + processName);
}
If I stumble across problems with this approach I will update this thread.
For example my application creates mutex's like so:
MyApplication\\{UserName}
and then my updater program (that updates this application) needs to check whether this has been created or not but it doesn't know the username.
I have the code:
string mutexString = "MyApplication\\User1"
bool isNew;
var mutex = new Mutex(true, mutexString , out isNew);
if(isNew)
{
//Run my program
}
This works fine if I know the username but I would like to know if it is possible for this to work with wildcards?
So from the comments I get the sense that this is impossible so a better solution might be to check the running processes which I achieved like so:
foreach (var process in Process.GetProcesses())
{
if (process.MainWindowTitle.IndexOf("MyApp",StringComparison.InvariantCultureIgnoreCase) >= 0)
{
isNew = false;
}
}
I am tracking multiple instances of the same application and need to get the memory and cpu use of both processes. However, I cant seem to figure out a way to use the performance counter and know which result is for which process. I have seen that I can append #1 and such to the end of the name to get results for each, but that doesn't tell me which one is for which process.
How can I determine the ProcessId or pass the process ID to the counter to get the result per each process with same name?
PerformanceCounterCPU.CategoryName = "Process";
PerformanceCounterCPU.CounterName = "% Processor Time";
PerformanceCounterCPU.InstanceName = proc.ProcessHandle.ProcessName;
PerformanceCounterMemory.CategoryName = "Process";
PerformanceCounterMemory.CounterName = "Working Set - Private";
PerformanceCounterMemory.InstanceName = proc.ProcessHandle.ProcessName;
This answer to a related question might work:
private static string GetProcessInstanceName(int pid)
{
PerformanceCounterCategory cat = new PerformanceCounterCategory("Process");
string[] instances = cat.GetInstanceNames();
foreach (string instance in instances)
{
using (PerformanceCounter cnt = new PerformanceCounter("Process",
"ID Process", instance, true))
{
int val = (int) cnt.RawValue;
if (val == pid)
{
return instance;
}
}
}
throw new Exception("Could not find performance counter " +
"instance name for current process. This is truly strange ...");
}
If you don't mind a machine-wide registry change, you can configure Windows to use the form ProcessName_ProcessID for Perf Counter instance names, rather than appending #1, #2, etc:
Create DWORD HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PerfProc\Performance\ProcessNameFormat and set its value to 2.
If you do stick with the #1, #2 etc form, beware that the instance name for a given process can change during the process' lifetime!
This is similar to, but not a dupe of, this question - however, where it sought information on manually joining a server to a domain (and was rightly redirected) I am looking for help with some code that programmatically joins a machine to a domain.
The scenario is that we have a launcher service that instantiates Amazon EC2 Server2008R1 VMs, optionally passing a Machine Name in through the User-Data stream. A process is baked into our images that checks User-Data for a name on bootup - If none is present then the VM remains outside of our Cloud domain, but if the name is present then the machine is renamed as specified and auto-joined to the domain.
Here's the problem - if I run this process manually any time after instance start, it works exactly as described; the machine name is changed, and the VM is joined to the domain (we force a restart to make this happen).
However, when running as a Scheduled Task (triggered on startup) the machine rename happens as expected, but the subsequent call to JoinDomainOrWorkgroup (see below) picks-up the old randomised machine name given to the VM by EC2 instead of the new name it has just been assigned.
This results in a WMI return code of 8525, we get a disconnected misnamed entry in the AD repository (of that randomised name) and the machine is not joined to the domain. The VM then restarts, and a second pass through the startup process (abnormally triggered because there is content in User-Data but the machine is not yet in the domain) executes all the same steps and succeeds.
It looks like the machine name is set in the first pass but not 'finalised', and JoinDomainOrWorkgroup still sees the original name. On the second pass, the machine name is already set properly, and so JoinDomainOrWorkgroup works as expected. Quite why the process behaves this way during startup, but works perfectly when run manually on an already-started VM, is I think the nub of the problem.
I've tried inserting a delay between the rename and join steps in case the call to JoinDomainOrWorkgroup was happening before the rename was finalised behind the scenes, but this hasn't helped - and I didn't really expect it to, since the whole process works perfectly when run manually. So it's probably a combination of a subtle difference in machine state during bootup and something silly in the code.
Maybe using System.Environment.MachineName in the SetDomainMembership method is inadvisable? But it stil fails even if I pass the new name in as a string as I do for SetMachineName. So I'm stumped.
Here's the WMI code that renames the machine:
/// <summary>
/// Set Machine Name
/// </summary>
public static bool SetMachineName(string newName)
{
_lh.Log(LogHandler.LogType.Debug, string.Format("Setting Machine Name to '{0}'...", newName));
// Invoke WMI to populate the machine name
using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")))
{
ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("Rename");
inputArgs["Name"] = newName;
// Set the name
ManagementBaseObject outParams = wmiObject.InvokeMethod("Rename", inputArgs, null);
// Weird WMI shennanigans to get a return code (is there no better way to do this??)
uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
if (ret == 0)
{
// It worked
return true;
}
else
{
// It didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to change Machine Name from '{0}' to '{1}'", System.Environment.MachineName, newName));
return false;
}
}
}
And here's the WMI code that joins it to the domain:
/// <summary>
/// Set domain membership
/// </summary>
public static bool SetDomainMembership()
{
_lh.Log(LogHandler.LogType.Debug, string.Format("Setting domain membership of '{0}' to '{1}'...", System.Environment.MachineName, _targetDomain));
// Invoke WMI to join the domain
using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + System.Environment.MachineName + "'")))
{
try
{
// Obtain in-parameters for the method
ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");
inParams["Name"] = "*****";
inParams["Password"] = "*****";
inParams["UserName"] = "*****";
inParams["FJoinOptions"] = 3; // Magic number: 3 = join to domain and create computer account
// Execute the method and obtain the return values.
ManagementBaseObject outParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);
_lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", outParams["ReturnValue"]));
// Did it work? ** disabled so we restart later even if it fails
//uint ret = (uint)(outParams.Properties["ReturnValue"].Value);
//if (ret != 0)
//{
// // Nope
// _lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", outParams["ReturnValue"]));
// return false;
//}
return true;
}
catch (ManagementException e)
{
// It didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to join domain '{0}'", _targetDomain), e);
return false;
}
}
}
Apologies if this code looks mind-numbingly stupid - I'm new to WMI, and this is largely cribbed from examples I've found on the interwebs; if there's a smarter/neater way to do this then by all means demonstrate. If you can cure the problem at the same time, bonus points!
OK, here it is.
Firstly, the order of the fields in System Properties is a little misleading - you see Machine Name first, and Domain/Workgroup below that. This subconsciously affected my thinking, and meant my code copied that ordering by trying to set the name first, and then join the machine to the domain. Whilst this does work under some circumstances, it's not consistent or reliable. So the biggest lesson learned here is...
Join the domain first - then change
the machine name.
Yep, that's actually all there is to it. After numerous test iterations, it finally dawned on me that it might work better if I tried it this way around. I tripped-up on the change of name on my first pass, but quickly realised that it was still using the local system credentials - but now that the machine was joined to the domain at this point, it needed the same domain credentials as were used to join the domain itself. A fast bit of code-tweaking later, and we now have a consistently-reliable WMI routine that joins the domain and then changes the name.
It might not be the neatest implementation (feel free to comment on improvements) but it works. Enjoy.
/// <summary>
/// Join domain and set Machine Name
/// </summary>
public static bool JoinAndSetName(string newName)
{
_lh.Log(LogHandler.LogType.Debug, string.Format("Joining domain and changing Machine Name from '{0}' to '{1}'...", Environment.MachineName, newName));
// Get WMI object for this machine
using (ManagementObject wmiObject = new ManagementObject(new ManagementPath("Win32_ComputerSystem.Name='" + Environment.MachineName + "'")))
{
try
{
// Obtain in-parameters for the method
ManagementBaseObject inParams = wmiObject.GetMethodParameters("JoinDomainOrWorkgroup");
inParams["Name"] = "domain_name";
inParams["Password"] = "domain_account_password";
inParams["UserName"] = "domain_account";
inParams["FJoinOptions"] = 3; // Magic number: 3 = join to domain and create computer account
_lh.Log(LogHandler.LogType.Debug, string.Format("Joining machine to domain under name '{0}'...", inParams["Name"]));
// Execute the method and obtain the return values.
ManagementBaseObject joinParams = wmiObject.InvokeMethod("JoinDomainOrWorkgroup", inParams, null);
_lh.Log(LogHandler.LogType.Debug, string.Format("JoinDomainOrWorkgroup return code: '{0}'", joinParams["ReturnValue"]));
// Did it work?
if ((uint)(joinParams.Properties["ReturnValue"].Value) != 0)
{
// Join to domain didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("JoinDomainOrWorkgroup failed with return code: '{0}'", joinParams["ReturnValue"]));
return false;
}
}
catch (ManagementException e)
{
// Join to domain didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to join domain '{0}'", _targetDomain), e);
return false;
}
// Join to domain worked - now change name
ManagementBaseObject inputArgs = wmiObject.GetMethodParameters("Rename");
inputArgs["Name"] = newName;
inputArgs["Password"] = "domain_account_password";
inputArgs["UserName"] = "domain_account";
// Set the name
ManagementBaseObject nameParams = wmiObject.InvokeMethod("Rename", inputArgs, null);
_lh.Log(LogHandler.LogType.Debug, string.Format("Machine Rename return code: '{0}'", nameParams["ReturnValue"]));
if ((uint)(nameParams.Properties["ReturnValue"].Value) != 0)
{
// Name change didn't work
_lh.Log(LogHandler.LogType.Fatal, string.Format("Unable to change Machine Name from '{0}' to '{1}'", Environment.MachineName, newName));
return false;
}
// All ok
return true;
}
}
Ok little update after all these years if somebody would need it.
WMI does not contain JoinDomain anymore only workgroup (WIN 10, Build 1909). You can use netapi32.dll
More info here:
https://learn.microsoft.com/en-us/windows/win32/api/lmjoin/nf-lmjoin-netjoindomain
Little fast example:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
static extern uint NetJoinDomain(
string lpServer,
string lpDomain,
string lpAccountOU,
string lpAccount,
string lpPassword,
JoinOptions NameType);
[Flags]
enum JoinOptions
{
NETSETUP_JOIN_DOMAIN = 0x00000001,
NETSETUP_ACCT_CREATE = 0x00000002,
NETSETUP_ACCT_DELETE = 0x00000004,
NETSETUP_WIN9X_UPGRADE = 0x00000010,
NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020,
NETSETUP_JOIN_UNSECURE = 0x00000040,
NETSETUP_MACHINE_PWD_PASSED = 0x00000080,
NETSETUP_DEFER_SPN_SET = 0x10000000
}
public static uint domainjoin(string server, string domain, string OU, string account, string password)
{
try
{
uint value1 = NetJoinDomain(server, domain, OU, account, password, (JoinOptions.NETSETUP_JOIN_DOMAIN | JoinOptions.NETSETUP_DOMAIN_JOIN_IF_JOINED | JoinOptions.NETSETUP_ACCT_CREATE));
return value1;
}
catch (Exception e)
{
MessageBox.Show(e.Message);
return 11;
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var succes = domainjoin(null, "mydomain.local", null, "administrator", "UltraSecretPasword");
MessageBox.Show(succes.ToString());
}
}
I need to find a way to check if an Active Directory UserAccount has his account locked or not.
I've tried userAccountControl property in a Windows 2000 AD but that property does not change a byte when I force an account to get locked (by trying to log on to a workstation providing the wrong password for that specific user) And I can tell by using ADExplorer.exe utility made by semi-god -> Mr. Russinovich
I've seen that in the 3.5 Framework they use the method .InvokeGet("userLockedOut"); but I'm trying to do this in a Enterprise Application that was written in .Net Framework 1.1 and there's no chance of using newer ones (just if you thought of suggesting so).
Here is a link with all the info on Active Directory stuff...
http://www.codeproject.com/KB/system/everythingInAD.aspx
Found this, it is a little more than I have done in the past (can't find exact snippets) though the key is doing a directory search and limiting based on the lockouttime for your user(s) that are returned. Additionally for a particular user, you can limit your search further using additional properties. The codeproject link above has that particular logic (for search limiting) I believe.
class Lockout : IDisposable
{
DirectoryContext context;
DirectoryEntry root;
DomainPolicy policy;
public Lockout(string domainName)
{
this.context = new DirectoryContext(
DirectoryContextType.Domain,
domainName
);
//get our current domain policy
Domain domain = Domain.GetDomain(this.context);
this.root = domain.GetDirectoryEntry();
this.policy = new DomainPolicy(this.root);
}
public void FindLockedAccounts()
{
//default for when accounts stay locked indefinitely
string qry = "(lockoutTime>=1)";
TimeSpan duration = this.policy.LockoutDuration;
if (duration != TimeSpan.MaxValue)
{
DateTime lockoutThreshold =
DateTime.Now.Subtract(duration);
qry = String.Format(
"(lockoutTime>={0})",
lockoutThreshold.ToFileTime()
);
}
DirectorySearcher ds = new DirectorySearcher(
this.root,
qry
);
using (SearchResultCollection src = ds.FindAll())
{
foreach (SearchResult sr in src)
{
long ticks =
(long)sr.Properties["lockoutTime"][0];
Console.WriteLine(
"{0} locked out at {1}",
sr.Properties["name"][0],
DateTime.FromFileTime(ticks)
);
}
}
}
public void Dispose()
{
if (this.root != null)
{
this.root.Dispose();
}
}
}
Code was pulled from this post: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/5e0fadc2-f27b-48f6-a6ac-644e12256c67/
After seeing the .NET 1.1, check this thread out: http://forums.asp.net/t/434077.aspx, using the lockouttime in the filter should still do the trick.
Specifically in the thread (after the larger code post which provides alot of the syntax):
(&(objectClass=user)(objectCategory=person)(lockoutTime>=1));
One other thing, it turns out that if you are using .NET v.1.1, then S.DS converts the Integer8 to the long integer correctly for you (does not work with 1.0) - which means you can do away with reflection code (in the post):
//use the filter from above
SearchResultCollection src = ds.FindAll();
foreach(SearchResult sr in src)
{
DateTime lockoutTime = DateTime.FromFileTime((long)sr.Properties["lockoutTime][0]);
Response.Output.Write("Locked Out on: {0}", lockoutTime.ToString());
}