SpringBoot jar包如何制作windows客户端安装程序

背景

Web端打印功能需求,一开始使用Lodop插件同用户本地的打印机做交互,它的功能很强大也很齐全。但还是有很多需求无法覆盖到,比如文字旋转(转图片方式旋转有清晰度问题)、标价签之类的精确打印、打印性能等方面无法满足或者有缺陷。因此需要自行开发一个客户端打印插件服务,方案很简单,服务端生成打印结果的pdf文件,客户端打印插件服务下载pdf文件并调用用户本地打印机进行打印。打印插件服务使用Spring Boot开发并提供获取本地打印机列表、调用打印机等接口服务,由此引申出本文,如何让用户便捷安装该插件服务,并提供用户电脑开机自启动功能。

开发环境说明

操作系统:windows 10
Java开发环境:32位jdk1.8(为了Spring Boot程序能兼容XP)
Python开发环境:Python3.6.5+wxPython4.0.6+PyInstaller3.5
C语言开发环境:VC++6.0(为了制作启动程序exe能够兼容XP)
安装程序制作工具:Inno Setup 5.6.1 -unicode(6.0版本不支持XP,必须下载Unicode版本才能在安装界面支持中文)
MT.exe:为启动程序.exe注入管理员许可证权限
exescope.exe:查看exe程序支持的windows操作系统版本。

解决方案

方案一

实现过程

1、 使用 winsw.exe 将打印插件服务的jar包做成windows服务,并设置为自启动。

  • winsw.exe 2.3 下载地址

  • 新建和exe同名的xml配置文件winsw.xml放到同一目录下,配置说明>>

    <service>
        <id>GemBox</id>
        <name>GemBox</name>
        <description>GemBox打印插件服务</description>
        <executable>.\jre\bin\javaw.exe</executable>
        <!--服务启动方式:Automatic-自动,Manual-手动-->
        <startmode>Automatic</startmode>
        <arguments>-jar ".\lib\gem-box-service.jar -" "-spring.config.location=classpath:/application.yml,./conf/application.properties"</arguments>
        <logmode>reset</logmode>
    </service>
    
  • 打开当前目录执行以下dos命令

    # 安装服务
    > winsw.exe install
    # 启动服务(虽然是自启动模式,但安装后并不会自启动)
    > net start GemBox
    # 卸载服务
    > winsw.exe uninstall
    

存在缺陷

  1. Web服务虽然正常启动,但是由于该jar需要同用户本地打印机交互,win10系统下调用打印相关接口无反应,经排查发现是用户权限问题。制作的自启动的windows服务默认是以system用户启动,然而它拿不到同打印机交互的权限,必须以管理员用户模式启动打印插件服务。(虽然,winsw.exe也可以安装为指定用户的自启动服务,但得配置好用户名和密码,无法给不同用户使用)。

  2. 能否兼容其他操作系统:XP/win7/vista 未知,没验证过。

方案二

实现过程

为解决以本地用户账户启动插件服务问题,通过观察日常使用的软件程序发现有些软件会在windows的任务计划程序中自动创建一个定时任务,通过定时任务来触发程序的启动。立马开洞脑筋,搜一下如何使用dos命令创建任务计划—> schtasks.exe。

  1. 使用bat脚本,提供安装、卸载、启动、停止服务脚本。

    • 安装脚本 install.bat

      @echo off
      ::进入脚本所在目录
      cd /D %~dp0
      ::schtasks /Create /tn GemBox /tr %cd%\gembox.bat /sc ONLOGON /RL HIGHEST
      ::schtasks  /Delete  /tn  testschtask
      ::先删除,再新建任务计划。
      schtasks /Delete /tn GemBox /F
      ::XP系统不支持 /RL 参数,且程序目录需要双引号
      ::用户登录后执行指定的bat脚本,%cd%指向执行脚本的目录。
      schtasks /Create /tn GemBox /tr "%cd%\gembox.bat" /sc ONLOGON
      @echo 'install successfully!'
      pause
      
      • 启动脚本 gembox.bat
      @echo off
      ::进入脚本所在目录
      cd /D %~dp0
      ::此处把jre/bin中的javaw.exe重命名为GemBox.exe,为了方便后面按名称杀进程
      start .\jre\bin\GemBox -jar .\lib\gem-box-service.jar --spring.config.location=classpath:/application.yml,./conf/application.properties
      exit
      
    • 停止脚本 stop.bat

      @echo off
      ::强制杀掉指定程序
      taskkill /im GemBox.exe /f
      exit
      
    • 卸载脚本 uninstall.bat

      @echo off
      cd /D %~dp0
      taskkill /im GemBox.exe /f
      schtasks /Delete /tn GemBox /F
      @echo 'uninstall successfully!'
      pause
      
    1. 执行脚本时,必须以管理员模式运行,否则任务计划创建、删除等无效。

