inno setup6.6.1实例,制作安装包,创建共享文件夹,写入注册表(提供给excel加载项,此文章解释iss文件)

Vue Office 插件Inno Setup脚本解释文档

一、脚本概述

1.1 核心定位

本脚本适配 Inno Setup 6.6.1,是通用Vue Office插件安装/卸载自动化脚本,基于官方原生属性与方法开发,无第三方依赖,可直接编译生成可执行安装包。核心用于Windows系统下Vue Office插件的分发部署,实现"安装/卸载"二合一自动化管控,包含环境检测、权限校验、日志记录、注册表配置、网络共享管理等全流程功能。

1.2 核心特性

  • 二合一功能:集成安装/卸载选项,无需单独制作安装包和卸载包
  • 环境自适应:自动识别Office 2013+版本、Windows LanmanServer文件共享服务状态,兼容Win10/11系统
  • 安全合规:自动注册Excel可信目录,避免插件被Office安全策略拦截
  • 日志溯源:实时输出操作日志(界面显示+本地文件保存至{app}\install.log),便于问题排查
  • 权限管控:强制要求管理员权限运行,保障共享创建、注册表修改等关键操作生效
  • 兼容适配:自动启用SMB1.0协议,兼容老旧Windows系统的文件共享逻辑

二、脚本完整结构拆解

2.1 宏定义区(全局常量)

作用

统一管理脚本中可复用的常量,降低后期维护成本,所有宏定义均可根据实际需求自定义修改。

宏名称 默认取值 核心作用
MyAppName "Vue Office 插件" 应用名称,用于向导标题、完成页提示文本、程序组名称
MyAppVersion "1.1" 插件版本号,标识当前插件版本
MyAppPublisher "开发者" 发布者名称,用于安装程序属性展示、注册表备注
MyAppURL "https://example.com/" 官方链接,用于安装程序的"支持/更新"链接跳转
ShareName "VueOfficeAddin" 网络共享名称,插件目录的共享标识(Excel访问插件依赖此共享)
DefaultInstallDir "{commonappdata}\VueOfficeAddin" 默认安装路径,公共应用数据目录(无需用户手动选择)
FixedGUID "{01254BD0-0689-43DE-9274-8D3368DF7ADF}" Excel可信目录注册表唯一标识,避免与其他插件注册表项冲突(可通过Inno Setup的Tools→Generate GUID生成新值替换)

2.2 Setup核心配置区([Setup])

作用

配置安装程序的基础行为、界面样式、权限要求、压缩规则等核心参数,所有配置项均为Inno Setup 6.6.1官方原生支持属性,无自定义扩展。

