我的Notion

Clowd.Squirrel

Squirrel.Windows 是一组工具和适用于.Net的库,用于管理 Desktop Windows 应用程序的安装和更新。 Squirrel.Windows 对 Windows 应用程序的实现语言没有任何要求,甚至无需服务端即可完成增量更新。

Clowd.Squirrel 是 Squirrel.Windows 的一个优秀分支。2019 年 Squirrel.Windows 宣布不再维护,虽然 2020 年又重新恢复维护,但其不再处于积极开发阶段,依赖库开始陈旧。所以推荐转移到 Clowd.Squirrel,用法也更加简单。

快速使用

下面以 .net 程序 和 vs 2022 为例,介绍如何使用 Clowd.Squirrel

  1. 安装 Clowd.Squirrel

    1. 通过 nuget包管理器安装 Clowd.Squirrel,

    2. 安装后,目录 ..\packages\Clowd.Squirrel.2.9.40\tools 里是用到的工具

  2. 创建文件 Properties\app.manifest,并在项目属性→生成→设置清单设置该文件

    这一步是为了指定该项目exe需要创建快捷方式,否则安装时会将所有exe文件都建立一个快捷方式

    <?xml version="1.0" encoding="utf-8"?>
    <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
    <SquirrelAwareVersion xmlns="urn:schema-squirrel-com:asm.v1">1</SquirrelAwareVersion>
    </assembly>

  3. 在程序启动入口增加检查更新相关代码

    public static void Main(string[] args)
    {
    // run Squirrel first, as the app may exit after these run
    SquirrelAwareApp.HandleEvents(
    onInitialInstall: OnAppInstall,
    onAppUninstall: OnAppUninstall,
    onEveryRun: OnAppRun); //本地文件夹或服务器地址
    using (var mgr = new UpdateManager(@"D:\Desktop\test"))
    {
    var newVersion = await mgr.UpdateApp(); // optionally restart the app automatically, or ask the user if/when they want to restart
    if (newVersion != null)
    {
    UpdateManager.RestartApp();
    }
    }
    // ... other app init code after ...
    } private static void OnAppInstall(SemanticVersion version, IAppTools tools)
    {
    tools.CreateShortcutForThisExe(ShortcutLocation.StartMenu | ShortcutLocation.Desktop);
    } private static void OnAppUninstall(SemanticVersion version, IAppTools tools)
    {
    tools.RemoveShortcutForThisExe(ShortcutLocation.StartMenu | ShortcutLocation.Desktop);
    } private static void OnAppRun(SemanticVersion version, IAppTools tools, bool firstRun)
    {
    tools.SetProcessAppUserModelId();
    // show a welcome message when the app is first installed
    if (firstRun) MessageBox.Show("Thanks for installing my application!"); // 启动你的应用
    }
  4. 版本号改成3段,需要符合SemVer规范

    [assembly: AssemblyVersion("1.3.2")]
    [assembly: AssemblyFileVersion("1.3.2")]
  5. .csproj 项目文件增加下面的代码,编译 Release 时自动打包

    <Target Name="AfterReleaseBuild" AfterTargets="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="myAssemblyInfo" />
    </GetAssemblyIdentity>
    <Exec Command="$(SolutionDir)packages\Clowd.Squirrel.2.9.40\tools\Squirrel.exe pack --packId $(ProjectName) --packVersion $([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3)) --packAuthors XXX --packDirectory $(OutDir)" />
    </Target>

Squirrel.exe 参数

Squirrel pack`
--releaseDir .\Release # 更新输出到该目录
--framework net6,vcredist143-x86` # Install .NET 6.0 (x64) and vcredist143 (x86) during setup, if not installed
--packId "YourApp"` # Application / package name
--packTitle "YourApp"` # Application / package name
--packVersion "1.0.0"` # Version to build. Should be supplied by your CI
--packAuthors "YourCompany"` # Your name, or your company name
--packDirectory ".\publish"` # The directory the application was published to
--icon "mySetupIcon.ico"` # Icon for Setup.exe
--splashImage "install.gif" # The splash artwork (or animation) to be shown during install

发布更新