存在缺陷

  1. win7及以上操作系统,需要以管理员模式执行脚本,可能有方案可以让bat脚本默认以管理员模式运行,暂时没去研究。
  2. 需要用户自己执行bat脚本,加大了沟通成本,而且看起来很low。
  3. 安装创建的任务计划程序,用户登录时会有dos窗口闪过。暂时没找到好方法隐藏。

方案三

实现过程

  1. 使用Python 3.6.5+wxPython编写安装、启动、卸载用的exe程序。

    • 通过Python代码调用方案二中的相关dos命令来实现安装、启动程序。

    • 使用PyInstaller将Python脚本编译为exe程序。

    • 为了复用,将相关dos命令配置成cmd.json文件,只需一个对应的Python脚本即可创建对应的安装、启动、卸载程序。具体代码不在此处详述。

      • 难点1:win7/win10需要以管理员模式运行,通过ctypes库来处理。

      • 难点2:使用subprocess.run来执行dos命令,达到隐藏dos窗口效果。

      • 难点3:取得exe启动程序所在的目录传给dos命令,相关jar包配置文件路径需要指定绝对路径,否则任务计划的自启动程序触发时默认指向的是C:\windows\system32等错误目录,使用os和sys库

         # 获取exe执行程序所在目录
          os.path.dirname(os.path.realpath(sys.argv[0]))
        
  2. 使用Inno Setup包装制品

    • 配置使用说明详见>>

    • 5.6.1-unicode下载地址

    • 5.6.1 中文语言包下载地址,下载后确保文件格式为ANSI编码格式,否则编译报错,同时拷贝文件到Inno Setup安装目录/Languages下,这样创建模板时即可在语言支持列中看到简体中文。

    • 打包后的安装程序在XP下执行报错,6.X 版本以后不再支持XP及以下版本操作系统,最低支持Vista系统(通过exescope.exe即可看到编译出来的setup.exe的操作系统主版本为6副版本为0),没奈何只好装回5.X版本。

    • 5.X 版本部分变量同6.X不一样,比如执行安装程序时的默认安装目录5.X变量定义为{pf},6.X为{autopf}具体详见各个版本的变量说明。

存在缺陷

  1. PyInstaller编译出来的exe制品太大,10多兆,哪怕内部只是调用了几行dos命令。(加载一些额外的辅助库占用了较大空间,比如wxPython)
  2. 没使用Inno Setup打包前,直接放到XP中启动程序提示不是有效的win32应用程序,经Google发现我本地装的PyInstaller 3.X 版本不支持XP,只能降低版本,降低版本后发现没法使用,必须基于32位的Python库,我本地装的是64位,实在没功夫重头折腾一遍开发环境,毕竟Python也只是很早以前玩一玩遗留下来的。

方案四(推荐)

