场景:实现Windows服务,例如TerryService。
1 服务安装脚本 serviceInstall.bat
cd /d %~dp0 echo %date%_%time% >>InstallLog.txt TerryService.exe uninstall >>InstallLog.txt TerryService.exe install >>InstallLog.txt sc config TerryService type= interact type= own >>InstallLog.txt sc failure TerryService reset= 30 actions= restart/5000 >>InstallLog.txt TerryService.exe start >>InstallLog.txt exit
2 服务开启脚本 serviceStart.bat
cd /d %~dp0 TerryService stop >>StartLog.txt TerryService start >>StartLog.txt exit
3 服务停止脚本 serviceStop.bat
cd /d %~dp0 TerryService stop >>StopLog.txt exit
4 服务卸载脚本
cd /d %~dp0 TerryService uninstall >>UninstallLog.txt exit
方案:采用控制台 + Topshelf 实现Windows服务
1 控制台入口启动 TerryService 服务
internal class Program { static void Main(string[] args) { //默认线程内外部统一使用英语的,小数默认都是.号分割,CurrentUICulture不变 CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US"); Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); var serviceLoadResult = RepairAmpStart(); LogServiceStartResult(serviceLoadResult); } /// <summary> /// 开启服务 /// </summary> private static OperateResult TerryServiceStart() { //开启服务失败重试次数 int tryTimes = 10; for (int i = 0; i < tryTimes; i++) { try { var result = ServiceLoader.Load(new ServiceInfo() { Description = "TerryService服务(测试Demo)", DisplayName = "TerryService", ServiceName = "TerryService" }, new WindowService()); if (result.IsSuccess) { return result; } } catch (Exception) { //ignore } //开启服务失败间隔时间 Thread.Sleep(100); } return default; } /// <summary> /// 记录服务开启结果到日志 /// </summary> private static void LogServiceStartResult(OperateResult serviceLoadResult) { var logContent = serviceLoadResult != null ? $"{serviceLoadResult.IsSuccess}_{serviceLoadResult.Message}" : "服务启动失败"; try { var fileName = $"info_start_{DateTime.Now:yyyy_MM_dd}.txt"; var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); var fileFullName = Path.Combine(appDataPath, CustomText.FamilyDataName, CustomText.ProjectName, "Log", fileName); File.AppendAllText(fileFullName, logContent); } catch (Exception) { //ignore } } }
2 服务接口类 IService
/// <summary> /// 服务接口 /// </summary> public interface IService { /// <summary> /// 开始服务 /// </summary> void Start(); /// <summary> /// 停止服务 /// </summary> void Stop(); /// <summary> /// 暂停服务 /// </summary> void Pause(); /// <summary> /// 继续服务 /// </summary> void Continue(); /// <summary> /// 配置宿主信息 /// </summary> /// <param name="hostConfig"></param> void ConfigHost(HostConfigurator hostConfig); /// <summary> /// 配置服务信息 /// </summary> /// <param name="serviceConfig"></param> void ConfigService(ServiceConfigurator<IService> serviceConfig); }
3 服务加载类 ServiceLoader
public class ServiceLoader { public static OperateResult Load(ServiceInfo serviceInfo, IService service) { try { string startException = ""; var code = HostFactory.Run(hostConfig => { service.ConfigHost(hostConfig); hostConfig.SetDescription(serviceInfo.Description); hostConfig.SetDisplayName(serviceInfo.DisplayName); hostConfig.SetServiceName(serviceInfo.ServiceName); hostConfig.EnableSessionChanged(); hostConfig.RunAsLocalSystem(); hostConfig.Service<IService>(serviceConfig => { service.ConfigService(serviceConfig); serviceConfig.ConstructUsing(settings => service); serviceConfig.WhenStarted(tc => { try { tc.Start(); } catch (Exception ex) { startException = ex.ToString(); } }); serviceConfig.WhenStopped(tc => tc.Stop()); serviceConfig.WhenPaused(tc => tc.Pause()); serviceConfig.WhenContinued(tc => tc.Continue()); }); }); string exitInfo = $"服务停止,退出码{code}"; if (!string.IsNullOrEmpty(startException)) { exitInfo = $"{exitInfo},启动异常信息:{startException}"; } return OperateResult.GetSuccessResult(exitInfo); } catch (Exception ex) { return OperateResult.GetFailedResult($"服务加载异常,原因:{ex.Message}"); } }
4 实现IService ,服务入口 WindowService
/// <summary> /// 服务入口 /// </summary> public class WindowService : IService { public WindowService() { } /// <summary> /// 开启服务 /// </summary> public async void Start() { Logger.Info("H3CAmpService服务启动"); } /// <summary> /// 停止服务 /// </summary> public void Stop() { _Logger.Info("H3CAmpService服务停止"); } /// <summary> /// 暂停服务 /// </summary> public void Pause() { _Logger.Info("H3CAmpService服务暂停"); } /// <summary> /// 恢复服务 /// </summary> public void Continue() { _Logger.Info("H3CAmpService服务恢复"); } public void ConfigHost(HostConfigurator hostConfig) { hostConfig.EnableShutdown(); } public void ConfigService(ServiceConfigurator<IService> serviceConfig) { serviceConfig.WhenShutdown(( service,control) => _Logger.Info("=======>>系统即将关闭.....")); } }
5 完成上述即可实现简单的服务了。再结合Inno Setup 打包成安装程序,编译安装和卸载。安装服务前先检测 停止服务,再卸载,最后安装
实现QuickBuild.iss 脚本如下
#define MyAppName "TerryService" #define MyAppChineseName "TerryService" #define MyAppVersion "1.0.0" #define MyAppPublisher MyAppName #define MyAppCopyright "TerryService" #define MyAppURL "https://www.h3c.com/" #define MyAppExeName MyAppName+".exe" #define RootPath ".." #define CurrentDateTimeString GetDateTimeString('mmdd', '', ''); #define ServiceDirectory "TerryService" [Setup] AppId={{AA167630-A054-41B7-82BC-945C75846388}} AppName={#MyAppChineseName} AppVersion={#MyAppVersion} AppVerName={#MyAppChineseName} {#MyAppVersion} AppCopyright={#MyAppCopyright} VersionInfoVersion={#MyAppVersion} VersionInfoCompany={#MyAppPublisher} VersionInfoTextVersion={#MyAppVersion} VersionInfoCopyright={#MyAppCopyright} AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={commonpf}\{#ServiceDirectory} DefaultGroupName={#MyAppChineseName} OutputDir=..\JenkinsBuild\outputFile OutputBaseFilename={#MyAppChineseName}_V{#MyAppVersion}.{#CurrentDateTimeString} Compression=lzma SolidCompression=yes [Dirs] ;修改文件访问权限 Name: "{app}";Permissions: everyone-full; [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Files] ; app 安装根目录 Source: "{#RootPath}\Applications\*"; Excludes: ".pdb";DestDir: "{app}\{#ServiceDirectory}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#RootPath}\JenkinsBuild\Inno Setup 6\psvince.dll"; Excludes: ".pdb,Languages";DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [UninstallRun] Filename: "{app}\{#ServiceDirectory}\serviceUninstall.bat"; StatusMsg: "Uninstalling AmpService"; Flags: runhidden [UninstallDelete] Type: filesandordirs; Name: "{app}\{#ServiceDirectory}" [Code] //检测进程存在使用 function IsModuleLoaded(modulename: AnsiString ): Boolean; external 'IsModuleLoaded@{app}/psvince.dll stdcall delayload uninstallonly'; // PSVince控件在64位系统(Windows 7/Server 2008/Server 2012)下无法检测到进程,使用下面的函数可以解决。 function IsAppRunning(const FileName : string): Boolean; var FSWbemLocator: Variant; FWMIService : Variant; FWbemObjectSet: Variant; begin Result := false; try FSWbemLocator := CreateOleObject('WBEMScripting.SWBEMLocator'); FWMIService := FSWbemLocator.ConnectServer('', 'root\CIMV2', '', ''); FWbemObjectSet := FWMIService.ExecQuery(Format('SELECT Name FROM Win32_Process Where Name="%s"',[FileName])); Result := (FWbemObjectSet.Count > 0); FWbemObjectSet := Unassigned; FWMIService := Unassigned; FSWbemLocator := Unassigned; except if (IsModuleLoaded(FileName)) then begin Result := false; end else begin Result := true; end end; end; //判断是否有应用正在运行 function HasProcessRunning(processList: TStringList): Boolean; var i: integer; begin for i := 0 to processList.Count - 1 do if(IsAppRunning(processList[i])) then begin Result := true; break; end end; // 关闭进程 function TaskKillProcessByName(const FileName : string): Boolean; var ResultCode: Integer; begin Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + FileName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; //关闭所有正在运行的应用 procedure KillAllProcesses(processList: TStringList); var i: integer; begin for i := 0 to processList.Count - 1 do if(IsAppRunning(processList[i])) then TaskKillProcessByName(processList[i]); end; //安装服务 procedure InstallAndStartService(); var batPath:string; ResultCode: Integer; begin batPath := ExpandConstant('{app}\{#ServiceDirectory}\serviceInstall.bat'); Exec(batPath, '/s', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; // 2 procedure CurStepChanged(CurStep: TSetupStep); begin InstallAndStartService(); end; //需要关闭的进程 function GetRunnableProcessName(): TStringList; var appExeNameList: TStringList; begin appExeNameList:=TStringList.Create; appExeNameList.Add('TerryService'); Result := appExeNameList; end; //调用系统net工具停止服务 procedure StopAllServices(); var ResultCode: Integer; netPath: string; begin netPath := ExpandConstant ('{sys}\net.exe'); Exec(netPath, 'stop TerryService', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; //卸载前先关闭所有正在运行的应用 function KillRunningProcessBeforeInstall(): Boolean; var appExeNameList: TStringList; batPath:string; ResultCode: Integer; sInstallPath: String; hasInstalled: Boolean; begin Result := true; try appExeNameList := GetRunnableProcessName(); if(HasProcessRunning(appExeNameList)) then begin if MsgBox('卸载程序检测到扬声器修复服务正在运行!'#13''#13'单击“是”按钮关闭程序并继续卸载;'#13'单击“否”按钮退出卸载!', mbConfirmation, MB_YESNO) = IDYES then begin StopAllServices(); KillAllProcesses(appExeNameList); end else Result := false; end; finally appExeNameList.Free; end; end; // 1 安装的时候判断进程是否存在,存在则先提示是否结束进程 function InitializeSetup(): Boolean; begin Result:= KillRunningProcessBeforeInstall(); end; procedure ExitProcess(uExitCode: UINT); external '[email protected] stdcall'; function IsEnoughFreeSpace(const Path: string; MinSpace: Cardinal): Boolean; var FreeSpace, TotalSpace: Cardinal; begin // the second parameter set to True means that the function operates with // megabyte units; if you set it to False, it will operate with bytes; by // the chosen units you must reflect the value of the MinSpace paremeter if GetSpaceOnDisk(Path, True, FreeSpace, TotalSpace) then Result := FreeSpace >= MinSpace else RaiseException('Failed to check free space.'); end; function NextButtonClick(CurPageID: Integer): Boolean; begin Result := True; if CurPageID = wpSelectDir then begin // the second parameter in this function call is the expected min. space in // units specified by the commented parameter above; in this example we are // checking if there's at least 1 MB of free space on drive of the selected // directory; we need to extract a drive portion of the selected directory, // because it's probable that the directory won't exist yet when we check if not IsEnoughFreeSpace(ExtractFileDrive(WizardDirValue), 1) then begin MsgBox('There is not enough space on drive of the selected directory. ' + 'Setup will now exit.', mbCriticalError, MB_OK); // in this input parameter you can pass your own exit code which can have // some meaningful value indicating that the setup process exited because // of the not enough space reason ExitProcess(666); end; end; end; [CustomMessages] DependenciesDir=MyProgramDependencies WindowsServicePack=Windows %1 Service Pack %2
标签:begin,end,Windows,void,TerryService,Topshelf,var,服务,控制台 From: https://www.cnblogs.com/terryK/p/18203614