完整配置项说明
配置项 取值示例 核心作用
AppId {{01254BD0-0689-43DE-9274-8D3368DF7ADF}} 应用唯一标识,与FixedGUID一致,关联系统的安装/卸载记录
AppName {#MyAppName} 引用宏定义的应用名称,统一维护
AppVersion {#MyAppVersion} 引用宏定义的版本号
DefaultDirName {#DefaultInstallDir} 引用宏定义的安装路径,固定安装位置
DisableDirPage yes 禁用目录选择页面,防止用户修改安装路径
DisableProgramGroupPage yes 禁用程序组创建页面(插件无需创建开始菜单组)
DisableReadyMemo yes 禁用"准备安装"备注页,简化安装流程
DisableReadyPage yes 禁用"准备安装"确认页,点击下一步直接执行安装
DisableStartupPrompt yes 禁用安装程序启动时的提示框,直接进入向导
OutputBaseFilename VueOfficeAddin_Setup 生成的安装包文件名(最终输出为VueOfficeAddin_Setup.exe)
Compression lzma 采用LZMA高压缩算法,减小安装包体积
SolidCompression yes 启用固实压缩,进一步提升压缩率
PrivilegesRequired admin 强制要求管理员权限运行(创建共享、修改注册表必须管理员权限)
PrivilegesRequiredOverridesAllowed commandline 允许通过命令行参数覆盖权限要求(仅用于调试)
Uninstallable yes 支持卸载功能,生成卸载程序
WizardStyle modern dynamic polar 现代动态极地风格向导界面,优化用户体验
AllowNoIcons yes 允许无快捷方式(插件无需桌面/开始菜单快捷方式)
AppPublisher {#MyAppPublisher} 引用宏定义的发布者名称
AppPublisherURL {#MyAppURL} 引用宏定义的官方URL(安装程序属性中的"发布者链接")
AppSupportURL {#MyAppURL} 引用宏定义的官方URL(安装程序属性中的"支持链接")
AppUpdatesURL {#MyAppURL} 引用宏定义的官方URL(安装程序属性中的"更新链接")
DefaultGroupName Vue Office 插件 默认程序组名称(虽禁用创建,但预留配置)
SetupIconFile .\favicon.ico 安装程序图标路径(需替换为自身图标文件路径)
WizardImageFile .\images\WizardImage.bmp 向导页面大图路径(需替换为自身图片路径)
WizardSmallImageFile .\images\WizardSmallImage.bmp 向导页面小图路径(需替换为自身图片路径)

2.3 语言配置区([Languages])

Languages

Name: "chinesesimp"; MessagesFile: "compiler:Default.isl"

作用

配置安装程序的显示语言,此处仅启用简体中文,直接引用Inno Setup编译器内置的简体中文语言文件,无需额外配置。

2.4 文件复制配置区([Files])

Files

Source: "manifest.xml"; DestDir: "{app}"; Flags: ignoreversion

作用

定义需要复制到安装目录的插件核心文件,核心参数说明:

  • Source: 源文件路径(此处为插件清单文件manifest.xml,需确保该文件与iss脚本同目录)
  • DestDir: 目标目录({app} 等价于宏定义的DefaultInstallDir,即安装路径)
  • Flags: ignoreversion: 忽略文件版本检查,强制复制(避免因版本号问题导致复制失败)

2.5 自定义代码区([Code])

作用

实现安装/卸载的核心业务逻辑、自定义界面控件、事件处理等,所有代码均基于Inno Setup原生Pascal脚本语法,无第三方扩展。

2.5.1 全局变量定义

var

UserChoice: Integer; // 用户选择:1=安装, 2=卸载

ProgressMemo: TMemo; // 日志显示控件

OperationCaption: String; // 当前操作名称(安装/卸载)

SelectPage: TWizardPage; // 自定义操作选择页

MarqueeProgressBar: TNewProgressBar; // 跑马灯进度条

ProgressLabel: TNewStaticText; // 进度条提示标签

2.5.2 核心函数说明
  1. LogStatus(Text: String):日志输出函数,同时将日志显示在界面Memo控件、写入本地install.log文件
  2. GetComputerNameStr():获取计算机名称,用于构建UNC共享路径(格式:\计算机名\共享名)
  3. RemoveNetworkShareIfExists(ShareNameParam: String):检查并删除已存在的同名网络共享,避免创建冲突
  4. CheckServerService():检查并确保LanmanServer(文件共享)服务正常运行,设置为自动启动
  5. EnableSMB1Protocol():启用SMB1.0协议,兼容老旧Windows系统的文件共享逻辑
  6. CreateNetworkShare(ShareNameParam, FolderPath: String):创建网络共享,包含目录创建、NTFS权限配置、共享发布
  7. GetOfficeMajorVersion():自动识别Office主版本(15.0/16.0/19.0),用于注册表路径匹配
  8. RegisterExcelSharedFolder(ShareNameParam: String):注册Excel可信目录,避免插件被Office安全策略拦截
  9. DeleteExcelRegistry():卸载时删除Excel可信目录对应的注册表项
  10. DeleteSharedFolder(FolderPath: String):卸载时删除安装目录及其中所有文件
  11. IsAdmin():检查当前用户是否拥有管理员权限,无权限则日志提示
2.5.3 核心事件说明
  1. InitializeWizard():初始化向导界面,创建安装/卸载选择页、跑马灯进度条、日志显示Memo控件
  2. CurPageChanged(CurPageID: Integer):页面切换事件,修改完成页的提示文本(安装完成/卸载完成)
  3. CurStepChanged(CurStep: TSetupStep):安装步骤变更事件,执行核心的安装/卸载逻辑
  4. CurUninstallStepChanged(CurUninstallStep: TUninstallStep):卸载步骤变更事件,清理共享、注册表、安装目录等残留

三、使用注意事项

  1. 文件替换:将manifest.xml替换为自身插件的清单文件,SetupIconFile/WizardImageFile等路径替换为实际文件路径
  2. GUID替换:通过Inno Setup的Tools→Generate GUID生成新GUID,替换脚本中的FixedGUID/AppId
  3. 权限要求:脚本必须以管理员身份运行,否则无法创建共享、修改注册表
  4. 环境适配:支持Windows 10/11、Office 2013+,SMB1.0协议可根据实际需求注释
  5. 日志排查:安装/卸载日志保存在{commonappdata}\VueOfficeAddin\install.log,可用于问题定位
  6. 编译方式:使用Inno Setup 6.6.1打开iss脚本,点击"Compile"按钮即可生成安装包

代码如下:

javascript 复制代码
; ----------------------------
; 通用Vue Office插件安装脚本
; 适配 Inno Setup 6.6.1,仅使用官方原生属性/方法
; ----------------------------

; ======================== 宏定义区 ========================
; 应用名称(可自定义)
#define MyAppName "Vue Office 插件"
; 应用版本号
#define MyAppVersion "1.1"
; 发布者(可自定义)
#define MyAppPublisher "开发者"
; 官方URL(可自定义)
#define MyAppURL "https://example.com/"
; 网络共享名称(插件共享标识)
#define ShareName "VueOfficeAddin" 
; 默认安装目录(公共应用数据目录)
#define DefaultInstallDir "{commonappdata}\VueOfficeAddin"
; 固定GUID(用于Excel注册表唯一标识,可自行生成新GUID替换)
#define FixedGUID "{01254BD0-0689-43DE-9274-8D3368DF7ADF}"

; ======================== Setup核心配置 ========================
[Setup]
; 应用唯一标识(需与上面的FixedGUID一致,或自行生成)
AppId={{01254BD0-0689-43DE-9274-8D3368DF7ADF}}
; 应用名称(引用宏定义)
AppName={#MyAppName}
; 应用版本
AppVersion={#MyAppVersion}
; 默认安装目录
DefaultDirName={#DefaultInstallDir}
; 禁用目录选择页面(固定安装路径)
DisableDirPage=yes
; 禁用程序组创建页面
DisableProgramGroupPage=yes
; 禁用准备安装备注页
DisableReadyMemo=yes
; 禁用准备安装确认页
DisableReadyPage=yes
; 禁用启动提示框
DisableStartupPrompt=yes
; 安装包输出文件名
OutputBaseFilename=VueOfficeAddin_Setup
; 压缩方式(LZMA高压缩)
Compression=lzma
; 固实压缩(提升压缩率)
SolidCompression=yes
; 要求管理员权限(创建共享/修改注册表需要)
PrivilegesRequired=admin
; 允许命令行覆盖权限要求
PrivilegesRequiredOverridesAllowed=commandline
; 允许卸载
Uninstallable=yes
; 向导样式(现代动态极地风格)
WizardStyle=modern dynamic polar
; 允许无快捷方式
AllowNoIcons=yes
; 发布者信息
AppPublisher={#MyAppPublisher}
; 发布者URL
AppPublisherURL={#MyAppURL}
; 支持URL
AppSupportURL={#MyAppURL}
; 更新URL
AppUpdatesURL={#MyAppURL}
; 默认程序组名称
DefaultGroupName=Vue Office 插件
; 许可证文件(如需添加,取消注释并替换路径)
; LicenseFile=.\license.txt
; 安装程序图标(需替换为自己的图标路径)
SetupIconFile=.\favicon.ico
; 向导大图(需替换为自己的图片路径)
WizardImageFile=.\images\WizardImage.bmp
; 向导小图(需替换为自己的图片路径)
WizardSmallImageFile=.\images\WizardSmallImage.bmp

; ======================== 语言配置 ========================
[Languages]
; 简体中文语言包(使用Inno Setup内置默认语言文件)
Name: "chinesesimp"; MessagesFile: "compiler:Default.isl"

; ======================== 文件复制配置 ========================
[Files]
; 复制插件清单文件到安装目录,忽略版本检查
Source: "manifest.xml"; DestDir: "{app}"; Flags: ignoreversion

; ======================== 自定义代码区 ========================
[Code]
//全局变量定义
var
  UserChoice: Integer; // 用户选择:1=安装, 2=卸载
  ProgressMemo: TMemo; // 日志显示控件
  OperationCaption: String; // 当前操作名称(安装/卸载)
  SelectPage: TWizardPage; // 自定义操作选择页
  MarqueeProgressBar: TNewProgressBar; // 跑马灯进度条
  ProgressLabel: TNewStaticText; // 进度条提示标签

// -----------------------------------------------------------------------------
// 功能:日志输出(同时显示在界面和本地日志文件)
// 参数:Text - 要输出的日志内容
//-----------------------------------------------------------------------------
procedure LogStatus(Text: String);
var
  LogFile: String;
  SL: TStringList;
begin
  // 显示到界面Memo控件
  if Assigned(ProgressMemo) then
  begin
    ProgressMemo.Lines.Add(Text);
    ProgressMemo.SelStart := Length(ProgressMemo.Text); // 滚动到最后一行
    ProgressMemo.Update; // 立即刷新界面
  end;

  // 写入本地日志文件
  LogFile := ExpandConstant('{app}\install.log');
  SL := TStringList.Create;
  try
    // 确保日志目录存在
    if not DirExists(ExtractFileDir(LogFile)) then
      CreateDir(ExtractFileDir(LogFile));
      
    // 加载现有日志(追加模式)
    if FileExists(LogFile) then 
      SL.LoadFromFile(LogFile);
    SL.Add(Text); // 添加新日志行
    SL.SaveToFile(LogFile); // 保存日志
  finally
    SL.Free; // 释放资源
  end;
end;

//-----------------------------------------------------------------------------
//功能:获取计算机名称(用于构建UNC共享路径)
// 返回值:计算机名,默认localhost
//-----------------------------------------------------------------------------
function GetComputerNameStr(): String;
begin
  Result := GetEnv('COMPUTERNAME');
  if Result = '' then Result := 'localhost';
end;

//-----------------------------------------------------------------------------
//功能:检查并删除已存在的同名网络共享
// 参数:ShareNameParam - 共享名称
//-----------------------------------------------------------------------------
procedure RemoveNetworkShareIfExists(ShareNameParam: String);
var
  ResultCode: Integer;
begin
  LogStatus('🔍 检查并删除旧共享: ' + ShareNameParam);

  // 方法1:使用net share命令删除
  Exec(ExpandConstant('{cmd}'),
       '/c net share ' + ShareNameParam + ' /DELETE /Y',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  // 方法2:使用PowerShell的SMB共享命令(兼容新版Windows)
  Exec('powershell',
       '-Command "if (Get-SmbShare -Name ''' + ShareNameParam + ''' -ErrorAction SilentlyContinue) { Remove-SmbShare -Name ''' + ShareNameParam + ''' -Force -ErrorAction SilentlyContinue }"',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  // 日志输出结果
  if ResultCode = 0 then
    LogStatus('✅ 已删除旧共享: ' + ShareNameParam)
  else
    LogStatus('ℹ️ 无旧共享或删除失败(非错误): ' + ShareNameParam);
end;

//-----------------------------------------------------------------------------
// 功能:检查并确保LanmanServer(文件共享)服务正常运行
// 说明:文件共享依赖该服务,需确保启动且设为自动
//-----------------------------------------------------------------------------
procedure CheckServerService();
var
  ResultCode: Integer;
begin
  LogStatus('🔍 检查LanmanServer(文件共享)服务状态');
  
  // 检查服务状态
  Exec('powershell',
       '-Command "$s=Get-Service -Name LanmanServer -ErrorAction SilentlyContinue; if($s){Write-Host $s.Status}else{Write-Host ''NotFound''}"',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  if ResultCode = 0 then
  begin
    LogStatus('✅ LanmanServer 服务运行正常');
    // 设置服务为自动启动
    Exec('powershell',
         '-Command "Set-Service -Name LanmanServer -StartupType Automatic"',
         '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  end
  else
  begin
    LogStatus('⚠️ LanmanServer 服务不可用,尝试启动...');
    // 尝试启动服务
    Exec('powershell',
         '-Command "Start-Service -Name LanmanServer -ErrorAction SilentlyContinue"',
         '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
    LogStatus('⚠️ 已尝试启动LanmanServer服务,返回码: ' + IntToStr(ResultCode));
  end;
end;

//-----------------------------------------------------------------------------
//功能:启用SMB1.0协议(兼容老旧Windows系统)
// -----------------------------------------------------------------------------
procedure EnableSMB1Protocol();
var
  ResultCode: Integer;
begin
  LogStatus('🔍 检查并启用SMB1.0协议(兼容旧系统)');
  Exec('powershell',
       '-Command "Enable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -All -NoRestart -ErrorAction SilentlyContinue"',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  LogStatus('ℹ️ SMB1.0协议启用操作完成,返回码: ' + IntToStr(ResultCode));
end;

//-----------------------------------------------------------------------------
// 功能:创建网络共享(含目录创建、权限配置、共享发布)
// 参数:
//  ShareNameParam - 共享名称
//   FolderPath - 要共享的文件夹路径
//-----------------------------------------------------------------------------
procedure CreateNetworkShare(ShareNameParam, FolderPath: String);
var
  ResultCode: Integer;
  Computer: String;
  UNCPath: String;
begin
  Computer := GetComputerNameStr();
  UNCPath := '\\' + Computer + '\' + ShareNameParam;

  // 确保安装目录存在
  if not DirExists(FolderPath) then
  begin
    LogStatus('📁 创建安装目录: ' + FolderPath);
    if not CreateDir(FolderPath) then
    begin
      LogStatus('❌ 创建目录失败: ' + FolderPath);
      Exit;
    end;
  end;

  // 设置文件夹NTFS权限(Everyone完全控制)
  LogStatus('🔧 设置文件夹NTFS权限: ' + FolderPath);
  Exec('powershell',
       '-Command "$acl=Get-Acl ''' + FolderPath + '''; $rule=New-Object System.Security.AccessControl.FileSystemAccessRule(''Everyone'',''FullControl'',''ContainerInherit,ObjectInherit'',''None'',''Allow''); $acl.SetAccessRule($rule); Set-Acl ''' + FolderPath + ''' $acl"',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  // 使用net share创建共享
  LogStatus('🔧 使用net share创建共享: ' + ShareNameParam);
  Exec(ExpandConstant('{cmd}'),
       '/c net share ' + ShareNameParam + '=' + '"' + FolderPath + '" /GRANT:Everyone,FULL /Y',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  // 结果判断与日志输出
  if ResultCode = 0 then
  begin
    LogStatus('✅ 共享创建成功: ' + UNCPath);
    // 验证共享路径可访问
    if FileExists(UNCPath + '\') then
      LogStatus('✅ 共享路径可访问: ' + UNCPath)
    else
      LogStatus('⚠️ 共享创建成功,但访问验证失败(可能是网络延迟)');
  end
  else
  begin
    LogStatus('❌ net share创建共享失败,返回码: ' + IntToStr(ResultCode));
    LogStatus('❌ 失败原因排查:');
    LogStatus('  1. 未以管理员身份运行安装程序');
    LogStatus('  2. 组策略/杀毒软件阻止了共享创建');
    LogStatus('  3. 文件夹路径含特殊字符或权限不足');
    LogStatus('  4. 手动执行命令排查:net share ' + ShareNameParam + '=' + '"' + FolderPath + '" /GRANT:Everyone,FULL');
  end;
end;

//-----------------------------------------------------------------------------
// 功能:获取Office主版本号(用于注册表路径)
//返回值:Office版本号(16.0/19.0/15.0),默认16.0
// -----------------------------------------------------------------------------
function GetOfficeMajorVersion(): String;
begin
  if RegKeyExists(HKCU, 'Software\Microsoft\Office\16.0\Excel') then
    Result := '16.0'
  else if RegKeyExists(HKCU, 'Software\Microsoft\Office\19.0\Excel') then
    Result := '19.0'
  else if RegKeyExists(HKCU, 'Software\Microsoft\Office\15.0\Excel') then
    Result := '15.0'
  else
    Result := '16.0';
end;

// -----------------------------------------------------------------------------
//功能:注册Excel可信目录(避免插件被Excel拦截)
// 参数:ShareNameParam - 共享名称
// -----------------------------------------------------------------------------
procedure RegisterExcelSharedFolder(ShareNameParam: String);
var
  UNCPath, RegPath: String;
  ResultCode: Integer;
  OfficeVersion: String;
begin
  OfficeVersion := GetOfficeMajorVersion();
  UNCPath := '\\' + GetComputerNameStr() + '\' + ShareNameParam;
  RegPath := 'HKCU\Software\Microsoft\Office\' + OfficeVersion + '\WEF\TrustedCatalogs\{#FixedGUID}';

  LogStatus('🔧 注册Excel可信目录: ' + UNCPath);

  // 创建注册表项
  Exec(ExpandConstant('{sys}\reg.exe'), 'add "' + RegPath + '" /f', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  
  // 设置注册表值(Flags:启用标识)
  Exec(ExpandConstant('{sys}\reg.exe'),
       'add "' + RegPath + '" /v "Flags" /t REG_DWORD /d 1 /f',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  // 设置ID(唯一标识)
  Exec(ExpandConstant('{sys}\reg.exe'),
       'add "' + RegPath + '" /v "Id" /t REG_SZ /d "{#FixedGUID}" /f',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  // 设置可信目录URL
  Exec(ExpandConstant('{sys}\reg.exe'),
       'add "' + RegPath + '" /v "Url" /t REG_SZ /d "' + UNCPath + '" /f',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  // 设置描述信息
  Exec(ExpandConstant('{sys}\reg.exe'),
       'add "' + RegPath + '" /v "Description" /t REG_SZ /d "VueOffice插件" /f',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  // 验证注册表项是否创建成功
  if RegKeyExists(HKCU, RegPath) then
    LogStatus('✅ Excel可信目录注册成功: ' + UNCPath)
  else
    LogStatus('❌ Excel可信目录注册失败(注册表项未创建)');
end;

//-----------------------------------------------------------------------------
// 功能:卸载时删除Excel可信目录注册表项
// -----------------------------------------------------------------------------
procedure DeleteExcelRegistry();
var 
  ResultCode: Integer;
  RegPath: String;
  OfficeVersion: String;
begin
  OfficeVersion := GetOfficeMajorVersion();
  RegPath := 'HKCU\Software\Microsoft\Office\' + OfficeVersion + '\WEF\TrustedCatalogs\{#FixedGUID}';
  LogStatus('🔧 删除Excel可信目录注册表');
  
  // 注册表项存在则删除
  if RegKeyExists(HKCU, RegPath) then
  begin
    Exec(ExpandConstant('{sys}\reg.exe'), 'delete "' + RegPath + '" /f', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
    if ResultCode = 0 then
      LogStatus('✅ Excel注册表项删除成功')
    else
      LogStatus('⚠️ Excel注册表项删除失败(可能无相关项),返回码: ' + IntToStr(ResultCode));
  end
  else
  begin
    LogStatus('ℹ️ 无本插件注册表项,无需删除');
  end;
end;

// -----------------------------------------------------------------------------
//功能:卸载时删除安装文件夹
//参数:FolderPath - 要删除的文件夹路径
//-----------------------------------------------------------------------------
procedure DeleteSharedFolder(FolderPath: String);
begin
  LogStatus('🔧 删除安装文件夹: ' + FolderPath);
  if DirExists(FolderPath) then
  begin
    // 删除文件夹内所有文件
    DelTree(FolderPath + '\*.*', True, True, True);
    // 删除空文件夹
    if DelTree(FolderPath, True, True, True) then
      LogStatus('✅ 安装文件夹删除成功: ' + FolderPath)
    else
      LogStatus('⚠️ 安装文件夹删除失败(可能被占用): ' + FolderPath);
  end
  else
  begin
    LogStatus('ℹ️ 安装文件夹不存在,无需删除');
  end;
end;

//-----------------------------------------------------------------------------
//功能:更新操作选择页的下一步按钮文字
//-----------------------------------------------------------------------------
procedure UpdateFirstPageButtonText();
begin
  if WizardForm.CurPageID = SelectPage.ID then
  begin
    WizardForm.NextButton.Visible := True;
    if UserChoice = 1 then
      WizardForm.NextButton.Caption := '开始安装'
    else
      WizardForm.NextButton.Caption := '开始卸载';
  end;
end;

//-----------------------------------------------------------------------------
// 事件:安装单选按钮点击事件
// -----------------------------------------------------------------------------
procedure InstallRadioClick(Sender: TObject);
begin
  UserChoice := 1;
  OperationCaption := '安装';
  UpdateFirstPageButtonText();
  WizardForm.Caption := '{#MyAppName} 安装程序';
end;

// -----------------------------------------------------------------------------
//事件:卸载单选按钮点击事件
// -----------------------------------------------------------------------------
procedure UninstallRadioClick(Sender: TObject);
begin
  UserChoice := 2;
  OperationCaption := '卸载';
  UpdateFirstPageButtonText();
  WizardForm.Caption := '{#MyAppName} 卸载程序';
end;

//-----------------------------------------------------------------------------
//事件:初始化向导界面(创建自定义控件)
//-----------------------------------------------------------------------------
procedure InitializeWizard();
var
  InstallRadio, UninstallRadio: TRadioButton;
begin
  WizardForm.Caption := '{#MyAppName} 安装程序';
  
  // 1. 创建操作选择页(安装/卸载选择)
  SelectPage := CreateCustomPage(wpWelcome, '操作选择', '请选择要执行的操作');

  // 创建安装单选按钮
  InstallRadio := TRadioButton.Create(SelectPage);
  InstallRadio.Parent := SelectPage.Surface;
  InstallRadio.Top := 10;
  InstallRadio.Left := 10;
  InstallRadio.Width := 400;
  InstallRadio.Caption := '安装 Vue Office 插件';
  InstallRadio.Checked := True;
  InstallRadio.OnClick := @InstallRadioClick;

  // 创建卸载单选按钮
  UninstallRadio := TRadioButton.Create(SelectPage);
  UninstallRadio.Parent := SelectPage.Surface;
  UninstallRadio.Top := 40;
  UninstallRadio.Left := 10;
  UninstallRadio.Width := 300;
  UninstallRadio.Caption := '卸载 Vue Office 插件';
  UninstallRadio.OnClick := @UninstallRadioClick;

  // 默认选择安装
  UserChoice := 1;
  OperationCaption := '安装';

  // 2. 创建跑马灯进度条(安装/卸载过程显示)
  MarqueeProgressBar := TNewProgressBar.Create(WizardForm);
  MarqueeProgressBar.Parent := WizardForm.InstallingPage;
  MarqueeProgressBar.Left := 0;
  MarqueeProgressBar.Top := ScaleY(20);
  MarqueeProgressBar.Width := WizardForm.InstallingPage.ClientWidth;
  MarqueeProgressBar.Height := ScaleY(20);
  MarqueeProgressBar.Style := npbstMarquee; // 跑马灯样式
  MarqueeProgressBar.Visible := True;

  // 创建进度条提示标签
  ProgressLabel := TNewStaticText.Create(WizardForm);
  ProgressLabel.Parent := WizardForm.InstallingPage;
  ProgressLabel.Left := ScaleX(0);
  ProgressLabel.Top := MarqueeProgressBar.Top - ScaleY(20);
  ProgressLabel.Caption := '处理中,请稍候...';
  ProgressLabel.Visible := True;

  // 3. 创建日志显示Memo控件(实时显示操作日志)
  ProgressMemo := TMemo.Create(WizardForm);
  ProgressMemo.Parent := WizardForm.InstallingPage;
  ProgressMemo.Left := 0;
  ProgressMemo.Top := MarqueeProgressBar.Top + MarqueeProgressBar.Height + ScaleY(5);
  ProgressMemo.Width := WizardForm.InstallingPage.ClientWidth;
  ProgressMemo.Height := WizardForm.InstallingPage.ClientHeight - ProgressMemo.Top;
  ProgressMemo.ScrollBars := ssVertical; // 垂直滚动条
  ProgressMemo.ReadOnly := True; // 只读
  ProgressMemo.Font.Name := 'Consolas'; // 等宽字体,便于日志阅读
  ProgressMemo.Font.Size := 9;

  // 隐藏原有默认进度条
  WizardForm.ProgressGauge.Visible := False;

  // 初始化选择页按钮文字
  UpdateFirstPageButtonText();
end;

//-----------------------------------------------------------------------------
//事件:页面切换事件(修改完成页文本)
//参数:CurPageID - 当前页面ID
//-----------------------------------------------------------------------------
procedure CurPageChanged(CurPageID: Integer);
begin
  // 处理完成页
  if CurPageID = wpFinished then
  begin
    // 根据用户选择修改完成页文本
    if UserChoice = 1 then
    begin
      WizardForm.FinishedHeadingLabel.Caption := '安装完成';
      WizardForm.FinishedLabel.Caption := '{#MyAppName} 已成功安装!';
    end
    else
    begin
      WizardForm.FinishedHeadingLabel.Caption := '卸载完成';
      WizardForm.FinishedLabel.Caption := '{#MyAppName} 已成功卸载!';
    end;
  end
end;

//-----------------------------------------------------------------------------
// 功能:检查当前用户是否拥有管理员权限
//返回值:True=有管理员权限,False=无
//-----------------------------------------------------------------------------
function IsAdmin: Boolean;
var 
  ResultCode: Integer;
begin
  Exec('powershell',
       '-Command "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);

  Result := (ResultCode = 0);
  // 日志输出权限检查结果
  if Result then
    LogStatus('✅ 当前用户拥有管理员权限')
  else
    LogStatus('❌ 当前用户无管理员权限,' + OperationCaption + '可能失败!');
end;

//-----------------------------------------------------------------------------
// 事件:安装步骤变更事件(执行核心安装/卸载逻辑)
//参数:CurStep - 当前安装步骤
// -----------------------------------------------------------------------------
procedure CurStepChanged(CurStep: TSetupStep);
var
  AppFolder: String;
  LocalShareName: String;
begin
  AppFolder := ExpandConstant('{app}');
  LocalShareName := '{#ShareName}';

  // 安装后步骤(执行核心逻辑)
  if CurStep = ssPostInstall then
  begin
    LogStatus('=====================================');
    LogStatus('开始执行' + OperationCaption + '操作: ');
    LogStatus('=====================================');

    // 检查管理员权限
    IsAdmin();

    // 安装逻辑
    if UserChoice = 1 then
    begin
      LogStatus('🔹 开始环境检测...');
      EnableSMB1Protocol(); // 启用SMB1.0
      CheckServerService(); // 检查文件共享服务
      LogStatus('🔹 环境检测完成');

      LogStatus('🔹 开始安装流程...');
      RemoveNetworkShareIfExists(LocalShareName); // 删除旧共享
      CreateNetworkShare(LocalShareName, AppFolder); // 创建新共享
      RegisterExcelSharedFolder(LocalShareName); // 注册Excel可信目录
      LogStatus('=====================================');
      LogStatus('✅ 安装流程执行完成');
      LogStatus('=====================================');
    end
    // 卸载逻辑
    else if UserChoice = 2 then
    begin
      LogStatus('🔹 开始卸载流程...');
      RemoveNetworkShareIfExists(LocalShareName); // 删除共享
      DeleteExcelRegistry(); // 删除注册表项
      DeleteSharedFolder(AppFolder); // 删除安装目录
      LogStatus('=====================================');
      LogStatus('✅ 卸载流程执行完成');
      LogStatus('=====================================');
    end;
  end;
end;

//-----------------------------------------------------------------------------
// 事件:卸载步骤变更事件(卸载后清理残留)
//参数:CurUninstallStep - 当前卸载步骤
//-----------------------------------------------------------------------------
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  LocalShareName: String;
begin
  LocalShareName := '{#ShareName}';
  // 卸载后步骤(清理残留)
  if CurUninstallStep = usPostUninstall then
  begin
    LogStatus('🔹 卸载后清理残留...');
    RemoveNetworkShareIfExists(LocalShareName); // 清理共享
    DeleteExcelRegistry(); // 清理注册表
    DeleteSharedFolder(ExpandConstant('{app}')); // 清理安装目录
  end;
end;
相关推荐
2501_907136822 小时前
通用财务账模板【Excel版】
excel·软件需求
MediaTea2 小时前
Python:实例 __dict__ 详解
java·linux·前端·数据库·python
狗头大军之江苏分军2 小时前
又是一个周末加班夜,前端的我只想哭…
前端
nnsix2 小时前
文件系统、分配单元大小 什么意思
windows
个案命题2 小时前
鸿蒙ArkUI组件通信专家:@Param装饰器的奇幻漂流
java·服务器·前端
Data_agent2 小时前
CNFANS模式淘宝1688代购系统搭建指南
大数据·开发语言·前端·javascript
美酒没故事°2 小时前
vue3+element 滚动触底加载选择器
javascript·vue.js·ecmascript
CodeCraft Studio2 小时前
Excel处理控件Aspose.Cells教程:使用C#在Excel中创建折线图
java·c#·excel·aspose.cells·excel图表·excel api库·excel折线图
澄江静如练_3 小时前
表单输入绑定
服务器·前端·javascript