2012-06-21 12 views
20

Ho bisogno di un modo per monitorare un'applicazione desktop e riavviarla se muore.Qual è il modo migliore per guardare un'applicazione desktop?

Inizialmente ho assunto il modo migliore sarebbe quello di monitorare/riavviare il processo da un servizio di Windows, fino a quando ho scoperto che da quando Vista Windows services should not interact with the desktop

Ho visto diverse questioni che si occupano di questo problema, ma ogni risposta che Ho visto coinvolti alcuni tipi di hack che sono scoraggiati da Microsoft e che probabilmente smetteranno di funzionare nei futuri aggiornamenti del sistema operativo.

Quindi, un servizio di Windows non è probabilmente più un'opzione. Probabilmente potrei semplicemente creare un'applicazione desktop/console diversa per fare questo, ma questo tipo di sconfigge il suo scopo.

Quale sarebbe il modo più elegante per raggiungere questo risultato, secondo te?

MODIFICA: Questo non è né malware né virus. L'app che deve essere monitorata è un lettore multimediale che verrà eseguito su un sistema integrato e, anche se sto cercando di coprire tutti i possibili scenari di arresto, non posso correre il rischio che si verifichi un arresto anomalo di un errore imprevisto (s ** t succede). Questo cane da guardia sarebbe solo una salvaguardia nel caso in cui tutto il resto andasse storto. Inoltre, poiché il player mostra contenuti Flash di terze parti, un ulteriore vantaggio potrebbe essere ad esempio monitorare l'utilizzo delle risorse e riavviare il lettore se, ad esempio, alcuni film di flash scadenti iniziano a perdere memoria.

EDIT 2: Ho dimenticato di menzionare, l'applicazione che vorrei monitorare/riavviare ha assolutamente non è necessario eseguire su account LocalSystem né con alcun privilegio amministrativo. In realtà, io preferisco eseguire il utilizzando le credenziali dell'utente attualmente registrate.

+9

Perché suona come ** malware ** o ** virus **? – SliverNinja

+1

Monitorare localmente o da remoto? –

+0

Non riesco a pensare ad altro che a un processo che controlla .. – nawfal

risposta

6

Inizialmente ho assunto il modo migliore sarebbe quello di monitorare/riavviare il processo da un servizio di Windows ...

Certo che puoi! L'ho fatto qualche tempo fa. È possibile iniziare ad imparare come guardare questo:

http://msdn.microsoft.com/en-us/windows7trainingcourse_win7session0isolation_topic2#_Toc243675529

e questo:

http://www.codeproject.com/Articles/18367/Launch-your-application-in-Vista-under-the-local-s

In sostanza, è necessario eseguire programmi come SYSTEM, ma con il SessionID dell'utente corrente.

Se ti senti pigro, suppongo che potrebbero esserci dei buoni piccoli servizi che rendono la cosa che stai cercando. Prova a cercare su www.codeproject.com.

+0

Mi dispiace, mi sono collegato ai codici C++ ... ma potresti facilmente trovare i sapori C#. –

+0

Cool, sto leggendo quei link in questo momento. Grazie! –

+0

Funziona! Grazie mille per la tua risposta! = D –

4

Il processo di watchdog può utilizzare System.Diagnostics.Process per avviare l'applicazione, utilizzare WaitForExitMethod() e verificare la proprietà ExitCode.

In risposta ai reclami sulla domanda, ho dovuto utilizzare tale metodo quando si lavora con un'applicazione legacy di call center su cui non avevo accesso al controllo del codice sorgente.

EDIT:

Per l'applicazione host è possibile utilizzare un'applicazione .NET di tipo di uscita "Applicazione Windows" e semplicemente non hanno una forma a tutti. Per esempio:

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!"); 
     } 
    } 
} 
+0

Questo è più o meno esattamente quello che ho provato, ma l'applicazione che viene monitorata ha una GUI, quindi questo metodo non funzionerà da Vista e fino. –

+0

@AxelMagagnini - Perché non funziona. Non ha suggerito di farlo in un servizio di Windows. –

+0

@ChrisDunaway ho pensato che volesse dire questo, scusa se ho frainteso. E se sì, da dove lo faresti allora? –

13

Ho finalmente implementato la soluzione suggerita da @ A_nto2 e ha raggiunto esattamente quello che stavo cercando: ora ho un servizio di Windows che monitora un elenco di processi e ogni volta che sono inattivo, vengono riavviati automaticamente utilizzando il credenziali dell'utente e sessione, quindi la GUI è visibile.

Tuttavia, dal momento che i link che ha postato mostrato codice di VC++, sto condividendo la mia C# implementazione per tutti coloro che si occupano dello stesso 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); 
     } 
    } 
} 

Ed ecco come il codice viene richiamato:

var process = new Process(); 
process.StartInfo = new ProcessStartInfo { FileName = @"C:\path-to\target.exe", Arguments = "-arg1 -arg2" }; 
process.StartAsActiveUser(); 

Spero che aiuti!

+2

Per chiunque usi questo codice in futuro: funziona molto bene, ma il servizio DEVE essere eseguito come LocalSystem. –

+0

Quale versione di Windows è supportata? –

+0

Dovrebbe funzionare bene con Vista e 7. Non ne ho testato altri. Se stai utilizzando un sistema operativo precedente (XP per esempio) non dovresti aver bisogno di questo codice, perché in quel momento i Servizi Windows erano in grado di mostrare una GUI. –