c# - ¿Cuál es la mejor manera de vigilar una aplicación de escritorio?
.net windows-services (3)
Necesito alguna forma de monitorear una aplicación de escritorio y reiniciarla si muere.
Inicialmente asumí que la mejor manera sería monitorear / reiniciar el proceso desde un servicio de Windows, hasta que descubrí que los servicios de Windows Vista no deberían interactuar con el escritorio
He visto varias preguntas relacionadas con este problema, pero cada respuesta que he visto involucraba algún tipo de piratería que Microsoft no recomienda y probablemente dejará de funcionar en futuras actualizaciones del sistema operativo.
Por lo tanto, un servicio de Windows probablemente ya no sea una opción. Probablemente podría crear una aplicación de escritorio / consola diferente para hacer esto, pero ese tipo de derrota cumple su propósito.
¿Cuál sería la forma más elegante de lograr esto, en su opinión?
EDITAR: Esto no es ni malware ni virus. La aplicación que necesita supervisión es un reproductor multimedia que se ejecutará en un sistema integrado, y aunque estoy tratando de cubrir todos los posibles escenarios de bloqueo, no puedo arriesgarme a que se bloquee por un error inesperado (s ** t sucede) . Este perro guardián sería solo una garantía en caso de que todo lo demás salga mal. Además, dado que el reproductor mostraría contenido flash de terceros, una ventaja adicional sería, por ejemplo, monitorear el uso de recursos y reiniciar el reproductor si, por ejemplo, alguna película flash de mala calidad comienza a perder memoria.
EDIT 2: olvidé mencionar, la aplicación que me gustaría monitorear / reiniciar no tiene ninguna necesidad de ejecutarse en la cuenta LocalSystem ni con ningún privilegio administrativo. En realidad, prefiero que se ejecute utilizando las credenciales de usuario registradas actualmente.
Inicialmente asumí que la mejor manera sería monitorear / reiniciar el proceso desde un servicio de Windows ...
¡Seguro que puede! Lo hice hace algunas veces. Puedes empezar a aprender cómo ver esto:
http://msdn.microsoft.com/en-us/windows7trainingcourse_win7session0isolation_topic2#_Toc243675529
y esto:
http://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s
En esencia, tiene que ejecutar programas como SISTEMA, pero con el SessionID del usuario actual.
Si te sientes perezoso, supongo que podría haber algunos buenos servicios que hacen lo que estás buscando. Intenta buscar en www.codeproject.com .
El proceso de vigilancia podría hacer uso de System.Diagnostics.Process
para iniciar la aplicación, usar WaitForExitMethod()
y verificar la propiedad ExitCode
.
En respuesta a las quejas sobre la pregunta, tuve que usar un método de este tipo cuando trabajaba con una aplicación de centro de llamadas heredada sobre la cual no tenía acceso de control de fuente.
EDITAR:
Para la aplicación host, podría usar una aplicación .NET del tipo de salida "Aplicación de Windows" y simplemente no tener un formulario. Por ejemplo:
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var info = new ProcessStartInfo(@"calc.exe");
var process = Process.Start(info);
process.WaitForExit();
MessageBox.Show("Hello World!");
}
}
}
Finalmente implementé la solución sugerida por @ A_nto2 y logró exactamente lo que estaba buscando: ahora tengo un servicio de Windows que controla una lista de procesos y, cuando están inactivos, se vuelven a iniciar automáticamente usando las credenciales y sesión del usuario activo. , por lo que la GUI es visible.
Sin embargo, ya que los enlaces que publicó muestran el código VC ++, estoy compartiendo mi implementación de C # para cualquiera que trate el mismo problema:
public static class ProcessExtensions
{
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
[Flags]
public enum CREATE_PROCESS_FLAGS : uint
{
NONE = 0x00000000,
DEBUG_PROCESS = 0x00000001,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
CREATE_SUSPENDED = 0x00000004,
DETACHED_PROCESS = 0x00000008,
CREATE_NEW_CONSOLE = 0x00000010,
NORMAL_PRIORITY_CLASS = 0x00000020,
IDLE_PRIORITY_CLASS = 0x00000040,
HIGH_PRIORITY_CLASS = 0x00000080,
REALTIME_PRIORITY_CLASS = 0x00000100,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_FORCEDOS = 0x00002000,
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
INHERIT_PARENT_AFFINITY = 0x00010000,
INHERIT_CALLER_PRIORITY = 0x00020000,
CREATE_PROTECTED_PROCESS = 0x00040000,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
PROCESS_MODE_BACKGROUND_END = 0x00200000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NO_WINDOW = 0x08000000,
PROFILE_USER = 0x10000000,
PROFILE_KERNEL = 0x20000000,
PROFILE_SERVER = 0x40000000,
CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
public class Kernel32
{
[DllImport("kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
}
public class WtsApi32
{
[DllImport("Wtsapi32.dll", EntryPoint = "WTSQueryUserToken")]
public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr phToken);
}
public class AdvApi32
{
public const uint MAXIMUM_ALLOWED = 0x2000000;
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx
(
IntPtr hExistingToken,
uint dwDesiredAccess,
SECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
out IntPtr phNewToken
);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcessAsUser
(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
SECURITY_ATTRIBUTES lpProcessAttributes,
SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
CREATE_PROCESS_FLAGS dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
}
public class UserEnv
{
[DllImport("userenv.dll", SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
}
public static void StartAsActiveUser(this Process process)
{
// Sanity check.
if (process.StartInfo == null)
{
throw new InvalidOperationException("The StartInfo property must be defined");
}
if (string.IsNullOrEmpty(process.StartInfo.FileName))
{
throw new InvalidOperationException("The StartInfo.FileName property must be defined");
}
// Retrieve the active session ID and its related user token.
var sessionId = Kernel32.WTSGetActiveConsoleSessionId();
var userTokenPtr = new IntPtr();
if (!WtsApi32.WTSQueryUserToken(sessionId, out userTokenPtr))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Duplicate the user token so that it can be used to create a process.
var duplicateUserTokenPtr = new IntPtr();
if (!AdvApi32.DuplicateTokenEx(userTokenPtr, AdvApi32.MAXIMUM_ALLOWED, null, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out duplicateUserTokenPtr))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Create an environment block for the interactive process.
var environmentPtr = new IntPtr();
if (!UserEnv.CreateEnvironmentBlock(out environmentPtr, duplicateUserTokenPtr, false))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Create the process under the target user’s context.
var processFlags = CREATE_PROCESS_FLAGS.NORMAL_PRIORITY_CLASS | CREATE_PROCESS_FLAGS.CREATE_NEW_CONSOLE | CREATE_PROCESS_FLAGS.CREATE_UNICODE_ENVIRONMENT;
var processInfo = new PROCESS_INFORMATION();
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
if (!AdvApi32.CreateProcessAsUser
(
duplicateUserTokenPtr,
process.StartInfo.FileName,
process.StartInfo.Arguments,
null,
null,
false,
processFlags,
environmentPtr,
null,
ref startupInfo,
out processInfo
))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// Free used resources.
Kernel32.CloseHandle(processInfo.hProcess);
Kernel32.CloseHandle(processInfo.hThread);
if (userTokenPtr != null)
{
Kernel32.CloseHandle(userTokenPtr);
}
if (duplicateUserTokenPtr != null)
{
Kernel32.CloseHandle(duplicateUserTokenPtr);
}
if (environmentPtr != null)
{
UserEnv.DestroyEnvironmentBlock(environmentPtr);
}
}
}
Y así es como se invoca el código:
var process = new Process();
process.StartInfo = new ProcessStartInfo { FileName = @"C:/path-to/target.exe", Arguments = "-arg1 -arg2" };
process.StartAsActiveUser();
¡Espero eso ayude!