在开发桌面程序时,往往需要用到打包工具将程序打包为exe可执行文件。
之前在项目中用了下 InstallShield Limited Edition for Visual Studio 2015,它的功能很强大,但是需要付费使用,而且有些细粒度的操作比较复杂。
后来,我发现了一款简单方便的打包工具,即我们今天的主角Inno Setup
Inno Setup 支持pascal脚本,这样我们就能通过直接写脚本来实现功能了。
下面我们来看看我们打包过程中常见功能需求:
程序的使用方法就不多做介绍了,打开程序新建脚本,会创建出一个基本的框架,然后我们就可以修改脚本添加功能了。
上面的1、2、3 条功能基本的脚本已经帮我们实现了。
关于操作windows服务,我这里收藏了一个极好用的脚本。
// Code pasted from the following address, for examples and more visit it: // http://www.vincenzo.net/isxkb/index.php?title=Service_-_Functions_to_Start%2C_Stop%2C_Install%2C_Remove_a_Service // function IsServiceInstalled(ServiceName: string) : boolean; // function IsServiceRunning(ServiceName: string) : boolean; // function InstallService(FileName, ServiceName, DisplayName, Description : string;ServiceType,StartType :cardinal) : boolean; // function RemoveService(ServiceName: string) : boolean; // function StartService(ServiceName: string) : boolean; // function StopService(ServiceName: string) : boolean; // function SetupService(service, port, comment: string) : boolean; type SERVICE_STATUS = record dwServiceType : cardinal; dwCurrentState : cardinal; dwControlsAccepted : cardinal; dwWin32ExitCode : cardinal; dwServiceSpecificExitCode : cardinal; dwCheckPoint : cardinal; dwWaitHint : cardinal; end; HANDLE = cardinal; const SERVICE_QUERY_CONFIG = $1; SERVICE_CHANGE_CONFIG = $2; SERVICE_QUERY_STATUS = $4; SERVICE_START = $10; SERVICE_STOP = $20; SERVICE_ALL_ACCESS = $f01ff; SC_MANAGER_ALL_ACCESS = $f003f; SERVICE_WIN32_OWN_PROCESS = $10; SERVICE_WIN32_SHARE_PROCESS = $20; SERVICE_WIN32 = $30; SERVICE_INTERACTIVE_PROCESS = $100; SERVICE_BOOT_START = $0; SERVICE_SYSTEM_START = $1; SERVICE_AUTO_START = $2; SERVICE_DEMAND_START = $3; SERVICE_DISABLED = $4; SERVICE_DELETE = $10000; SERVICE_CONTROL_STOP = $1; SERVICE_CONTROL_PAUSE = $2; SERVICE_CONTROL_CONTINUE = $3; SERVICE_CONTROL_INTERROGATE = $4; SERVICE_STOPPED = $1; SERVICE_START_PENDING = $2; SERVICE_STOP_PENDING = $3; SERVICE_RUNNING = $4; SERVICE_CONTINUE_PENDING = $5; SERVICE_PAUSE_PENDING = $6; SERVICE_PAUSED = $7; // ####################################################################################### // nt based service utilities // ####################################################################################### function OpenSCManager(lpMachineName, lpDatabaseName: string; dwDesiredAccess :cardinal): HANDLE; external '[email protected] stdcall'; function OpenService(hSCManager :HANDLE;lpServiceName: string; dwDesiredAccess :cardinal): HANDLE; external '[email protected] stdcall'; function CloseServiceHandle(hSCObject :HANDLE): boolean; external '[email protected] stdcall'; function CreateService(hSCManager :HANDLE;lpServiceName, lpDisplayName: string;dwDesiredAccess,dwServiceType,dwStartType,dwErrorControl: cardinal;lpBinaryPathName,lpLoadOrderGroup: String; lpdwTagId : cardinal;lpDependencies,lpServiceStartName,lpPassword :string): cardinal; external '[email protected] stdcall'; function DeleteService(hService :HANDLE): boolean; external '[email protected] stdcall'; function StartNTService(hService :HANDLE;dwNumServiceArgs : cardinal;lpServiceArgVectors : cardinal) : boolean; external '[email protected] stdcall'; function ControlService(hService :HANDLE; dwControl :cardinal;var ServiceStatus :SERVICE_STATUS) : boolean; external '[email protected] stdcall'; function QueryServiceStatus(hService :HANDLE;var ServiceStatus :SERVICE_STATUS) : boolean; external '[email protected] stdcall'; function QueryServiceStatusEx(hService :HANDLE;ServiceStatus :SERVICE_STATUS) : boolean; external '[email protected] stdcall'; function GetLastError() : cardinal; external '[email protected] stdcall'; function OpenServiceManager() : HANDLE; begin if UsingWinNT() = true then begin Result := OpenSCManager('','',SC_MANAGER_ALL_ACCESS); if Result = 0 then MsgBox('the servicemanager is not available', mbError, MB_OK) end else begin MsgBox('only nt based systems support services', mbError, MB_OK) Result := 0; end end; function IsServiceInstalled(ServiceName: string) : boolean; var hSCM : HANDLE; hService: HANDLE; begin hSCM := OpenServiceManager(); Result := false; if hSCM <> 0 then begin hService := OpenService(hSCM,ServiceName,SERVICE_QUERY_CONFIG); if hService <> 0 then begin Result := true; CloseServiceHandle(hService) end; CloseServiceHandle(hSCM) end end; function InstallService(FileName, ServiceName, DisplayName, Description : string;ServiceType,StartType :cardinal) : boolean; var hSCM : HANDLE; hService: HANDLE; begin hSCM := OpenServiceManager(); Result := false; if hSCM <> 0 then begin hService := CreateService(hSCM,ServiceName,DisplayName,SERVICE_ALL_ACCESS,ServiceType,StartType,0,FileName,'',0,'','',''); if hService <> 0 then begin Result := true; // Win2K & WinXP supports aditional description text for services if Description<> '' then RegWriteStringValue(HKLM,'System\CurrentControlSet\Services\' + ServiceName,'Description',Description); CloseServiceHandle(hService) end; CloseServiceHandle(hSCM) end end; function RemoveService(ServiceName: string) : boolean; var hSCM : HANDLE; hService: HANDLE; begin hSCM := OpenServiceManager(); Result := false; if hSCM <> 0 then begin hService := OpenService(hSCM,ServiceName,SERVICE_DELETE); if hService <> 0 then begin Result := DeleteService(hService); CloseServiceHandle(hService) end; CloseServiceHandle(hSCM) end end; function StartService(ServiceName: string) : boolean; var hSCM : HANDLE; hService: HANDLE; begin hSCM := OpenServiceManager(); Result := false; if hSCM <> 0 then begin hService := OpenService(hSCM,ServiceName,SERVICE_START); if hService <> 0 then begin Result := StartNTService(hService,0,0); CloseServiceHandle(hService) end; CloseServiceHandle(hSCM) end; end; function StopService(ServiceName: string) : boolean; var hSCM : HANDLE; hService: HANDLE; Status : SERVICE_STATUS; begin hSCM := OpenServiceManager(); Result := false; if hSCM <> 0 then begin hService := OpenService(hSCM,ServiceName,SERVICE_STOP); if hService <> 0 then begin Result := ControlService(hService,SERVICE_CONTROL_STOP,Status); CloseServiceHandle(hService) end; CloseServiceHandle(hSCM) end; end; function IsServiceRunning(ServiceName: string) : boolean; var hSCM : HANDLE; hService: HANDLE; Status : SERVICE_STATUS; begin hSCM := OpenServiceManager(); Result := false; if hSCM <> 0 then begin hService := OpenService(hSCM,ServiceName,SERVICE_QUERY_STATUS); if hService <> 0 then begin if QueryServiceStatus(hService,Status) then begin Result :=(Status.dwCurrentState = SERVICE_RUNNING) end; CloseServiceHandle(hService) end; CloseServiceHandle(hSCM) end end;
有了这个脚本,windows 服务怎么玩都可以了,在[code] 下把这个文件加上就可以使用其中的方法了。
接下来要操作注册表了,在[Registry]下写脚本我们就可以操作注册表了,格式如下:
[Registry]
Root: HKLM; Subkey: "SOFTWARE\ODBC\ODBC.INI\bsjdw101100";ValueType: string; ValueName: "AutoStop"; ValueData: "Yes"
Root: HKLM; Subkey: "SOFTWARE\ODBC\ODBC.INI\bsjdw101100";ValueType: string; ValueName: "Debug"; ValueData: "No"
修改的东西包括,注册表路径、值类型、key和value。
再往下,我们就要写打包.Net framework 进安装包。XP 没有自带.Net framework 环境,有的可能会带 2.0 。win 7 自带 .Net framework 3.5(包含 .NET 2.0 and 3.0) ,Windows 8 默认安装了 .NET Framework 4.5 (包含 4.0)。但是.Net 4.0是不向下兼容.Net 3.5的,所以即使你装了4.0 你的程序可能还是用不了。感兴趣可以参考Net Framework各个版本区别
下面我们来看看这些脚本怎么写?
首先我们要把.Net 环境加入到打包程序中
[file] Source: "D:\NetFx20SP2_x86.exe"; DestDir: "{tmp}"; Flags: ignoreversion {#IsExternal}; Check: NeedsFramework <pre name="code" class="plain">//添加执行操作
[Run]
Filename: {tmp}\NetFx20SP2_x86.exe; Parameters: "/q:a /c:""install /l /q"""; WorkingDir: {tmp}; Flags: skipifdoesntexist; StatusMsg: "Installing .NET Framework if needed"
Filename: {win}\Microsoft.NET\Framework\v2.0.50727\CasPol.exe; Parameters: "-q -machine -remgroup ""{#MyAppName}"""; WorkingDir: {tmp}; Flags: skipifdoesntexist runhidden; StatusMsg: "Setting Program Access Permissions..."
Filename: {win}\Microsoft.NET\Framework\v2.0.50727\CasPol.exe; Parameters: "-q -machine -addgroup 1.2 -url ""file://{app}/*"" FullTrust -name ""{#MyAppName}"""; WorkingDir: {tmp}; Flags: skipifdoesntexist runhidden; StatusMsg: "Setting Program Access Permissions..."
检查目标系统中是否有.Net 环境(以2.0为例),有则安装,无则不装。
//判断是否需要安装.NET Framework 2.0 SP1(或以上) function NeedsFramework(): Boolean; begin Result := (IsDotNET20Detected = false); end; function IsDotNET20Detected(): boolean; var success: boolean; sp: cardinal; begin success := RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727', 'SP', sp); Result := success and (sp >= 1); end;
接着,我们要建立互斥变量来限制只能运行一个安装程序。
function InitializeSetup():boolean; var bResult:boolean; begin Result := true; //检测是否有另一个安装程序在运行 bResult := CheckForMutexes('MutexBugskySetup'); if bResult = true then begin MsgBox('另一安装程序已经在运行,此安装程序将退出。',mbInformation,MB_OK); Result := false; Exit; end else begin //没有就创建互斥量 CreateMutex('MutexBugskySetup'); end; end;
接下来我们要检查安装和卸载时是否程序正在运行,为了提示用户保存数据,所以要关闭程序后再安装。
<pre name="code" class="plain">function InitializeSetup():boolean; var ResultCode:Integer; var hwnd:HWND; var CloseNum:integer begin //先检测进程中程序是否在运行 hwnd := FindWindowByWindowName('{#MyAppName}'); CloseNum := 0; if hwnd <> 0 then begin ResultCode := MsgBox('检测程序正在运行,是否关闭程序继续安装?'#13 #10'选择“是”继续安装;选择“否”,退出安装。',mbInformation,MB_YESNO); //继续安装 if ResultCode = IDYES then begin Result := true; end else begin Result := false; Exit; end; end; while ((hwnd <> 0) and (CloseNum < 5)) do begin //关闭服务程序,尝试5次。 PostMessage(hwnd,18,0,0); Sleep(100); CloseNum := CloseNum + 1; hwnd := FindWindowByWindowName('{#MyAppName}'); end; if IsServiceRunning('{#MyAppServiceName}') then begin StopService('{#MyAppServiceName}'); end end; end;
卸载的时候,只要在CurUninstallStepChanged()方法中做同样的事就行了。
好了,就写到这里了,具体的语法和API请参考Inno Setup帮助文档。
参考:
http://www.cnblogs.com/xiaogangqq123/archive/2012/03/19/2405730.html
http://stackoverflow.com/questions/2456987/upgrading-windows-service-using-inno-setup