实现过程

  1. 基于方案三,将Python实现exe启动程序改为使用C语言实现,创建一个windows Application工程。

    • 难点1:从Hello World开始重新复习。

    • 难点2:如何获取exe执行程序所在目录,赋值给dos命令。

       //获取exe执行程序所在目录
      TCHAR _szPath[MAX_PATH + 1]={0};
      GetModuleFileName(NULL, _szPath, MAX_PATH);
      (strrchr(_szPath, '\\'))[1] = 0;//删除文件名,只获得路径 字串
      
    • 难点3:如何隐藏dos命令窗口。

       #include <stdio.h>
       #include <stdlib.h>
       #include <windows.h>
       //隐藏dos命令窗口相关代码
       #pragma comment(linker, "/subsystem:windows/entry:mainCRTStartup")
       #include <iostream>
       using namespace std;
             ...
      int main(){
          //隐藏dos命令窗口相关代码
          //最优解,注意ShellExecute是异步的,如果要做成同步挺麻烦的,有鉴于C语言功力有限,先简单来。去掉了先杀插件服务进程的逻辑,否则会误杀刚启动的进程。
          ShellExecute(0, "open", "cmd.exe",cmdStr.c_str(), 0, SW_HIDE);
         
         //废弃方案:如果执行失败会一直重试,而且dos命令窗口一直在。
         //WinExec("cmd /c taskkill /im GemBox.exe /f >start.txt",SW_HIDE);
         //WinExec("start .\\jre\\bin\\GemBox -jar .\\lib\\gem-box-service.jar --spring.config.location=classpath:/application.yml,./conf/application.properties",SW_HIDE);
         
         //废弃方案:虽然是同步运行的,但是dos命令窗口无法隐藏,有人说使用start /B 参数,试了没效果。
         //system("taskkill /im GemBox.exe /f");
         //system("start .\\jre\\bin\\GemBox -jar \".\\lib\\gem-box-service.jar\" \"--spring.config.location=classpath:/application.yml,./conf/application.properties\"");
         return 0;
         }
      
      • 难点4:如何给exe程序添加图标,通过给C代码源工程添加XX.rc文件导入ico格式图标,重新编译即可。(一搜一大把,此处不列出具体操作步骤)
  2. 为在win7/win10/vista中以管理员运行gembox.exe启动程序,利用MT.exe注入manifest信息。

    • mt.exe下载地址:https://github.com/eladkarako/mt/blob/master/x64/mt.exe
      文件命名为gembox.exe.manifest,配置如下:

      <?xml version='1.0' encoding='UTF-8' standalone='yes'?>  
          <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>  
            <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">  
              <security>  
                <requestedPrivileges>  
                  <requestedExecutionLevel level='requireAdministrator' uiAccess='false' />  
                </requestedPrivileges>  
              </security>  
            </trustInfo>  
          </assembly>
      
    • gembox.exe所在目录下执行以下dos命令(win7/win10系统,执行结束后即可发现gembox.exe图标上带了小盾牌):

      mt.exe -manifest "gembox.manifest" -outputresource:"gembox.exe"
      
  3. 使用Inno Setup打包以上制品,任务计划的创建、服务进程的关闭、卸载等dos命令都在Inno Setup中设置。

  4. 最终的Inno Setup配置文件内容,供大家参考(含自定义代码)

    ; Script generated by the Inno Setup Script Wizard.
    ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
    
    [Setup]
    PrivilegesRequired=admin
    ; NOTE: The value of AppId uniquely identifies this application.
    ; Do not use the same AppId value in installers for other applications.
    ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
    AppId={{9CE6C1B6-6029-48B5-B332-0820678F7629}
    AppName=GemBox
    AppVersion=1.0
    ;AppVerName=GemBox 1.0
    AppPublisher=Shanghai Heading
    AppPublisherURL=http://www.hd123.com
    AppSupportURL=http://www.hd123.com
    AppUpdatesURL=http://www.hd123.com
    DefaultDirName={pf}\GemBox
    DefaultGroupName=GemBox
    DisableProgramGroupPage=yes
    OutputDir=F:\test
    OutputBaseFilename=GemBoxSetup
    Compression=lzma
    SolidCompression=yes
    ;安装程序图标
    SetupIconFile=F:\test\print.ico
    ;是否生成卸载程序
    Uninstallable=yes
    ;设置控制面板卸载程序列表中的图标及名称
    ;名称
    UninstallDisplayName=GemBox
    ;图标
    ;UninstallDisplayIcon=F:\test\uninstall.ico
    
    [Languages]
    Name: "english"; MessagesFile: "compiler:Default.isl"
    Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
    
    [Files]
    Source: "F:\GemBox\gembox.exe"; DestDir: "{app}"; Flags: ignoreversion
    Source: "F:\GemBox\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
    ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
    
    [Icons]
    Name: "{group}\GemBox"; Filename: "{app}\gembox.exe"
    
    [Run]
    Filename: "taskkill.exe"; Parameters:"/im GemBox.exe /F";Flags:runhidden
    Filename: "{app}\gembox.exe"; Flags: nowait runhidden
    ;添加任务计划程序,先删再新增
    Filename: "schtasks.exe"; Parameters:"/Delete /tn GemBox /F";Flags:runhidden
    ;RL HIGHEST XP下不支持
    Filename: "schtasks.exe"; Parameters:"/Create /tn GemBox /tr ""{\}""{app}{\}gembox.exe{\}"""" /sc ONLOGON";Flags:waituntilidle
    ;大于XP的版本
    ;Filename: "schtasks.exe"; Parameters:"/Create /tn GemBox /tr ""'{app}\gembox.exe'"" /sc ONLOGON /RL HIGHEST";Flags:nowait runhidden
    
    [Code]
    //Exec(ExpandConstant("{cmd}"), "/c dir c:\ >a.txt",ExpandConstant("{app}"), SW_SHOWNORMAL, ewNoWait, ResultCode)
    
    ///停止服务
    procedure stopService;
    var  
      ResultCode: Integer;
    begin  
      Exec(ExpandConstant('{cmd}'), '/c taskkill /im GemBox.exe /f', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
    end;
    
    function Uninstall():Boolean;
    var ResultCode : Integer;
    begin
      Exec(ExpandConstant('{cmd}'), '/c schtasks.exe /Delete /tn GemBox /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
      Result :=true;
    end;
    
    function InitializeSetup(): Boolean;
    begin
     stopService;
     Result := true;
    end;
    
    // 卸载前检查关闭**进程
    procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
    begin
      if CurUninstallStep = usUninstall then
      begin
        stopService;
        Uninstall();
        //删除文件夹
        //DeleteFile(ExpandConstant('{app}'));
        DelTree(ExpandConstant('{app}'),true,true,true);
      end;
    end;
    

存在缺陷

  1. 总体效果还不错,启动程序制品也才几百k,就是使用了C,一会儿Java一会儿C语言,后续维护挺麻烦的,还好启动程序一般轻易不会动,一次编译所有windows操作系统皆可兼容运行。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容