【转】Windows服务如何启动带UI界面的外部程序及启动服务时传递命令行参数 您所在的位置:网站首页 服务进程怎么打开 【转】Windows服务如何启动带UI界面的外部程序及启动服务时传递命令行参数

【转】Windows服务如何启动带UI界面的外部程序及启动服务时传递命令行参数

2024-01-15 01:08| 来源: 网络整理| 查看: 265

【目标】定时检测特定应用是否运行,如没运行则启动它:

方法1:创建Windows任务计划程序(外部程序实例已运行时则忽略):

最后一步就是指定运行程序(起始路径设置为外部程序的所在路径很重要,否则可能无法因找不到程序无法启动):

方法2:创建Windows服务来启动外部程序(外部程序编写需要有自己判断实例是否已存在,存在则退出,保持单实例):

但是通常应用程序的带UI界面的,遗憾的是Windows服务是不支持运行UI界面程序的,譬如下面方式运行带UI界面的应用则发现打不开

//打开程序的地址 var fileName = path; //判断文件是否存在,如果不存在,返回 false Console.WriteLine(File.Exists(fileName)); ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = fileName; Process process = new Process(); process.StartInfo = startInfo; process.Start();

这里就需要用到“绕过UAC以最高权限”来启动:

自Vista操作系统之后,微软考虑到安全因素,在系统管理员账户和标准用户之间创出了UAC(用户账户控制)。当标准用户启动需管理员权限的操作时要弹框让用户确认,这样可防止恶意软件或间谍软件随意修改系统造成破坏。

但对于必须要通过最高权限运行交互进程来说就造成问题,微软API接口提供CreateProcessAsUser函数用于在后台服务程序中启动前台进程,但启动时要请求UAC权限(由于后台服务是最高权限启动,其创建的子进程也继承最高权限),这时后台UAC窗口无法显示在前台界面上,造成程序永远等待无法启动。

vista之后,微软会为每个登录用户分配一个会话,后台服务在系统启动时最先启动,分配的会话ID为0,其后每登录一个用户会话ID加1:

问题来了,由于有会话隔离,我们无法在一个会话程序中直接启动另一会话的程序。但微软有一个特殊的进程,对于每个会话会有一个对应的进程,这个进程就是winlogin.exe:

winlogin进程的作用

Winlogon.exe进程是微软公司为其Windows操作系统定义的一个非常重要的系统核心进程,被称为“Windows登陆应用程序”,它会随着系统启动而启动并一直运行。通常情况下此进程应该是安全的,并且只占用很少的CPU及内存资源,如果资源占用过多则很有可能被病毒“劫持”。

请不要尝试将本进程终止(也无法在任务管理器将其终止)或将此进程从系统中删除,这会导致你的系统无法正常运行。因为Winlogon.exe进程为系统提供了有以下4项重要的功能:  1、在登录系统时加载的用户配置文件,以及执行注销用户与锁定计算机;  2、负责处理Ctrl+Alt+Del快捷键(SAS)的功能;  3、监控键盘和鼠标使用情况来决定什么时候启动屏幕保护程序;  4、检验Windows操作系统激活密钥是否为合法许可;

可以发现winlogin进程是后台服务进程,但所属登录用户会话,那是不是可以通过这个进程来达到我们绕过UAC的限制启动前台交互程序呢?没错!!!