首次发布

切换Release模式,编译产生

exe 用于首次安装,先将它发到web服务器,供用户下载

后续更新

代码稍作修改后,提高版本号,再次编译多出以下文件

其中delta是相交于1.3.16的增量更新包,将RELEASES delta文件发到web服务器,UpdateManager类从该web服务器地址获取RELEASES,检查是否有更新,

你也可以再将Setup.exe文件发到web服务器覆盖旧的Setup.exe,以便新安装用户都能下载到最新的安装包

撤回更新

如果不小心发布了问题包。修改bug后,提高版本号,编译。

删除RELEASES文件中有问题的包信息,

发布full 和RELEASES,以便后续用户能更新到正常版本

快捷方式

根据下列顺序,第一个不为空的,作为快捷方式名称

  1. [assembly: AssemblyProduct("MyApp") (AssemblyInfo.cs)
  2. Squirrel.exe packTitle 参数
  3. [assembly: AssemblyDescription("MyApp") ( AssemblyInfo.cs)
  4. exe 文件名

这里我使用 packTitle ,方便控制Release与Test用不同的名称打包。

改进 .csproj 项目文件 内容

$(SolutionDir)packages\Clowd.Squirrel.2.9.40\tools\Squirrel.exe pack  --packTitle 我的APP$(Configuration) --packId $(Configuration).$(ProjectName) --packVersion $([System.Version]::Parse(%(myAssemblyInfo.Version)).ToString(3)) --packAuthors 作者 --packDirectory $(OutDir) --releaseDir .\Publish\$(Configuration) --icon $(ProjectDir)logo.ico

user.config 问题

如果你的应用也使用user.config,会出现”更新版本后设置丢失,变成默认设置“的问题。根本原因是新版 exe 和旧版 exe 目录不同。

user.config 保存在

%LocalAppData%\公司名\MyApp.exe_[Url|StrongName]_Hash码\版本号\user.config

例如

C:\Users\yourname\AppData\Local\yourcompany\MyApp.exe_Url_qdx0no02b2yzg0ddn33isevehzmexfmy\1.3.4.0\user.config

其中Hash码是根据exe所在目录,exe名称等计算所得

而 Squirrel 更新会产生一个新的 app-版本号 文件夹,导致 user.config 目录变化,旧版本的用户设置在新版上不生效

搜索一番解决方法比较复杂,例如重写一个设置适配器SettingsProvider

我的思路

在设置目录里查找,把MyApp.exe_Url_或MyApp.exe_StrongName_开头的文件夹,把低版本的user.config设置复制过来就行了

具体的代码逻辑

wpf

//检查本地配置文件夹
var configPath = GetDefaultExeConfigPath(ConfigurationUserLevel.PerUserRoamingAndLocal);
var configName = "user.config";
var exeName = Assembly.GetExecutingAssembly().GetName().Name + ".exe";
var companyDirectoryName = configPath.Split(new string[1] { exeName }, StringSplitOptions.RemoveEmptyEntries)[0];
var companyDirectory = new DirectoryInfo(companyDirectoryName);
if (companyDirectory.Exists)
{
configPath = configPath.TrimEnd((@"\" + configName).ToCharArray());
configPath = configPath.TrimEnd((@"\" + Assembly.GetExecutingAssembly().GetName().Version).ToCharArray()); var configDirectory = new DirectoryInfo(configPath);
if (!configDirectory.Exists)
{ var urltargetName = exeName + "_Url_";//非强签名Url
var strongNametargetName = exeName + "_StrongName_";//强签名StrongName var drs = companyDirectory.GetDirectories();
var theDrs = drs.Where(x => x.Name.StartsWith(urltargetName)).Concat(drs.Where(x => x.Name.StartsWith(strongNametargetName)));
if (theDrs.Count() > 0)
{
configDirectory.Create();
foreach (var theDr in theDrs)
{
foreach (var d in theDr.GetDirectories())
{
CopyDirectory(d.FullName, configDirectory.FullName + @"\" + d, true);
}
}
}
}
} //最后,把低版本配置升级到最新版。
//新版本号下是否有user.config,如果没有从旧版本升级配置
if (!ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).HasFile)
{
Settings.Default.Upgrade();
}

winfrom(exeName 和 Version 获取方式和 wpf 不一样)

//检查本地配置文件夹
var configPath = Config.GetDefaultExeConfigPath(ConfigurationUserLevel.PerUserRoamingAndLocal);
var configName = "user.config";
var exeName = ResourceAssembly.GetName().Name + ".exe";
var companyDirectoryName = configPath.Split(new string[1] { exeName }, StringSplitOptions.RemoveEmptyEntries)[0];
var companyDirectory = new DirectoryInfo(companyDirectoryName);
if (companyDirectory.Exists)
{
configPath = configPath.TrimEnd((@"\" + configName).ToCharArray());
configPath = configPath.TrimEnd((@"\" + ResourceAssembly.GetName().Version.ToString()).ToCharArray());
var configDirectory = new DirectoryInfo(configPath);
if (!configDirectory.Exists)
{ var urltargetName = exeName + "_Url_";//非强签名Url
var strongNametargetName = exeName + "_StrongName_";//强签名StrongName var drs = companyDirectory.GetDirectories();
var theDrs = drs.Where(x => x.Name.StartsWith(urltargetName)).Concat(drs.Where(x => x.Name.StartsWith(strongNametargetName)));
if (theDrs.Count() > 0)
{
configDirectory.Create();
foreach (var theDr in theDrs)
{
foreach (var d in theDr.GetDirectories())
{
CopyDirectory(d.FullName, configDirectory.FullName + @"\" + d, true);
}
}
}
}
} //最后,把低版本配置升级到最新版。
//新版本号下是否有user.config,如果没有从旧版本升级配置
if (!ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).HasFile)
{
Print.Properties.Settings.Default.Upgrade();
Settings.Default.Upgrade();
}
static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
{
var dir = new DirectoryInfo(sourceDir);
if (!dir.Exists)
return;
DirectoryInfo[] dirs = dir.GetDirectories();
Directory.CreateDirectory(destinationDir);
foreach (FileInfo file in dir.GetFiles())
{
string targetFilePath = Path.Combine(destinationDir, file.Name);
if (!new FileInfo(destinationDir + @"\" + file.Name).Exists)
{
file.CopyTo(targetFilePath, true);
}
} if (recursive)
{
foreach (DirectoryInfo subDir in dirs)
{
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
CopyDirectory(subDir.FullName, newDestinationDir, true);
}
}
}
static string GetDefaultExeConfigPath(ConfigurationUserLevel userLevel)
{
try
{
var UserConfig = ConfigurationManager.OpenExeConfiguration(userLevel);
return UserConfig.FilePath;
}
catch (ConfigurationException e)
{
return e.Filename;
}
}

Windows 程序安装与更新方案: Clowd.Squirrel的更多相关文章

  1. Windows 程序自动更新方案: Squirrel.Windows

    Windows 程序自动更新方案: Squirrel.Windows 1. Squirrel Squirrel 是一组工具和适用于.Net的库,用于管理 Desktop Windows 应用程序的安装 ...

  2. Windows程序通用自动更新模块(C#,.NET4.5以上)

    本通用自动更新模块适合所有Windows桌面程序的自动更新,不论语言,无论Winform还是wpf. 一.工作流程:1. 主程序A调起升级程序B2. B从服务器获取更新程序列表,打印更新信息.3. B ...

  3. mongoDB在windows下安装与配置方案

    首先在官网下载mongoDB的安装包: https://www.mongodb.org/downloads 百度云盘下载:http://pan.baidu.com/s/1slUSGYp (安装版 wi ...

  4. windows server r2 安装vs2017 更新补丁Windows8.1-KB2919355-x6

    方法一: 点击vs2017安装包后提示需要更新Windows8.1-KB2919355-x64补丁 点击链接进入全部下载后查看官方安装顺序为 注意 必须按照以下顺序安装更新:clearcompress ...

  5. 解决xp下无法通过windows installer服务安装此安装程序包。您必须安装带有更新版本Wi

    今天装 tortoisegit 的时候发现安装包不能使用.报错无法通过windows installer服务安装此安装程序包.您必须安装带有更新版本Windows Installer服务的Window ...

  6. 无法通过windows installer服务安装此安装程序包。您必须安装带有更新版本windows Installer服务的Windows

    无法通过windows installer服务安装此安装程序包.您必须安装带有更新版本windows installer服务的Windows 出现这个问题不让安装程序,可以到微软网站更新Windows ...

  7. 安装Office时出现windows installer服务不能更新一个或多个受保护的windows文件错误的解决方法

    今天在Windows XP上安装Microsoft Office 2010时,总是遇到“Windows Installer服务不能更新一个或多个受保护的windows文件,安装失败,正在回滚更改”提示 ...

  8. 解决Windows 7下IE11无法卸载、无法重新安装,提示安装了更新的IE版本

    2013年12月14日 iefans 有用户反馈在Windows 7系统上安装IE11时发现安装程序似乎出了问题,等待了很长时间都没有响应之后就断开了电脑的电源.之后IE11浏览器虽然能使用,却无法安 ...

  9. Scoop - 在Windows命令行上进行程序安装

    2019-01-28  22:49:21 资料来源自Scoop官方网站以及github上的帮助文档 如果有疑惑或者觉得文章有错误请留言以帮助改正 补充内容(2019-04-09 21:11:36):不 ...

随机推荐

  1. Linux下的cman中文帮助手册配置

    Linux下的帮助命令man功能很强大,很好用,但显示的结果是英文,有时候看着还是吃力,就想着要是有man的中文显示结果该多好.网上搜寻一番后,终于找到解决方案,很简单,亲测有效.具体步骤如下: su ...

  2. 前端性能优化(Application Cache篇)

    正巧看到在送书,于是乎找了找自己博客上记录过的一些东西来及其无耻的蹭书了~~~ 小广告:更多内容可以看我的博客 之前在segmentfault上刷问题看到一个关于manifest的问题,很好奇就研究了 ...

  3. 给新手的最简单electron使用教程

    我花了两个月闲暇翻译完了文档,大概是目前最完整最实时的中文文档了,有需要可以去看看学学:github传送门,大多数的需求阅读文档即可解决,实际上,翻译文档正是我入门一项未知事物时的最简单常用的法子. ...

  4. 小程序web-view加载H5信息不全

    满足小程序的web-view标签跳转网页形式 配置小程序后台的web-view(业务域名) 可打开关联的公众号的文章 通常实现逻辑 页面加载的时候赋值于一个data对象的值,然后赋值到web-view ...

  5. 【网易云信】H5 容器技术方案

    Native 开发原生应用是手机操作系统厂商(目前主要是苹果的 iOS 和 Google 的 Android)对外界提供的标准化的开发模式,他们对于 Native 开发提供了一套标准化实现和优化方案. ...

  6. web页面性能优化之接口前置

    上个Q做了一波web性能优化,积累了一点点经验 记录分享一下. 先分享一个比较常用的接口前置 的优化方案吧 优化前首屏秒开大约在40%左右 首屏秒开大约提高了25% 先发一张优化成果图 前置原因 对于 ...

  7. MySQL8.0官方文档学习

    InnoDB架构 下面的架构里只挑选了部分内容进行学习 内存架构(In-Memory Structures) Buffer Pool Buffer Pool是内存中的一块区域,InnoDB访问表和索引 ...

  8. 微信小程序HTTP接口请求封装

    1.方法封装(新建文件夹util,工具文件,在文件夹下创建request.js文件,用于对方法封装)request.js: var app = getApp(); //项目URL相同部分,减轻代码量, ...

  9. Qt QTableView 表格内添加控件

    目录 Qt QTableView 表格内添加控件 1. QItemDelegate 2. setIndexWidget 3. setIndexWidget + setLayout Qt QTableV ...

  10. LC-19

    19. 删除链表的倒数第 N 个结点 思路基本直接出来,双指针,IndexFast 和 IndexSlow 中间相隔 N - 1, 这样 IndexFast 到了最后,IndexSlow 自然就是倒数 ...