首页 > 系统相关 >控制台+Topshelf实现Windows服务,以及打包

控制台+Topshelf实现Windows服务,以及打包

时间:2024-05-21 11:43:21浏览次数:25  
标签:begin end Windows void TerryService Topshelf var 服务 控制台

场景:实现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

相关文章

  • windows cmd拉取linux文件夹下的文件,并解压
    前言:nginx静态文件从linux文件夹下拉取,然后放到windows下,并且解压 需要安装putty,用pscp命令del-pull.bat文件,负责删除本地文件夹下所有文件,并且拉取数据@echooffsetlocalsetFOLDER_PATH=C:\Users\admin\Desktop\yaya_nginx\web\echoDeletingfilesinfolder...rm......
  • 突破边界:基于Windows 11的高效渗透测试系统构建
    在这篇文章中,我将向大家推荐一款基于Windows11的渗透测试系统,由一位行业内大佬封装而成。这个名为Windows11PenetrationSuiteToolkit的项目旨在提供一个开箱即用的Windows渗透测试环境,方便安全专家和爱好者进行渗透测试工作。项目地址你可以在GitHub上找到该项目:W......
  • CLON + QT + CMAKE debug 不打印 (控制台不输出)
    原因:CMAKE设置了WIN32_EXECUTABLETRUE这表示了要生成一个GUI程序,而且WindowsGUI会禁用控制台输出将set_target_properties(aaPROPERTIESWIN32_EXECUTABLETRUE)修改为set_target_properties(aaPROPERTIESWIN32_EXECUTABLEFALSE)或者手动创建控制台:`//未......
  • zookeeper控制台
    最近在使用ElasticJob的时候遇到了elasticjobconflictjobs的问题,就想着能不能把这个job从zk中剔除。ElasticJob的注册中心是zk,就想通过控制台把那个定时任务给移除掉。zk控制台下载(https://github.com/DeemOpen/zkui.git),用idea打开修改下config.cfg文件中zk服务的地址,端......
  • Mysql数据库安装卸载(windows)
    MySQL数据库环境准备MySQL下载、安装、配置、卸载、安装DBMS、使用DBMSMySQL版本及下载MySQL是Oracle的免费的关系型数据库,官网https://www.mysql.com/MySQL8.x新特性性能比5.7快支持NoSQL存储:5.7版本开始提供对NoSQL的支持,8.0.x作了改进窗口函数(新的查询方式)索引......
  • FFMPEG windows版本编译
    安装MSYS2:更新MSYS2系统:pacman-Syu打开正确的终端:?如果您要编译64位版本的FFmpeg)或MSYS2MinGW32-bit(如果您需要编译32位版本)。安装所需组件:?在打开的MinGW-w64终端中,安装编译FFmpeg所需的工具链、开发工具和依赖库:pacman-Sbase-develgitmingw-w64-x86_64-toolchainm......
  • 12代处理器在虚拟机中安装Windows98SE
    最近想把以前写的那个Windows98开始菜单完善一下,装个Windows98来参考参考。项目地址:https://github.com/zhaotianff/WindowsX.git路过的小伙伴可以帮忙点个star。  这里把安装过程分享一下。本文以VMware17虚拟机为例,介绍如何在12代处理器中安装Windows98SE。安装步骤......
  • Nexpose v6.6.252 for Linux & Windows - 漏洞扫描
    Nexposev6.6.252forLinux&Windows-漏洞扫描Rapid7VulnerabilityManagement,releaseMay15,2024请访问原文链接:https://sysin.org/blog/nexpose-6/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org您的本地漏洞扫描程序搜集通过实时覆盖整个网络,随......
  • Windows搭建ntp服务器
    1、启用WindowsTime服务:打开“服务”管理工具(可以通过搜索栏输入services.msc)。找到“WindowsTime”服务,确保它已启动并设置为“自动”启动类型。2、配置注册表:使用管理员权限打开“注册表编辑器”(regedit)。导航到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3......
  • Windows包管理工具chocolatey安装
    Windows软件管理工具chocolatey安装背景:chocolatey作为windows软件管理工具下载JDK等工具,可以避免下载工具,修改环境变量配置,操作方便安装步骤官方指导个人操作以管理员身份运行Poweshell按照官网指导调整执行策略PSC:\WINDOWS\system32>Get-ExecutionPolicyRestri......