有了winlogin进程,我们可以在后台服务中先查询到winlogin进程信息,获取其访问令牌,最后通过CreateProcessAsUser将进程启动到活动登录用户当前活动会话。由于和前台界面所属同一会话,启动后的程序便可以进行交互。下面为有心人写好的代码(ApplicationLoader.cs):

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Security; using System.Diagnostics; using System.Runtime.InteropServices; namespace CheckProcService { /// /// Class that allows running applications with full admin rights. In /// addition the application launched will bypass the Vista UAC prompt. /// 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 /// /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt /// /// The name of the application to launch /// Process information regarding the launched application that gets returned to the caller /// public static bool StartProcessAndBypassUAC(string applicationName, string currentDirectory, 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 currentDirectory, // 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 } } }

然后创建一个服务类(CheckProcRunningService.cs):

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading; using System.Threading.Tasks; namespace CheckProcService { public partial class CheckProcRunningService : ServiceBase { public CheckProcRunningService() { InitializeComponent(); } private const int _MIN_INTERVAL = 60000; protected override void OnStart(string[] args) { CommandLineArguments arguments = new CommandLineArguments(args); ConfigHelper.Arguments = arguments; System.Threading.ThreadStart job = new System.Threading.ThreadStart(StartJob); System.Threading.Thread thread = new System.Threading.Thread(job); thread.Start(); EventLog.WriteEntry(this.ServiceName, string.Format("CheckProcRunningService -period:{0} -procfilename:{1}", this.Period.ToString(), this.ProcFileName), EventLogEntryType.Information); return; } private static int? _period; public int Period { get { if (!_period.HasValue) { int period = _MIN_INTERVAL; //EventLog.WriteEntry(this.ServiceName, string.Format("-period:{0}", ConfigHelper.Arguments.Parameters.ContainsKey("Period")), EventLogEntryType.Warning); if (ConfigHelper.Arguments.Parameters.ContainsKey("Period")) int.TryParse(ConfigHelper.Arguments.Parameters["Period"], out period); _period = period; } return _period.Value; } } private static string _procfilename; public string ProcFileName { get { if (string.IsNullOrWhiteSpace(_procfilename)) { string fn = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "trayicondemo.exe"); if (ConfigHelper.Arguments.Parameters.ContainsKey("ProcFileName")) fn = ConfigHelper.Arguments.Parameters["ProcFileName"]; _procfilename = fn; } return _procfilename; } } protected override void OnStop() { if (_timer != null) _timer.Dispose(); } private Timer _timer; private void StartJob() { try { EventLog.WriteEntry(this.ServiceName, string.Format("Starting on {0} Paramters[Period: {1} ProcFileName:{2}]", DateTime.Now, this.Period.ToString(), this.ProcFileName), EventLogEntryType.Information); _timer = new Timer(new TimerCallback(ExecCheckRunJob)); _timer.Change(0, this.Period); } catch (Exception ex) { EventLog.WriteEntry(this.ServiceName, ex.Message, EventLogEntryType.Warning); } } private void ExecCheckRunJob(object state) { //string errMsg = string.Empty; //ProcessHelper.ExecBatch(fn, false, false, false, "", ref errMsg); // the name of the application to launch; // to launch an application using the full command path simply escape // the path with quotes, for example to launch firefox.exe: // String applicationName = "\"C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe\""; //string fn = "cmd.exe"; // launch the application ApplicationLoader.PROCESS_INFORMATION procInfo; string currentDirectory = Path.GetDirectoryName(this.ProcFileName); ApplicationLoader.StartProcessAndBypassUAC(this.ProcFileName, currentDirectory, out procInfo); } } }

覆写方法OnStart提供了命令行参数来指定检测频率(间隔多少毫秒,应用程序全路径),命令行参数辅助类(CommandLineUtility.cs)代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.Specialized; using System.Text.RegularExpressions; namespace CheckProcService { /// /// 命令行参数类 /// 测试参数如下:-param0 -param1 value1 --param2 value2 /param3 value3 =param4 value4 :param5 value5 -param6=param6 -param7:param7 -param8.1 "1234" -param8.2 "1 2 3 4" -param9.1 '1234' -param9.2='1 2 3 4' /// 各参数项测试内容如下: /// -param0,只有参数项 /// -param1 value1,有参数项,有参数值 /// --param2 value2,用--标记参数项开头 /// /param value3,用/标记参数项开头 /// =param4 value4,用=标记参数项开头 /// :param5 value5,用:标记参数项开头 /// -param6=param6,用=标记参数项与参数值的关系 /// -param7:param7,用:标记参数项与参数值的关系 /// -param8.1 "1234",用""指定参数 /// -param8.2 "1 2 3 4",用""指定参数(含空格) /// -param9.1 '1234',用''指定参数 /// -param9.2='1 2 3 4',用''指定参数(含空格) /// public class CommandLineArguments { // Variables private StringDictionary _parameters; // Constructor public CommandLineArguments(string[] Args) { Parameters = new StringDictionary(); Regex spliter = new Regex(@"^-{1,2}|^/|=|:", RegexOptions.IgnoreCase | RegexOptions.Compiled); Regex remover = new Regex(@"^['""]?(.*?)['""]?$", RegexOptions.IgnoreCase | RegexOptions.Compiled); string parameter = null; string[] parts; // Valid parameters forms: // {-,/,--}param{ ,=,:}((",')value(",')) // Examples: // -param1 value1 --param2 /param3:"Test-:-work" // /param4=happy -param5 '--=nice=--' foreach (string txt in Args) { // Look for new parameters (-,/ or --) and a // possible enclosed value (=,:) parts = spliter.Split(txt, 3); switch (parts.Length) { // Found a value (for the last parameter // found (space separator)) case 1: if (parameter != null) { if (!Parameters.ContainsKey(parameter)) { parts[0] = remover.Replace(parts[0], "$1"); Parameters.Add(parameter, parts[0]); } parameter = null; } // else Error: no parameter waiting for a value (skipped) break; // Found just a parameter case 2: // The last parameter is still waiting. // With no value, set it to true. if (parameter != null) { if (!Parameters.ContainsKey(parameter)) Parameters.Add(parameter, "true"); } parameter = parts[1]; break; // Parameter with enclosed value case 3: // The last parameter is still waiting. // With no value, set it to true. if (parameter != null) { if (!Parameters.ContainsKey(parameter)) Parameters.Add(parameter, "true"); } parameter = parts[1]; // Remove possible enclosing characters (",') if (!Parameters.ContainsKey(parameter)) { parts[2] = remover.Replace(parts[2], "$1"); Parameters.Add(parameter, parts[2]); } parameter = null; break; } } // In case a parameter is still waiting if (parameter != null) { if (!Parameters.ContainsKey(parameter)) Parameters.Add(parameter, "true"); } } // Retrieve a parameter value if it exists // (overriding C# indexer property) public string this[string param] { get { return (Parameters[param]); } } public StringDictionary Parameters { get { return _parameters; } set { _parameters = value; } } } public class ConfigHelper { public static CommandLineArguments Arguments { get; set; } } }

服务安装类(Installer.cs)如下:

using Microsoft.Win32; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using System.Linq; using System.Threading.Tasks; namespace CheckProcService { [RunInstaller(true)] public partial class ServiceInstaller : Installer { public ServiceInstaller() { InitializeComponent(); this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; this.serviceProcessInstaller1.Password = null; this.serviceProcessInstaller1.Username = null; //this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.User; //this.serviceProcessInstaller1.Password = "Bronzepen1o3$"; //this.serviceProcessInstaller1.Username = "[email protected]"; this.serviceInstaller1.ServiceName = "procsvcl"; this.serviceInstaller1.DisplayName = "Check Compiler Manager Running"; this.serviceInstaller1.Description = "Check compiler process running, otherwise run it immediately."; this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; } protected override void OnAfterInstall(IDictionary savedState) { base.OnAfterInstall(savedState); //设置允许服务与桌面交互 RegistryKey rk = Registry.LocalMachine; string key = @"SYSTEM\CurrentControlSet\Services\" + this.serviceInstaller1.ServiceName; RegistryKey sub = rk.OpenSubKey(key, true); int value = (int)sub.GetValue("Type"); sub.SetValue("Type", value | 256); } } }

主程序如下(Program.cs):

using System; using System.Collections.Generic; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace CheckProcService { static class Program { /// /// 应用程序的主入口点。 /// static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new CheckProcRunningService() }; ServiceBase.Run(ServicesToRun); } } }

部署服务代码如下:

net stop procsvcl C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /u "F:\GitHub\PycharmProjects\PyQtTest\dist\CheckProcService.exe" pause C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe "F:\GitHub\PycharmProjects\PyQtTest\dist\CheckProcService.exe" pause sc start procsvcl /period:300000 /procfilename:"F:\GitHub\PycharmProjects\PyQtTest\dist\trayicondemo.exe"

通过测试我们把命令行参数传递进去了:



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有