Windows 程式安裝與更新方案: Clowd.Squirrel

来源:https://www.cnblogs.com/ohzxc/archive/2022/05/21/16295615.html
-Advertisement-
Play Games

我的Notion Clowd.Squirrel Squirrel.Windows 是一組工具和適用於.Net的庫,用於管理 Desktop Windows 應用程式的安裝和更新。 Squirrel.Windows 對 Windows 應用程式的實現語言沒有任何要求,甚至無需服務端即可完成增量更新。 ...


我的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;
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 強轉int類型會直接對浮點數的小數部分進行截斷(無論是正還是負)。還有一種方法是math.ceil和math.floor。無論是正數還是負數,都遵循:ceil往數軸正方向取整,floor往數軸負方向取整。round原型為round(value, ndigits),可以將一個浮點數取整到固定的小數位。... ...
  • 很多人都喜歡使用黑色的主題樣式,包括我自己,使用了差不多三年的黑色主題,但是個人覺得在進行視窗轉換的時候很廢眼睛。 比如IDEA是全黑的,然後需要看PDF或者WORD又變成白色的了,這樣來回切換導致眼睛很累,畢竟現在網頁以及大部分軟體的界面都是白色的。那麼還是老老實實的使用原來比較順眼的模式吧。 1 ...
  • 本期教程人臉識別第三方平臺為虹軟科技,本文章講解的是人臉識別RGB活體追蹤技術,免費的功能很多可以自行搭配,希望在你看完本章課程有所收穫。 ...
  • 《零基礎學Java》 繪製幾何圖形 Java可以分別使用 Graphics 和 Graphics2D 繪製圖形,Graphics類 使用不同的方法繪製不同的圖形(drawLine()方法可f以繪製線、drawRect()方法用於繪製矩形、drawOval()方法用於繪製橢圓形)。 Graphics類 ...
  • 我使用Spring AOP實現了用戶操作日誌功能 今天答辯完了,復盤了一下系統,發現還是有一些東西值得拿出來和大家分享一下。 需求分析 系統需要對用戶的操作進行記錄,方便未來溯源 首先想到的就是在每個方法中,去實現記錄的邏輯,但是這樣做肯定是不現實的,首先工作量大,其次違背了軟體工程設計原則(開閉原 ...
  • 大家好,今天給大家介紹一款輕量、快速、穩定可編排的組件式規則引擎框架LiteFlow。 一、LiteFlow的介紹 LiteFlow官方網站和代碼倉庫地址 官方網站:https://yomahub.com/liteflow Gitee托管倉庫:https://gitee.com/dromara/li ...
  • 1. Netty源碼研究筆記(3)——Channel系列 依舊是通過先縱向再橫向的研究方法,在開篇中,我們發現不管是Sever還是Client,最終的啟動是通過調用channel的對應方法來完成的,而這個動作實際在channel綁定的eventLoop中執行。 接下來,我們繼續EchoSever、E ...
  • 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblog ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...