在.NET 開發的旅程中,高效的版本管理和便捷的發佈流程是提升開發效率和專案品質的關鍵環節。今天,我們就來深入探討如何利用腳本實現.NET Avalonia UI 專案(當然,這些方法也適用於其他.NET 專案)的自動更新程式版本以及一鍵發佈,讓你的開發過程更加流暢和高效。
PowerShell 腳本執行權限檢查
在執行 PowerShell 腳本之前,了解並正確設定執行原則是至關重要的。執行原則決定了 PowerShell 是否允許執行腳本以及執行何種類型的腳本。你可以使用以下簡單的命令來查看目前的執行原則:
Get-ExecutionPolicy
不同的執行原則值有著不同的含義,對腳本的執行有著不同程度的限制:
- Restricted:這是預設原則,它就像是一個嚴格的門衛,不允許執行任何腳本,你只能在 PowerShell 主控台中逐行輸入命令。這在一定程度上確保了系統的安全性,但對於需要批次執行腳本的開發者來說,無疑是一種束縛。
- AllSigned:只允許執行由受信任的發行者簽名的腳本。這就好比只允許持有特定通行證的人進入,確保了腳本來源的可靠性,但也增加了腳本使用的門檻,因為並非所有腳本都有簽章。
- RemoteSigned:本地建立的腳本可以直接執行,而從網際網路下載的腳本必須由受信任的發行者簽名才能執行。這是一個在安全性和便捷性之間取得平衡的設定,既允許本地開發的靈活性,又對網路下載的腳本進行了安全把關。
- Unrestricted:允許執行所有腳本,不過從網際網路下載的腳本在執行前會提示確認。這給予了開發者極大的自由度,但也伴隨著一定的安全風險,因為未經嚴格檢查的腳本可能攜帶惡意程式碼。
- Bypass:不阻止任何腳本的執行,也不會有安全提示。這是一個非常開放的設定,一般僅在特定的、安全可控的環境中使用,否則可能會讓系統暴露在安全威脅之下。
預設情況下,執行原則是 Restricted,這可能會導致回寫檔案失敗,提示不允許寫入串流。例如,當你嘗試執行一個需要修改檔案內容的腳本時,就會遇到權限問題。
修改執行原則
為了能夠順利執行腳本,我們需要根據自己的需求選擇合適的執行原則。如果希望在保證一定安全性的同時,能夠方便地執行本地腳本和經過一定安全檢查的網路下載腳本,將執行原則設定為 RemoteSigned 是一個不錯的選擇。
但需要注意的是,修改執行原則可能需要系統管理員權限,所以請務必以系統管理員身分執行 PowerShell,然後使用以下命令進行修改:
Set-ExecutionPolicy RemoteSigned
這樣,本地建立的腳本可以直接執行,而從網際網路下載的腳本則需要經過簽章驗證,有效降低了安全風險。
驗證修改結果
修改執行原則後,為了確保設定生效,你可以再次使用 Get-ExecutionPolicy 命令來驗證是否修改成功。當看到輸出結果為 RemoteSigned 時,就意味著你已經成功修改了執行原則,之後應該就可以正常執行 .ps1 腳本了。
VS 發佈
在 Visual Studio 中,我們可以透過在建置前事件中加入腳本來實現專案版本的自動更新。具體操作如下:在專案屬性中找到「建置事件」索引標籤,在「建置前事件命令列」中加入以下命令:
powershell -ExecutionPolicy Bypass -File "UpdateAssemblyVersion.ps1" -AssemblyInfoFile "GlobalAssembly.cs" -Configuration "$(ConfigurationName)" -Platform "$(PlatformName)"
這裡的參數有著明確的含義:
- ConfigurationName:表示使用中的解決方案組態,常見的如 Debug(偵錯模式)和 Release(發行模式)。在不同的組態下,我們可能希望產生不同版本號的程式,以區分開發和正式發行的版本。
- PlatformName:表示使用中的解決方案平台,例如 AnyCPU(適用於任何 CPU 架構)、x86(32 位元架構)、x64(64 位元架構)等。不同的平台可能需要不同的版本標識,以滿足相容性和效能需求。
批次檔發佈
有時候,我們可能希望在不開啟 Visual Studio 的情況下發佈程式,並且能夠方便地擴展到發佈 Windows、Linux、macOS 等多個平台。這時候,批次檔就派上用場了。以下是一個範例批次檔:
@echo off
setlocal enabledelayedexpansion
rem 遍歷 GlobalAssemblies 目錄下的檔案執行 PowerShell 腳本
for %%f in (GlobalAssemblies\*) do (
powershell -ExecutionPolicy Bypass -File "UpdateAssemblyVersion.ps1" -AssemblyInfoFile "%%f" -Configuration "Release" -Platform "x64"
rem 檢查 PowerShell 腳本執行是否成功
if !errorlevel! neq 0 (
echo Failed to update assembly version for file %%f!
endlocal
exit /b !errorlevel!
)
)
rem 設定發佈路徑
set PUBLISH_PATH=publish\win-x64
rem 定義專案資訊陣列,格式為 專案名稱,專案路徑(只寫專案目錄),相對發佈目錄名稱
set "projects=碼坊工具箱,src\CodeWF.Toolbox.Desktop,codewf Avalonia發佈測試,tests\AvaloniaAotDemo,AvaloniaAotDemo"
rem 遍歷專案陣列
for %%a in ("%projects: =","%") do (
set "current_project=%%~a"
for /f "tokens=1,2,3 delims=," %%b in ("!current_project!") do (
set "projectName=%%b"
set "projectPath=%%c"
set "relativePublishDir=%%d"
rem 拼接 .pubxml 檔案的預設路徑
set "pubxmlPath=!projectPath!\Properties\PublishProfiles\FolderProfile-win_x64.pubxml"
rem 拼接目前專案的發佈路徑
set "CURRENT_PUBLISH_DIR=!PUBLISH_PATH!\!relativePublishDir!"
echo "projectName after assignment: !projectName!"
echo "projectPath after assignment: !projectPath!"
echo "relativePublishDir after assignment: !relativePublishDir!"
echo "pubxmlPath after assignment: !pubxmlPath!"
echo "current publish dir after assignment: !CURRENT_PUBLISH_DIR!"
echo Publishing !projectName! for win-64...
rem 清空發佈目錄
if exist "!CURRENT_PUBLISH_DIR!" (
rd /s /q "!CURRENT_PUBLISH_DIR!"
)
rem 建立發佈目錄
mkdir /p "!CURRENT_PUBLISH_DIR!" 2>nul
rem 執行 dotnet publish 命令進行 AOT 發佈,並指定目標架構
dotnet publish "!projectPath!" /p:PublishProfile="!pubxmlPath!" -f net9.0-windows -o "!CURRENT_PUBLISH_DIR!"
rem 檢查 dotnet publish 命令的結束代碼
if !errorlevel! neq 0 (
echo Publish of !projectName! failed!
endlocal
exit /b !errorlevel!
)
rem 檢查發佈目錄是否存在
if exist "!CURRENT_PUBLISH_DIR!" (
rem 刪除偵錯符號檔
del "!CURRENT_PUBLISH_DIR!\*.pdb"
)
echo Publish of !projectName! succeeded!
echo Published files of !projectName! are located at: "!CURRENT_PUBLISH_DIR!"
)
)
endlocal
這個批次檔首先遍歷 GlobalAssemblies 目錄下的檔案,執行 PowerShell 腳本來更新程式版本。然後,它定義了專案資訊陣列,包括專案名稱、專案路徑和相對發佈目錄名稱。接著,透過迴圈遍歷專案陣列,依次對每個專案進行發佈操作。在發佈過程中,先清空發佈目錄,再建立新的發佈目錄,然後執行 dotnet publish 命令進行 AOT 發佈,並指定目標架構為 net9.0-windows。最後,檢查發佈是否成功,如果成功則刪除偵錯符號檔,並輸出成功資訊和發佈路徑。
PowerShell 腳本實現自動版本更新
下面這個 PowerShell 腳本用於根據 Git 標籤(Tag)自動更新程式版本:
param(
[Parameter(Mandatory=$true)]
[string]$AssemblyInfoFile,
[Parameter(Mandatory=$true)]
[string]$Configuration,
[Parameter(Mandatory=$true)]
[string]$Platform
)
# 檢查檔案是否存在
if (-not (Test-Path $AssemblyInfoFile)) {
throw "錯誤:檔案 $AssemblyInfoFile 不存在"
}
Write-Output "AssemblyInfoFile: $AssemblyInfoFile"
Write-Output "Configuration: $Configuration"
Write-Output "Platform: $Platform"
#取得目前日期時間,並產生格式化的時間戳記
$currentDateTime =Get-Date
#取得目前分支,取出標籤建立時間[基準時間]
$strBranchCreateTime =git log -1 --format=%ai $latest_tag
$dateBranchCreateTime=[DateTime]::Parse($strBranchCreateTime)
#時間差的總分鐘數
$timeDifMinutes =[math]::Round(($currentDateTime-$dateBranchcreateTime).TotalMinutes)
#版本號格式處理成x.x.65535.65535
$yy=[math]::Floor($timeDifMinutes/65535)
$thirdInfo=$timeDifMinutes-$yy*65535
#讀取檔案內容
$content = Get-Content -Path $AssemblyInfoFile -Encoding Unicode
#使用正則表達式取得 AssemblyProduct 的值
$productPattern='^\[assembly: AssemblyProduct\("([^"]+)"\)\]'
$product = ""
#尋找包含 AssemblyProduct 的行,並匹配正則表達式
foreach($line in $content){
if($line -match $productPattern){
$product= $matches[1]
break #找到匹配後離開迴圈
}
}
$items=$product -split '_'
$product=$items[0]
#目前最近標籤、checkout hash值
$currentCommitHash=git describe --always --tag --long
$items=$currentCommitHash.Split('-')
$hashInfo=$items[$items.Length-1]
$firstFileVersion="0.0"
$secondFileVersion="100"
if($items.Length -eq 3)
{
$firstFileVersion=$items[0].Replace("v","");
$firstFileItems=$firstFileVersion.split('.');
$firstFileversion=$firstFileItems[0]+"."+$firstFileItems[1]
$secondFileVersion=$items[1]+"$yy".PadLeft(2,'0')
}
$platformInfo = ""
if ($Configuration -eq "Debug") {
$platformInfo = "D"
} elseif ($Configuration -eq "Release") {
$platformInfo = "R"
} else {
$platformInfo = "A"
}
switch ($Platform) {
"x86" {
$platformInfo += "-86"
}
"x64" {
$platformInfo += "-64"
}
"ARM" {
$platformInfo += "-ARM"
}
"AnyCPU" {
$platformInfo += "-AnyCPU"
}
default {
$platformInfo += "-Unknow"
}
}
#定義新的 AssemblyProduct和 AssemblyFileVersion
$strDayInfo= $currentDateTime.Tostring("yyyyMMddHHmm")
$newAssemblyProduct = "$product"+"_"+"$platformInfo"+"_"+"$hashInfo"+"_"+"$strDayInfo"
if($secondFileVersion -eq "000")
{
$secondFileVersion="0"
}
$newAssemblyFileVersion = "$firstFileVersion"+"."+"$secondFileVersion"+"."+"$thirdInfo"
# 更新 AssemblyProduct
$content = $content -replace '^\[assembly: AssemblyProduct\(".*"\)\]', "[assembly: AssemblyProduct(`"$newAssemblyProduct`")]"
# 更新AssemblyVersion
$content = $content -replace '^\[assembly: AssemblyVersion\(".*"\)\]', "[assembly: AssemblyVersion(`"$newAssemblyFileVersion`")]"
# 更新AssemblyFileVersion
$content = $content -replace '^\[assembly: AssemblyFileVersion\(".*"\)\]', "[assembly: AssemblyFileVersion(`"$newAssemblyFileVersion`")]"
# 將更新後的內容寫入 AssemblyInfo.cs 檔案
Set-Content -Path $AssemblyInfoFile -Value $content -Encoding Unicode
這個腳本首先接收三個參數:AssemblyInfoFile(組件資訊檔案路徑)、Configuration(組態名稱)和 Platform(平台名稱)。然後,它檢查檔案是否存在,如果不存在則擲出錯誤。接著,透過取得目前日期時間、Git 標籤建立時間等資訊,計算出版本號的各個部分。根據組態和平台資訊,產生新的 AssemblyProduct 和 AssemblyFileVersion。最後,使用正則表達式取代檔案中的舊版本資訊,將更新後的內容寫入 AssemblyInfo.cs 檔案,實現了程式版本的自動更新。
透過以上這些腳本和方法,我們可以極大地提高.NET 專案的開發效率,實現版本管理和發佈流程的自動化。希望這些內容能對你的開發工作有所幫助,讓你在.NET 開發的道路上更加得心應手。
以上範例腳本可在開源專案找到:dotnet9/CodeWF.Toolbox: CodeWF Toolbox 使用 Avalonia 開發的跨平台工具箱 Cross platform toolbox developed using Avalonia