C# Windows service, Setup Prozess nach Setup Prozess starten.

xlShortylx

Cadet 4th Year
Registriert
Sep. 2012
Beiträge
103
Hallo.

Ich habe einen Windows Service programmiert, der als Software-Updater laufen soll.
Dieser Windows Service öffnet, nachdem die verfügbaren Updates heruntergeladen und geprüft wurden, ein Dialogfenster. Da der Window Service nicht direkt auf den Userdesktop zugreifen kann und somit das Dialogfenster auf dem Desktop 0 startet, habe ich folgendes im Internet gefunden gehabt:

Der Aufruf:

Code:
ApplicationLoader.PROCESS_INFORMATION procInfo;
ApplicationLoader.StartProcessAndBypassUAC(String.Format("{0} {1}", applicationName, md.InstallCommand), out procInfo);

Die Klasse:

Code:
public class ApplicationLoader
    {
        #region Structures

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public int Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public int cb;
            public String lpReserved;
            public String lpDesktop;
            public String lpTitle;
            public uint dwX;
            public uint dwY;
            public uint dwXSize;
            public uint dwYSize;
            public uint dwXCountChars;
            public uint dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public short wShowWindow;
            public short 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 uint dwProcessId;
            public uint dwThreadId;
        }

        #endregion

        #region Enumerations

        enum TOKEN_TYPE : int
        {
            TokenPrimary = 1,
            TokenImpersonation = 2
        }

        enum SECURITY_IMPERSONATION_LEVEL : int
        {
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3,
        }

        #endregion

        #region Constants

        public const int TOKEN_DUPLICATE = 0x0002;
        public const uint MAXIMUM_ALLOWED = 0x2000000;
        public const int CREATE_NEW_CONSOLE = 0x00000010;

        public const int IDLE_PRIORITY_CLASS = 0x40;
        public const int NORMAL_PRIORITY_CLASS = 0x20;
        public const int HIGH_PRIORITY_CLASS = 0x80;
        public const int REALTIME_PRIORITY_CLASS = 0x100;

        #endregion

        #region Win32 API Imports

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr hSnapshot);

        [DllImport("kernel32.dll")]
        static extern uint WTSGetActiveConsoleSessionId();

        [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
        public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
            String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("kernel32.dll")]
        static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
        public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
            ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
            int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

        [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
        static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);

        #endregion

        /// <summary>
        /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
        /// </summary>
        /// <param name="applicationName">The name of the application to launch</param>
        /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
        /// <returns></returns>
        public static bool StartProcessAndBypassUAC(String applicationName, out PROCESS_INFORMATION procInfo)
        {
            uint winlogonPid = 0;
            IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;            
            procInfo = new PROCESS_INFORMATION();

            // obtain the currently active session id; every logged on user in the system has a unique session id
            uint dwSessionId = WTSGetActiveConsoleSessionId();

            // obtain the process id of the winlogon process that is running within the currently active session
            Process[] processes = Process.GetProcessesByName("winlogon");
            foreach (Process p in processes)
            {
                if ((uint)p.SessionId == dwSessionId)
                {
                    winlogonPid = (uint)p.Id;
                }
            }

            // obtain a handle to the winlogon process
            hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

            // obtain a handle to the access token of the winlogon process
            if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
            {
                CloseHandle(hProcess);
                return false;
            }

            // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
            // I would prefer to not have to use a security attribute variable and to just 
            // simply pass null and inherit (by default) the security attributes
            // of the existing token. However, in C# structures are value types and therefore
            // cannot be assigned the null value.
            SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);

            // copy the access token of the winlogon process; the newly created token will be a primary token
            if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
            {
                CloseHandle(hProcess);
                CloseHandle(hPToken);
                return false;
            }

            // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
            // the window station has a desktop that is invisible and the process is incapable of receiving
            // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
            // interaction with the new process.
            STARTUPINFO si = new STARTUPINFO();
            si.cb = (int)Marshal.SizeOf(si);
            si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop

            // flags that specify the priority and creation method of the process
            int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

            // create a new process in the current user's logon session
            bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
                                            null,                   // file to execute
                                            applicationName,        // command line
                                            ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                            ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                            false,                  // handles are not inheritable
                                            dwCreationFlags,        // creation flags
                                            IntPtr.Zero,            // pointer to new environment block 
                                            null,                   // name of current directory 
                                            ref si,                 // pointer to STARTUPINFO structure
                                            out procInfo            // receives information about new process
                                            );

            // invalidate the handles
            CloseHandle(hProcess);
            CloseHandle(hPToken);
            CloseHandle(hUserTokenDup);

            return result; // return the result
        }
    }


