一般要執行 MSBuild 指令,標準做法是由開始選單找到 Developer Command Prompt for VS 2019 或 Developer PowerShell for VS 2019:

而這兩個環境跟一般命令視窗的差別在於它會先執行 "X:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" 設定必要的環境變數,確保相關作業能正確執行。

開發團隊成員所安裝的 VS 版本跟位置可能有差異,甚至有些環境只有裝 MSBuild 沒裝 IDE,所以我想寫一個 PowerShell 腳本,自動找到 VS2019/VS2017 的安裝位置呼叫 MSBuild 編譯專案,減少人為操作,也可能當成日後轉批次或自動化的基礎。

關於尋找 VS 安裝路徑的方法,我做了些研究:

  • 較早的 Visual Studio 版本,可由 Registry HKLM\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\xx.0 找安裝路徑
  • VS2017 開始改用新的安裝架構,還一併推出可以取得 Visual Studio 安裝資訊的程式元件(Microsoft.VisualStudio.Setup.Configuration.Interop),另外有個 C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe 工具可以列出已安裝版本。
  • 除了前述程式元件,Windows Management Framework (WMF) 5.0 還內建 Visual Studio Setup PowerShell Module
    網友有應用它找安裝路徑的範例

工作環境會以 VS2017+ 為主,看起來 VSSetup PowerShell Module 是最方便的選擇,下指令 Install-Module VSSetup -Scope CurrentUser 便可下載安裝,又可跟 PowerShell 無縫整合。

程式原理是先用 Get-Command 檢查 VSSetup 模組是否已安裝,若無則現場安裝。透過 Get-VSSetupInstance | Select-VSSetupInstance 找到最新版 VS,找到 VsMSBuildCmd.bat。這裡有個眉角,執行 VsMSBuildCmd.bat 環境變數是設在 Cmd.exe 工作階段,不會傳到 PowerShell 端。參考 PowerShell Community Extension (Pscx) Invoke-BatchFile 的巧妙做法,在 Cmd.exe 執行 SET 將所有環境變數匯出成檔案,再從 PowerShell 讀檔全部重設一次,將環境變數通通複製過來。

我的想法是在為每個專案寫一個專屬的編譯腳本,其中可包含各式客製化編譯程序,擁有最大彈性。以昨天提到的 Web Site Project 為例,將 MSBuildProj.ps1 與 .sln 放在同目錄;

MSBuildProj.ps1 範例如下:

function LoadVSMSBuildCmd() {
    # 檢查 VSSetup 指令是否存在,若無則現場安裝
    [bool] $vsSetupInstalled = $null -ne (Get-Command Get-VSSetupINstance -ErrorAction SilentlyContinue)
    if (!$vsSetupInstalled) {
        Write-Verbose "Installing VSSetup module..."
        Install-Module VSSetup -Scope CurrentUser -Force
    }

    # 取得最新版本 VS 的安裝路徑,類似 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise
    $instPath = (Get-VSSetupInstance | Select-VSSetupInstance -Latest -Require Microsoft.Component.MSBuild).InstallationPath
    $batPath = "$instPath\Common7\Tools\VsMSBuildCmd.bat"
    if (Test-Path $batPath -PathType Leaf) {
        # 執行 VsMSBuildCmd.bat 後,將所有環境變數複製到 PowerShell 環境中
        # ref: https://github.com/Pscx/Pscx/blob/master/Src/Pscx/Modules/Utility/Pscx.Utility.psm1
        $tempFile = [IO.Path]::GetTempFileName()
        cmd.exe /c " `"$batPath`" && set " > $tempFile
        Get-Content $tempFile | Foreach-Object {
            if ($_ -match "^(.*?)=(.*)$") {
                Set-Content "env:\$($matches[1])" $matches[2]
            }
            else {
                $_
            }
        }
        Remove-Item $tempFile
        Invoke-BatchFile $batPath
        
    }
}

LoadVSMSBuildCmd

# 將工作目錄切換到 .ps1 所在資料夾
Set-Location $PSScriptRoot

MSBuild.exe WebSite\website.publishproj "/p:DeployOnBuild=true;PublishProfile=$(Get-Location)\WebSite\App_Data\PublishProfiles\FolderPRofile.pubxml"

如此,從版控拉回原始碼後,執行 .ps1 即可進行編譯,不易受環境影響,編譯部署工作可以更簡單化標準化,應是不錯的策略。

Tips of writing flexible and customized PowerShell script to build .NET project.


Comments

Be the first to post a comment

Post a comment