Current attempt at WTSEnumerateProcesses:
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern Int32 WTSEnumerateProcesses(
IntPtr serverHandle, // Handle to a terminal server.
Int32 reserved, // must be 0
Int32 version, // must be 1
ref IntPtr ppProcessInfo, // pointer to array of WTS_PROCESS_INFO
ref Int32 pCount // pointer to number of processes
);
public struct WTS_PROCESS_INFO
{
public int SessionID;
public int ProcessID;
// This is spointer to a string...
public IntPtr ProcessName;
public IntPtr userSid;
}
public static void ListProcs(String ServerName)
{
IntPtr serverHandle = IntPtr.Zero;
List<string> resultList = new List<string>();
serverHandle = OpenServer(ServerName);
IntPtr ProcessInfoPtr = IntPtr.Zero;
Int32 processCount = 0;
Int32 retVal = WTSEnumerateProcesses(serverHandle, 0, 1, ref ProcessInfoPtr, ref processCount);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_PROCESS_INFO));
Int32 currentProcess = (int)ProcessInfoPtr;
uint bytes = 0;
if (retVal != 0)
{
WTS_PROCESS_INFO pi = (WTS_PROCESS_INFO)Marshal.PtrToStructure((System.IntPtr)currentProcess, typeof(WTS_PROCESS_INFO));
currentProcess += dataSize;
for (int i = 0; i < processCount; i++)
{
MessageBox.Show(pi.ProcessID.ToString());
}
WTSFreeMemory(ProcessInfoPtr);
}
}
I am obviously missing something pretty crucial here, as my listProcs method just returns the same ID over and over again. I need to read up on C APIs and work out what WTSEnumeratEProcesses is actually doing, and how I can query these processes.
Possible solution example code (top answer)
I am creating a self-help IT app for my organisation, where users have the ability to log off their own session as well as display all active processes and select one to terminate.
Users have no problem logging off, but I am having an issue when enumerating processes. Due to the fact that I am using a log in name and password to query active processes, the CMD window is displayed briefly every time this occurs. I can't find any solution to this in the documentation, and was hoping someone could point me in the right direction.
The code is below:
using System.Drawing;
using System;
using System.ComponentModel;
using System.Security;
using System.Diagnostics;
using System.DirectoryServices;
using System.Collections.Generic;
using System.Windows.Forms;
namespace ITHelp
{
class funcs
{
///////////////////////////////////////////////////// GET SERVERS
public static List<string> get_Servers()
{
// Get servers using AD directory searcher
List<string> serverList = new List<string>();
DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE");
string domainContext = rootDSE.Properties["defaultNamingContext"].Value as string;
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://OU=XA76-2012,OU=Servers,OU=XenApp,dc=MYDOMAINNAME1,dc=co,dc=uk");
using (DirectorySearcher searcher = new DirectorySearcher(
searchRoot,
"(&(objectClass=computer)(!(cn=*MASTER*)))",
new string[] { "cn" },
SearchScope.Subtree))
{
foreach (SearchResult result in searcher.FindAll())
{
foreach (string server in result.Properties["cn"])
{
serverList.Add(server);
}
}
}
return serverList;
}
///////////////////////////////////////////////////// GET SESSION
public static string[] get_Session(List<string> servers, string name)
{
string[] sessionDetails = new string[3];
// Iterate through serverList to find the correct connection - then add this to the sessionDetails array
string current = "";
for (int i = 0; i < servers.Count; i++)
{
ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c QUERY SESSION " + name + " /SERVER:" + servers[i] + ".MYDOMAINNAME1.co.uk ")
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process getsess = Process.Start(startInfo);
getsess.OutputDataReceived += (x, y) => current += y.Data;
getsess.BeginOutputReadLine();
getsess.WaitForExit();
if (current.Length != 0)
{
// Session ID
// Better to use this as an identifer than session name, as this is always available
sessionDetails[0] = current.Substring(119, 4);
// Server Name
sessionDetails[1] = servers[i] + ".MYDOMAINNAME1.co.uk";
// Session Name (ica-)
// This is only available if the session is not disconnected
//sessionDetails[2] = current.Substring(76, 11);
// Removed this as it is not used - BUT COULD BE HELPFUL FOR CHECKING SESSION EXISTENCE/DETAILS
break;
}
}
return sessionDetails;
}
///////////////////////////////////////////////////// GET PROCESSES
public static Dictionary<string, string> getProc(string server, string sessID)
{
var ss = new SecureString();
ss.AppendChar('M');
ss.AppendChar('y');
ss.AppendChar('p');
ss.AppendChar('a');
ss.AppendChar('s');
ss.AppendChar('s');
ss.AppendChar('w');
ss.AppendChar('o');
ss.AppendChar('r');
ss.AppendChar('d');
ss.MakeReadOnly();
ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C tasklist /S " + server + " /FI \"SESSION eq " + sessID + "\" /FO CSV /NH")
{
WindowStyle = ProcessWindowStyle.Minimized,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
WorkingDirectory = #"C:\windows\system32",
Verb = "runas",
Domain = "MYDOMAINNAME1",
UserName = "XATest",
Password = ss
};
List<string> procList = new List<string>();
Process proc = Process.Start(startInfo);
proc.OutputDataReceived += (x, y) => procList.Add(y.Data);
proc.BeginOutputReadLine();
proc.WaitForExit();
// Create a new ditionary ...
Dictionary<string, string> procDict = new Dictionary<string, string>();
for (int i = 0; i < procList.Count - 1; i++)
{
if (procDict.ContainsKey(procList[i].Split(',')[0].Trim('"')))
{
// Do nothing
}
else
{
procDict.Add(procList[i].Split(',')[0].Trim('"'), procList[i].Split(',')[1].Trim('"'));
}
}
return procDict;
}
///////////////////////////////////////////////////// RESET SESSION
public static void reset_Session(string sessID, string servName, string name)
{
// Ensure the sesion exists
if (sessID != null)
{
// Log session off
logoff_Session(sessID, servName);
// While isLoggedIn returns true, wait 1 second (checks 50 times)
for (int i = 0; i < 50; i++)
{
if (isLoggedIn(name, servName) == true)
{
System.Threading.Thread.Sleep(1000);
}
else
{
break;
}
}
// Wait here to prevent starting a session while still logged in
System.Threading.Thread.Sleep(3000);
}
// Finally, start the session (Outlook)
start_Session(name);
}
///////////////////////////////////////////////////// LOGOFF SESSION
public static void logoff_Session(string sessID, string servName)
{
Process logoff = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C LOGOFF " + sessID + " /SERVER:" + servName;
logoff.StartInfo = startInfo;
logoff.Start();
}
///////////////////////////////////////////////////// START SESSION
public static void start_Session(string name)
{
// Start Outlook
Process.Start("C:\\Users\\" + name + "\\AppData\\Roaming\\Citrix\\SelfService\\Test_Outlook2013.exe");
}
///////////////////////////////////////////////////// IS LOGGED IN
private static bool isLoggedIn(string name, string server)
{
string current = " ";
ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/c QUERY SESSION " + name + " /SERVER:" + server)
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process logcheck = Process.Start(startInfo);
logcheck.OutputDataReceived += (x, y) => current += y.Data;
logcheck.BeginOutputReadLine();
logcheck.WaitForExit();
if (current.Contains(userName()))
{
return true;
}
else
{
return false;
}
}
///////////////////////////////////////////////////// USERNAME
public static string userName()
{
// Get userName
string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
userName = userName.Remove(0, 8);
return userName;
}
///////////////////////////////////////////////////// KILL PROCESS
public static void killProc(string server, string procid)
{
var ss = new SecureString();
ss.AppendChar('M');
ss.AppendChar('y');
ss.AppendChar('p');
ss.AppendChar('a');
ss.AppendChar('s');
ss.AppendChar('s');
ss.AppendChar('w');
ss.AppendChar('o');
ss.AppendChar('r');
ss.AppendChar('d');
ss.MakeReadOnly();
ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C taskkill /S " + server + " /PID " + procid + " /F")
{
WorkingDirectory = #"C:\windows\system32",
Verb = "runas",
Domain = "MYDOMAINNAME1",
UserName = "XATest",
Password = ss,
WindowStyle = ProcessWindowStyle.Minimized,
UseShellExecute = false,
CreateNoWindow = true
};
Process proc = Process.Start(startInfo);
proc.WaitForExit();
}
///////////////////////////////////////////////////// KILL BUSYLIGHT
public static void killBL()
{
foreach (KeyValuePair<string, string> entry in Program.proclist)
{
if (entry.Key == "Busylight.exe")
{
killProc(Program.servName, entry.Value);
System.Threading.Thread.Sleep(3000);
Process.Start("C:\\Users\\" + Program.name + "\\AppData\\Roaming\\Citrix\\SelfService\\Test_Busylight.exe");
return;
}
// Start BUSYLIGHT - the above method should close the application instantly
}
}
///////////////////////////////////////////////////// KILL LYNC
public static void killLync()
{
foreach (KeyValuePair<string, string> entry in Program.proclist)
{
if (entry.Key == "lync.exe")
{
killProc(Program.servName, entry.Value);
Process.Start("C:\\Users\\" + Program.name + "\\AppData\\Roaming\\Citrix\\SelfService\\Test_SkypeforBusiness.exe");
System.Threading.Thread.Sleep(3000); /////////////////////////////////////////////////////////
return;
}
}
}
///////////////////////////////////////////////////// CHECK RUNNING
public static bool checkRunning(string procName)
{
var ss = new SecureString();
ss.AppendChar('M');
ss.AppendChar('y');
ss.AppendChar('p');
ss.AppendChar('a');
ss.AppendChar('s');
ss.AppendChar('s');
ss.AppendChar('w');
ss.AppendChar('o');
ss.AppendChar('r');
ss.AppendChar('d');
ss.MakeReadOnly();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C tasklist /S " + Program.servName + " /FI \"SESSION eq " + Program.sessID + "\" /FO CSV /NH";
startInfo.WorkingDirectory = #"C:\windows\system32";
startInfo.Verb = "runas";
startInfo.Domain = "MYDOMAINNAME1";
startInfo.UserName = "XATest";
startInfo.Password = ss;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.CreateNoWindow = true;
string strCheck = " ";
Process proc = Process.Start(startInfo);
proc.OutputDataReceived += (x, y) => strCheck += y.Data;
proc.BeginOutputReadLine();
proc.WaitForExit();
if (strCheck.Contains(procName))
{
return true;
}
else
{
return false;
}
}
}
}
Any suggestions or feedback on this much appreciated!
Many thanks
The Remote Desktop Services APIs can certainly do all the things you want. However I'm not sure whether non-admin users are allowed to manipulate their own sessions on other machines.
https://msdn.microsoft.com/en-us/library/windows/desktop/aa383464%28v=vs.85%29.aspx
WTSOpenServer to get a handle to a particular server.
WTSEnumerateProcesses to get a list of processes.
WTSEnumerateSessions to get a list of sessions.
WTSLogoffSession to logoff a specific session.
WTSTerminateProcess to kill a specific process.
Here's some example code using the APIs to enumerate sessions. This is using the constant WTS_CURRENT_SESSION to open the current server, but you can use WTSOpenServer to talk to some other remote server. This is code I hacked out of a live app so it won't compile as-is.
If you program C# long enough you will come across APIs that just don't exist in C# and you have to pinvoke the C versions of the API. I suggest you have a look at http://pinvoke.net if you want help learning how to pinvoke C APIs.
public const int WTS_CURRENT_SESSION = -1;
[StructLayout(LayoutKind.Sequential)]
public struct WTS_SESSION_INFO
{
public Int32 SessionID;
public IntPtr pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}
[DllImport("wtsapi32.dll")]
public static extern bool WTSEnumerateSessions(
IntPtr hServer,
Int32 Reserved,
Int32 Version,
ref IntPtr ppSessionInfo,
ref Int32 pCount);
[DllImport("wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr pMemory);
IntPtr pSessions = IntPtr.Zero;
int count = 0;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessions, ref count))
{
unsafe
{
WTS_SESSION_INFO* pHead = (WTS_SESSION_INFO*)pSessions.ToPointer();
for (int i = 0; i < count; ++i)
{
WTS_SESSION_INFO* pCurrent = (pHead + i);
var session = new Session(pCurrent->SessionID, pCurrent->State);
_activeSessions[pCurrent->SessionID] = session;
session.Id, session.IsConnected, session.IsLoggedOn, session.User.UserName);
}
}
WTSFreeMemory(pSessions);
}
From MSDN site on ProcessStartInfo.CreateNoWindow Property :
Remarks
If the UseShellExecute property is true or the UserName and Password
properties are not null, the CreateNoWindow property value is ignored
and a new window is created.
There is no workaround or resolution mentioned, and I have been unable to find one anywhere.
I have had to resort to me application briefly displaying CMD windows when running certain processes (The CreateNoWindow property works when not using UserName and Password).
Found another solution to this.
As Donovan mentioned, this can be done using WTSEnumerateProcesses.
However, if someone wanted to list remote processes (for a specific session) without marshalling c++ methods, you could also use qprocess:
qprocess /id:10 /server:servername
This lists all processes running on that session.
For more details see here
Related
I am creating a UI for our IT Department that shadow a user's session. First, you have to get the session ID using the following command.
Process Process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo("CMD.exe", "/K" + "qwinsta /server:" + ComputerName + " " + Username);
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = true;
Process = Process.Start(startInfo);
And this is what you get out.
SESSIONNAME USERNAME ID STATE TYPE DEVICE
console mlynch 8 Active
As of right now, this opens the command prompt for this command with the computer name and username you put into a couple of text boxes. You then take the session ID and input it into a 3rd text box and click the connect button. I am trying to bypass this step. Input the computer and username, click connect and it goes. I need to find a way to get the session ID and save it to a string, then reference that string in the next line of code.
if (MouseControlCheck.Checked == true)
{
Process Process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo("CMD.exe", "/C" + "mstsc.exe /shadow:" + SessionID + " /v " + ComputerName + " /control");
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
Process = Process.Start(startInfo);
}
else
{
Process Process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo("CMD.exe", "/C" + "mstsc.exe /shadow:" + SessionID + " /v " + ComputerName);
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
Process = Process.Start(startInfo);
}
How do I read the session ID to a string?
TLDR: I want to skip over console and mlynch and just get to the number under ID. This is not going to be used to get the local machine ID, I want the ID of a computer on the network.
Based on this post, c# how to determine session id of specific logged in windows user, might be easier to just use windows api to get the user session info. This removes the need to call cmd and parse the output. I modified it slightly to get a specific user
class Program
{
static void Main(string[] args)
{
// this code gets the users from localhost -
// can change this to a remote hostname on the network
Console.Write(UserLogins.GetUser("COMPUTER_NAME", "USER_NAME"));
Console.ReadKey();
}
public class UserLogins
{
[DllImport("wtsapi32.dll")]
static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);
[DllImport("wtsapi32.dll")]
static extern void WTSCloseServer(IntPtr hServer);
[DllImport("wtsapi32.dll")]
static extern Int32 WTSEnumerateSessions(
IntPtr hServer,
[MarshalAs(UnmanagedType.U4)] Int32 Reserved,
[MarshalAs(UnmanagedType.U4)] Int32 Version,
ref IntPtr ppSessionInfo,
[MarshalAs(UnmanagedType.U4)] ref Int32 pCount);
[DllImport("wtsapi32.dll")]
static extern void WTSFreeMemory(IntPtr pMemory);
[DllImport("Wtsapi32.dll")]
static extern bool WTSQuerySessionInformation(
System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public Int32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public String pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}
public enum WTS_INFO_CLASS
{
WTSInitialProgram,
WTSApplicationName,
WTSWorkingDirectory,
WTSOEMId,
WTSSessionId,
WTSUserName,
WTSWinStationName,
WTSDomainName,
WTSConnectState,
WTSClientBuildNumber,
WTSClientName,
WTSClientDirectory,
WTSClientProductId,
WTSClientHardwareId,
WTSClientAddress,
WTSClientDisplay,
WTSClientProtocolType
}
public enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
public static IntPtr OpenServer(String Name)
{
IntPtr server = WTSOpenServer(Name);
return server;
}
public static void CloseServer(IntPtr ServerHandle)
{
WTSCloseServer(ServerHandle);
}
public static UserInfo GetUser(string server, string username)
{
return GetUsers(server).FirstOrDefault(c => c.User.Equals(username, StringComparison.InvariantCultureIgnoreCase));
}
public static IEnumerable<UserInfo> GetUsers(String ServerName)
{
IntPtr serverHandle = IntPtr.Zero;
List<String> resultList = new List<string>();
serverHandle = OpenServer(ServerName);
try
{
IntPtr SessionInfoPtr = IntPtr.Zero;
IntPtr userPtr = IntPtr.Zero;
IntPtr domainPtr = IntPtr.Zero;
Int32 sessionCount = 0;
Int32 retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref SessionInfoPtr, ref sessionCount);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int32 currentSession = (int)SessionInfoPtr;
uint bytes = 0;
if (retVal != 0)
{
for (int i = 0; i < sessionCount; i++)
{
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)currentSession, typeof(WTS_SESSION_INFO));
currentSession += dataSize;
WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSUserName, out userPtr, out bytes);
WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSDomainName, out domainPtr, out bytes);
yield return new UserInfo
{
Domain = Marshal.PtrToStringAnsi(domainPtr),
User = Marshal.PtrToStringAnsi(userPtr),
SessionID = si.SessionID
};
WTSFreeMemory(userPtr);
WTSFreeMemory(domainPtr);
}
WTSFreeMemory(SessionInfoPtr);
}
}
finally
{
CloseServer(serverHandle);
}
}
}
public class UserInfo
{
public string Domain { get; set; }
public string User { get; set; }
public int SessionID { get; set; }
public override string ToString()
{
return string.Format("{0}\\{1}: {2}", Domain, User, SessionID);
}
}
}
From MS site
You need to add RedirectStandardOutput = true and then Read Standardoutput.
string[] allLines;
using (Process process = new Process())
{
process.StartInfo.FileName = "ipconfig.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
// Synchronously read the standard output of the spawned process.
StreamReader reader = process.StandardOutput;
string output = reader.ReadToEnd();
// Convert all text to string array
allLines = output.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
// Write the redirected output to this application's window.
Console.WriteLine(output);
process.WaitForExit();
}
An alternative solution to your problem would be to use WMI to get the user information (even remotely), avoiding output redirection altogether:
using Microsoft.Management.Infrastructure;
string Namespace = #"root\cimv2";
string OSQuery = "SELECT * FROM Win32_OperatingSystem";
CimSession mySession = CimSession.Create("Computer_B");
IEnumerable<CimInstance> queryInstance = mySession.QueryInstances(Namespace, "WQL", OSQuery);
I figured out what I had to do.
Process GetSessionID = new Process();
GetSessionID.StartInfo.FileName = "CMD.exe";
GetSessionID.StartInfo.Arguments = "/C" + "for /f \"skip=1 tokens=3\" %1 in ('query user " + Username + "/server:" + ComputerName + "') do #echo %1";
GetSessionID.StartInfo.RedirectStandardOutput = true;
GetSessionID.StartInfo.UseShellExecute = false;
GetSessionID.StartInfo.CreateNoWindow = true;
GetSessionID.Start();
SessionIDOutput = GetSessionID.StandardOutput.ReadToEnd();
GetSessionID.WaitForExit();
DoAllTheThingsTextBox.Text = SessionIDOutput;
if (GetSessionID.HasExited == true)
{
var digitArray = DoAllTheThingsTextBox.Text.Where(Char.IsDigit).ToArray();
SessionID = new String(digitArray);
if (MouseControlCheck.Checked == true)
{
Process Process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo("CMD.exe", "/C" + "mstsc /shadow:" + SessionID + " /v " + ComputerName + " /control");
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
Process = Process.Start(startInfo);
}
else
{
Process Process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo("CMD.exe", "/C" + "mstsc /shadow:" + SessionID + " /v " + ComputerName);
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
Process = Process.Start(startInfo);
}
}
I want my Process does not cross 70% of CPU usage. And i found solution in here:
How can I programmatically limit my program's CPU usage to below 70%? . Here is the class I am trying to use from that link:
public class ProcessManager
{
[Flags]
public enum ThreadAccess : int
{
TERMINATE = (0x0001),
SUSPEND_RESUME = (0x0002),
GET_CONTEXT = (0x0008),
SET_CONTEXT = (0x0010),
SET_INFORMATION = (0x0020),
QUERY_INFORMATION = (0x0040),
SET_THREAD_TOKEN = (0x0080),
IMPERSONATE = (0x0100),
DIRECT_IMPERSONATION = (0x0200)
}
[DllImport("kernel32.dll")]
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
static extern uint SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern int ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern int CloseHandle(IntPtr hThread);
public static void ThrottleProcess(int processId, double limit)
{
var process = Process.GetProcessById(processId);
var processName = process.ProcessName;
var p = new PerformanceCounter("Process", "% Processor Time", processName);
while (true)
{
var interval = 100;
Thread.Sleep(interval);
var currentUsage = p.NextValue() / Environment.ProcessorCount;
if (currentUsage < limit) continue; // Infinant loop ?
var suspensionTime = (currentUsage-limit) / currentUsage * interval;
SuspendProcess(processId);
Thread.Sleep((int)suspensionTime);
ResumeProcess(processId);
}
}
private static void SuspendProcess(int pid)
{
var process = Process.GetProcessById(pid);
if (process.ProcessName == string.Empty)
return;
foreach (ProcessThread pT in process.Threads)
{
IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);
if (pOpenThread == IntPtr.Zero)
{
continue;
}
SuspendThread(pOpenThread);
CloseHandle(pOpenThread);
}
}
private static void ResumeProcess(int pid)
{
var process = Process.GetProcessById(pid);
if (process.ProcessName == string.Empty)
return;
foreach (ProcessThread pT in process.Threads)
{
IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);
if (pOpenThread == IntPtr.Zero)
{
continue;
}
var suspendCount = 0;
do
{
suspendCount = ResumeThread(pOpenThread);
} while (suspendCount > 0);
CloseHandle(pOpenThread);
}
}
}
The author of this code under his post left the comment that says, use ThrottleProcess after Process.Start(). I did it, but it seems like right after Process has started, it gets inside ThrottleProcess and get stucks inside While loop. And i can't figure out what to do with it, maybe it should run method asynchronously? Like Process should run independently of Throttle isn't it?
Here's my Process method:
private string startProcess(string fileName, string args)
{
// Create Process manager
var ProcessManager = new ProcessManager();
string result = "";
Process p;
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = fileName;
psi.Arguments = args;
psi.WorkingDirectory = "...\\TempFolder";
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.StandardOutputEncoding = System.Text.Encoding.UTF8;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
p = Process.Start(psi);
// After it gots here, process get stuck inside while loop
ProcessManager.ThrottleProcess(p.Id , 1);
try
{
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
if (p.ExitCode != 0)
throw new Exception("Program returned with error code " + p.ExitCode);
result = output.ToString();
}
catch (Exception ex)
{
result = ex.ToString();
}
finally
{
p.Close();
p.Dispose();
}
return result;
}
Turns out you can set affinity for a process. Affinity it is the quantity of cores that your process will use. The only thing you need, is just to add for your Process method this string:
Process p = new Process();
p.ProcessorAffinity = (IntPtr)1; // or any number (your cores)
It decreased CPU overload to minimum for my case.
I need to export a registry key from the HKLM hive to a file. This is a key which I've successfully created just a few lines back in the same program. I am using a Process object to have my C# program execute the shell command:
cmd.exe /c regedit.exe /e C:\temp\CDPRegExport.txt HKEY_LOCAL_MACHINE\SOFTWARE\NPTMigration
If I execute my program, the file C:\temp\CDPRegExport.txt is not created. However, if I run the command above directly (in this same administrative console window) it works fine!
I tried adding an application manifest to ensure that my .NET program is running as an administrator.
I tried invoking reg.exe export instead of regedit.exe /e but the result is the same (I'm guessing the same DLL is ultimately being used by these 2 programs).
Here's the main Registry Export method:
static bool RegistryExport(string regKey, string destFile)
{
Cmd cmd = new Cmd()
{
CreateNoWindow = true
};
cmd.Exec(#"regedit.exe", #"/e", destFile, regKey);
Console.WriteLine("Standard Out:\r\n" + cmd.StandardOut);
Console.WriteLine("Standard Error:\r\n" + cmd.StandardErr);
if (!File.Exists(destFile))
{
AppContext.log.Critical(#"Registry export file ({0}) not found!", destFile);
return false;
}
return true;
}
...And here's Cmd.Exe():
public void Exec(string command, params string[] Parameters)
{
string fullyQualifiedCommand = #"/c " + command + GetParameters(Parameters);
Console.WriteLine(fullyQualifiedCommand);
try
{
psi = new ProcessStartInfo(#"cmd", fullyQualifiedCommand)
{
RedirectStandardOutput = true,
RedirectStandardError = true,
};
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
using (Process process = new Process())
{
process.StartInfo = psi;
process.Start();
if (RedirectIOStreams)
{
StandardOut = process.StandardOutput.ReadToEnd();
StandardErr = process.StandardError.ReadToEnd();
}
process.WaitForExit();
}
NormalExit = true;
}
catch (Exception ex)
{
StandardOut = string.Empty;
StandardErr = ex.ToString();
}
}
private static string GetParameters(string[] Parameters)
{
string expression = string.Empty;
if (Parameters.Length == 0)
return string.Empty;
for (int index=0; index<Parameters.Length; index++)
{
if (Parameters[index].Contains(" "))
expression += " \"" + Parameters[index] + "\"";
else
expression += " " + Parameters[index];
}
return expression;
}
When the program uses regedit.exe both standard out and standard error are simply blank.
When it uses reg.exe export however, standard error shows:
"ERROR: The system was unable to find the specified registry key or value."
Again, this is odd because if I invoke the exact same reg.exe or regedit.exe syntax directly via the command window, it works fine!
If 32-bit process on 64-bit OS, you must disable the WOW64 file system redirection
This test works for me (Windows 10, VS 2015 ) =>
bool bWow64 = false;
IsWow64Process(Process.GetCurrentProcess().Handle, out bWow64);
if (bWow64)
{
IntPtr OldValue = IntPtr.Zero;
bool bRet = Wow64DisableWow64FsRedirection(out OldValue);
}
string sKey = #"HKEY_LOCAL_MACHINE\SOFTWARE\NPTMigration";
string sFile = #"C:\temp\CDPRegExport.txt";
using (Process process = new Process())
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.FileName = "reg";
psi.Arguments = "export " + "" + sKey + "" + " " + "" + sFile + "";
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
process.StartInfo = psi;
process.Start();
using (StreamReader reader = process.StandardOutput)
{
string sResult = reader.ReadToEnd();
Console.Write(sResult);
}
}
with declarations :
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool IsWow64Process(IntPtr hProcess, out bool Wow64Process);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Wow64DisableWow64FsRedirection(out IntPtr OldValue);
What am I trying to solve
To improve my build pipeline, I'd like to add an end-to-end test step. I'm planning to achieve it by means of a CLI tool (.NET "Console App"). The tool will spin up and orchestrate a few npm/node commands (processes).
More specifically, there will be:
a back-end process;
a front-end process;
and a test process.
When a test process (3) completes, the CLI tool should terminate back-end (1) and front-end (2) processes gracefully, plus return 0 exit code if every orchestrated process has successfully terminated.
Trouble
In my Minimal, Complete, and Verifiable example below I'm trying to spin up a process serviceAlikeProcess and a failing process (brokenWithErrorProcess). When the latter one fails, I'm trying to forcibly terminate the former one via Kill(process) method.
!!! As it is suggested here, the node/npm processes are being launched via cmd process. I.e. I'm first spinning up a cmd process, and then write node test.js to its stdin stream. The node process gets launched just fine but when the cmd process is terminated later, the node process keeps running and producing the output.
I suppose this happens due to the fact cmd and node processes are not getting linked in a parent-child relationship (because if I manually terminate the cmd process from a Task Manager, I observe same exact behavior).
Question
How do I reliably kill both processes?
Idea: I was thinking about capturing the node process' pid and then terminate both cmd and node processes myself, but I haven't found a way to capture that pid...
Code
Program.cs
using System;
using System.Diagnostics;
using System.IO;
namespace RunE2E
{
public class Program
{
static string currentDirectory = Directory.GetCurrentDirectory();
public static int Main(string[] args)
{
var serviceAlikeProcess = StartProcessViaCmd("node", "test.js", "");
var brokenWithErrorProcess = StartProcessViaCmd("npm", "THIS IS NOT A REAL COMMAND, THEREFORE EXPECTED TO FAIL", "");
brokenWithErrorProcess.Exited += (_, __) => KillProcess(serviceAlikeProcess);
serviceAlikeProcess.WaitForExit();
return serviceAlikeProcess.ExitCode;
}
private static Process StartProcessViaCmd(string command, string arguments, string workingDirectory)
{
workingDirectory = NormalizeWorkingDirectory(workingDirectory);
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = "cmd",
Arguments = arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
}
};
process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "OUTPUT", e.Data);
try
{
Console.WriteLine($"[{workingDirectory}] {command} {arguments}");
var _ = process.Start();
process.BeginOutputReadLine();
process.StandardInput.WriteLine($"{command} {arguments} & exit");
}
catch (Exception exc)
{
Console.WriteLine($"[{workingDirectory}] {command} {arguments} : {exc}");
throw;
}
return process;
}
static string NormalizeWorkingDirectory(string workingDirectory)
{
if (string.IsNullOrWhiteSpace(workingDirectory))
return currentDirectory;
else if (Path.IsPathRooted(workingDirectory))
return workingDirectory;
else
return Path.GetFullPath(Path.Combine(currentDirectory, workingDirectory));
}
static Action<string, string, string, string, string> handle =
(string command, string arguments, string workingDirectory, string level, string message) =>
Console.WriteLine($"[{workingDirectory}] {command} {arguments} {level}: {message}");
static void KillProcess(Process process)
{
if (process != null && !process.HasExited)
process.Kill();
}
}
}
test.js
setInterval(() => {
console.info(new Date());
}, 1000);
Screenshots
Before launching any processes programmatically
After the `cmd` process is killed
Console output
I really really don't like ending up answering my own question(s), especially when the answer is based on a hack-ish way of achieving the results.
However, I understand that this may save somebody else their time. So, here's my solution:
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace RunE2E
{
public class Program
{
static string currentDirectory = Directory.GetCurrentDirectory();
public static int Main(string[] args)
{
var serviceAlikeProcessResult = StartProcessViaCmd("node", "test.js", "");
var serviceAlikeProcess = serviceAlikeProcessResult.MainProcess;
var brokenWithErrorResult = StartProcessViaCmd("npm", "THIS IS NOT A REAL COMMAND, THEREFORE EXPECTED TO FAIL", "");
var brokenWithErrorProcess = brokenWithErrorResult.MainProcess;
brokenWithErrorProcess.Exited += (_, __) =>
{
KillProcesses("Front-End", serviceAlikeProcessResult.MainProcess, serviceAlikeProcessResult.CreatedProcesses);
KillProcesses("E2E-Test", brokenWithErrorResult.MainProcess, brokenWithErrorResult.CreatedProcesses);
};
serviceAlikeProcess.WaitForExit();
return serviceAlikeProcess.ExitCode;
}
private static CommandStartResult StartProcessViaCmd(string command, string arguments, string workingDirectory)
{
workingDirectory = NormalizeWorkingDirectory(workingDirectory);
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = "cmd",
Arguments = arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
},
};
var createdProcesses = new List<Process>();
process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "", e.Data);
var commandId = $"[{workingDirectory}] {command} {arguments}";
try
{
WriteLine(commandId);
createdProcesses = StartProcessAndCapture(commandId, process);
process.BeginOutputReadLine();
process.StandardInput.WriteLine($"{command} {arguments} & exit");
}
catch (Exception exc)
{
WriteLine($"{commandId}: {exc}");
throw;
}
return new CommandStartResult
{
MainProcess = process,
CreatedProcesses = createdProcesses,
};
}
static List<Process> StartProcessAndCapture(string commandId, Process processToStart)
{
var before = Process.GetProcesses().ToList();
var beforePidSet = new HashSet<int>(before.Select(process => process.Id));
var _ = processToStart.Start();
Thread.Sleep(3000);
var after = Process.GetProcesses().ToList();
var newlyCreatedProcessIdList = new HashSet<int>(after.Select(process => process.Id));
newlyCreatedProcessIdList.ExceptWith(beforePidSet);
var createdProcesses = after.Where(process => newlyCreatedProcessIdList.Contains(process.Id)).ToList();
foreach (var process in createdProcesses)
WriteLine($"{commandId} ||| [{process.Id}] {process.ProcessName}", ConsoleColor.Blue);
return createdProcesses;
}
static string NormalizeWorkingDirectory(string workingDirectory)
{
if (string.IsNullOrWhiteSpace(workingDirectory))
return currentDirectory;
else if (Path.IsPathRooted(workingDirectory))
return workingDirectory;
else
return Path.GetFullPath(Path.Combine(currentDirectory, workingDirectory));
}
static Action<string, string, string, string, string> handle =
(string command, string arguments, string workingDirectory, string level, string message) =>
{
var defaultColor = Console.ForegroundColor;
Write($"[{workingDirectory}] ");
Write($"{command} ", ConsoleColor.DarkGreen);
Write($"{arguments}", ConsoleColor.Green);
Write($"{level} ", level == "" ? defaultColor : ConsoleColor.Red);
WriteLine($": {message}");
};
static void KillProcesses(string prefix, Process baseProcess, List<Process> processList)
{
processList = baseProcess == null ?
processList :
processList.Where(process => process.Id != baseProcess.Id).Append(baseProcess).ToList();
foreach (var process in processList)
KillProcess(prefix, process);
}
static void KillProcess(string prefix, Process process)
{
if (process != null && !process.HasExited)
try
{
WriteLine(prefix + " | Kill (" + process.ProcessName + ") [" + process.Id + "]");
process.Kill();
}
catch (Win32Exception win32exc)
{
WriteLine(prefix + " | Kill (" + process.ProcessName + ") [" + process.Id + "]: " + win32exc.Message);
}
}
static void WaitForExit(Process process)
{
while (process.HasExited == false) { }
}
static object console = new object();
static void Write(string text, ConsoleColor? color = null)
{
lock (console)
{
var original = Console.ForegroundColor;
Console.ForegroundColor = color.HasValue ? color.Value : original;
Console.Write(text);
Console.ForegroundColor = original;
}
}
static void WriteLine(string text = null, ConsoleColor? color = null)
{
lock (console)
{
var original = Console.ForegroundColor;
Console.ForegroundColor = color.HasValue ? color.Value : original;
Console.WriteLine(text);
Console.ForegroundColor = original;
}
}
}
class CommandStartResult
{
public Process MainProcess { get; set; }
public List<Process> CreatedProcesses { get; set; }
}
}
Additionally, one may want to use the following methods when dealing with .NET Core processes.
private static CommandStartResult StartDotnetProcess(string arguments, string workingDirectory)
{
var command = "dotnet";
workingDirectory = NormalizeWorkingDirectory(workingDirectory);
var process = PrepareProcess(command, arguments, workingDirectory);
var createdProcesses = new List<Process>();
var commandId = $"[{workingDirectory}] {command} {arguments}";
try
{
WriteLine(commandId);
createdProcesses = StartProcessAndCapture(commandId, process);
process.BeginOutputReadLine();
}
catch (Exception exc)
{
WriteLine($"{commandId} : {exc}");
throw;
}
return new CommandStartResult
{
MainProcess = process,
CreatedProcesses = createdProcesses,
};
}
private static Process PrepareProcess(
string command,
string arguments,
string workingDirectory
)
{
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
FileName = command,
Arguments = arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
},
};
process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "", e.Data);
process.StartInfo.Environment.Add("ASPNETCORE_ENVIRONMENT", "Development");
return process;
}
With this code I see the login window prompting for a password but I can't seem to write the password to the shell window.
Process scp = new Process();
scp.StartInfo.FileName = #"c:\cygwin\bin\scp";
scp.StartInfo.Arguments = "/cygdrive/c" + path + " " + username + "#" + ServerName + ":/cygdrive/c/Temp/.";
scp.StartInfo.UseShellExecute = false;
scp.StartInfo.RedirectStandardOutput = true;
scp.StartInfo.RedirectStandardError = true;
scp.StartInfo.RedirectStandardInput = true;
scp.Start();
//I've tried this with no success:
using (StreamWriter sw = scp.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine(pass);
}
}
// Another failed attempt:
scp.StandardInput.Write(pass + Environment.NewLine);
scp.StandardInput.Flush();
Thread.Sleep(1000);
I know I can get this to work with cygwin expect but would rather use c# to interact with the windows input / output.
Try this:
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
Process scp = new Process();
scp.StartInfo.FileName = #"c:\cygwin\bin\scp";
scp.StartInfo.Arguments = "/cygdrive/c" + path + " " + username + "#" + ServerName + ":/cygdrive/c/Temp/.";
scp.StartInfo.UseShellExecute = false;
scp.StartInfo.RedirectStandardOutput = true;
scp.StartInfo.RedirectStandardError = true;
scp.StartInfo.RedirectStandardInput = true;
scp.Start();
Process[] p = Process.GetProcessesByName("cmd");
SetForegroundWindow(p[0].MainWindowHandle);
SendKeys.SendWait(pass);
scp.WaitForExit();
EDIT: Be sure to include \n at the end of pass.
this code works fine as expected and with no need to call Flush or Sleep:
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.RedirectStandardInput = true;
info.UseShellExecute = false;
p.StartInfo = info;
p.Start();
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine("dir");
}
}
are you 100% sure that your cygwin is just waiting for the pwd?