Nun komme ich zu meinem Problem.
So wie der Updateprozess jetzt funktioniert, führt er beim Bestätigen des Dialogs alle Updates (Setups) nacheinander aus und schließt den Dialog.
Nur ich möchte, dass jedes Update nacheinander ausgeführt wird sobald das vorige Beendet wurde. (falls jemand eine bessere Idee hat wäre ich auch dankbar).

Da ich mit dieser Klasse aber keine Möglichkeit habe, einen Exited Eventhandler hinzuzufügen, weiß ich nicht mehr weiter.

Wäre echt dankbar über einen schnellen Lösungsansatz.
Danke im Voraus.
 
Du bekommst doch ne PROCESS_INFORMATION structure beim Aufruf von StartProcessAndBypassUAC().
Darin steht die ID des gestarteten Prozess.

Per Process.GetProcessById() bekommst du ne Instanz der Process-Klasse die dir die Methode WaitForExit() bietet, mit der du auf Beendigung des Prozesses warten kannst.
 
Einen Dienst eine GUI anzeigen zu lassen ist eine furchtbar schlechte Idee und es hat einen Grund, warum die GUI nicht in den normalen Desktop eingeblendet wird.
Löse das Problem unbedingt auf eine andere Weise. Bau dir einen GUI-Prozess mit niedrigeren Rechten (z.B. Administrator), der sich Daten von deinem Dienst holt.

Oder was auch immer. Aber LASS EINEN DIENST KEINE GUI EINBLENDEN!

https://blogs.msdn.microsoft.com/larryosterman/2005/09/14/interacting-with-services/

€: Moment. Dein Dienst soll nur Softwareupdates machen? Hast du völlig den Verstand verloren? Dienste haben die allerhöchstmöglichen Rechte, die man als Prozess
unter Windows überhaupt haben kann. Mit einem winzigen Bug in einem Dienst kann man das komplette System auf machen. Wie bist du bitte auf diese Idee gekommen?

Mach ein ganz normalo Pupsi Programm, das auf Updates prüft und mit Rechten eines lokalen Users (z.B. Administrator) ausgeführt wird. Da kann wenigstens nichts schlimmes bei kaputt gehen.
 
Zuletzt bearbeitet:
Danke für die Antworte und danke für die Kritik.

Ich habe das Projekt übernommen und kann nicht sagen wieso es so gemacht wurde. Eventuell aus Unwissenheit, deshalb sollte man sich eigentlich vorher gedanken machen.

Aber eine Frage habe ich. Reden wir von dem gleichen? Wenn ich im Internet nach Möglichkeiten für einen Update Dienst suche, wird mir vorgeschlagen einen Window Service zu schreiben.
 
Zuletzt bearbeitet:
Wieso willst du denn einen Dienst dafür haben?

Soll das ein Software-Updater von einem, oder mehreren Tools werden?
 
Es soll ein Software-Update für beliebig viele Softwareprodukte sein. Außerdem soll der nicht nur Updates laden, sondern auch Prüfungen durchführen (Verbindung mit WCF-Dienst auf einem unserer Server) und das alles selbstständig. Klar ist so etwas immer leicht Fehleranfällig bzw. Sicherheitstechnisch als riskant einzustufen.
 

Ähnliche Themen

Zurück
Oben