前幾天介紹如何把發展成熟的 PowerShell 小工具(.ps1) 發佈到私有 Repository 與其他成員共享,Install-Script 安裝到電腦環境,以後不必每次複製 .ps1,像內建 Cmdlet 一樣輸入 Script 名稱(不需加 .ps1,還有自動完)就能在電腦的任何角落執行。

但 Install-Script 有個限制,註冊對象只限單一 .ps1 檔,若 .ps1 依賴特定組件(.dll)才能運作,Install-Script 無法連同 dll 部署安裝。這種情況,就要考慮把 .ps1 轉成模組

PowerShell 模組通常是一群相關函式的集合,以模組為單位可便於開發、部署、安裝、移除及管理,並可內含腳本檔案、必要組件(Assembly, .dll)、相關資源檔案... 等等。

PowerShell 模組由四種基本元素組成:

  1. 程式碼:PowerShell Script (.psm1) 或是以 .NET 開發的 Cmdlet 組件
  2. 上述程式需要的檔案:參照的組件檔(.dll)、說明檔、額外腳本檔、相關資源檔案... 等等
  3. Manifest 檔案 (.psd1):列舉上述檔案,並提供作者、版號等資訊
  4. 資料夾:集中儲放上述檔案,方便 PowerShell 取得模組相關資源

這篇文章用一個簡單 QR Code 產生器當範例,示範怎麼將 .ps1 轉成模組發佈及共用。

假設我們有個 ConvertTo-QRCode.ps1,可將任意文件轉成 QR Code,核心邏輯全靠 QRCoder 這顆開源專案元件,PowerShell 只是白手套,接收參數,建立 QRCoder.QRCodeGenerator 物件並用它產生圖檔:(圖檔預設會以日期時間產生檔名存在當下目錄,有個小眉角是 .NET 的工作目錄不等於 PowerShell 的工作目錄,要用 $(Get-Location) 取代 ".",延伸閱讀:PowerShell 與 .NET 的工作目錄差異)

Param(
    [Parameter(Mandatory=$true)]
    [string]$text,
    [string]$imgPath
)
$ErrorActionPreference = "STOP"
Add-Type -Path $PSScriptRoot\QRCoder.dll

$qrGenerator = New-Object QRCoder.QRCodeGenerator
[byte[]] $textBytes = [System.Text.Encoding]::UTF8.GetBytes($text)
$qrCodeData = $qrGenerator.CreateQrCode([byte[]]$textBytes, [int][QRCoder.QRCodeGenerator]::ECCLevel.Q)
$qrCode = New-Object QRCoder.QRCode($qrCodeData)
$img = $qrCode.GetGraphic(20)
if (!$imgPath) { $imgPath = "$(Get-Location)\QRCode-$(Get-Date -Format MMddHHmmssfff).png" }
$img.Save($imgPath);

由於 ConvertTo-QRCode.ps1 依賴 QRCoder.dll,無法透過 Install-Script 安裝運作,故我們打算將它包成模組共用。

首先,我們將 ConvertTo-QRCode.ps1 轉成 QRCodeTools.psm1 (QRCodeTools 是模組的名字),將原本的主邏輯包成 Function ConvertTo-QRCode,最後加上 Export-ModuleMember -Function ConvertTo-QRCode 將其對外開放:

<#
 .Synopsis
  QR Code 產生器

 .Description
  輸入字串顯示 QR Code

 .Parameter Text
  要轉為 QR Code 的文字

 .Parameter ImgPath
  圖檔路徑,未提供時以時間流水號檔名寫入工作目錄

 .Example
   # 產生 QR Code
   ConvertTo-QRCode "Hello World"

 .Example
   # 產生 QR Code 並指定圖檔路徑
   ConvertTo-QRCode "Hello World" "D:\Temp\QRCode.png"
#>
function ConvertTo-QRCode {
    Param(
        [Parameter(Mandatory = $true)]
        [string]$text,
        [string]$imgPath
    )
    $ErrorActionPreference = "STOP"
    Add-Type -Path $PSScriptRoot\QRCoder.dll

    $qrGenerator = New-Object QRCoder.QRCodeGenerator
    [byte[]] $textBytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $qrCodeData = $qrGenerator.CreateQrCode([byte[]]$textBytes, [int][QRCoder.QRCodeGenerator]::ECCLevel.Q)
    $qrCode = New-Object QRCoder.QRCode($qrCodeData)
    $img = $qrCode.GetGraphic(20)
    if (!$imgPath) { $imgPath = "$(Get-Location)\QRCode-$(Get-Date -Format MMddHHmmssfff).png" }
    Write-Verbose "Saving image to $imgPath"
    $img.Save($imgPath);
}

Export-ModuleMember -Function ConvertTo-QRCode

下一步是製作 QRCodeTools.psd1,呼叫 New-ModuleManifest -Path .\QRCodeTools.psd1 -ModuleVersion "1.0" -Author "Jeffrey" PowerShell 會產生範本,我要要再補上一些資料,基本要素是 RootModule 指向 .psm1、Description 加入說明、FunctionsToExport 列舉開放功能、FileList 列出要參照的 dll 及要包進模組資源檔。小工具純供自用,提供這些就夠,若是要發行到官方 Repository 對全世界分享,則要提供較完整資訊。

#
# 模組 'QRCodeTools' 的模組資訊清單
#
# 產生者: Jeffrey
#
# 產生位置: 2021/4/9
#

@{

# 與這個資訊清單相關聯的指令碼模組或二進位模組檔案。
RootModule = 'QRCodeTools.psm1'

# 這個模組的版本號碼。
ModuleVersion = '1.0'

# 支援的 PSEditions
# CompatiblePSEditions = @()

# 用來唯一識別這個模組的識別碼
GUID = 'b946f3a9-e8b5-4c6d-ade9-ef05990fbd02'

# 這個模組的作者
Author = 'Jeffrey'

# 這個模組的公司或廠商
CompanyName = 'Darkthread Studio'

# 這個模組的著作權聲明
Copyright = '(c) 2021 Jeffrey. 著作權所有,並保留一切權利。'

# 這個模組所提供功能的描述
Description = '輸入文字產生 QRCode'

# 這個模組需要的最小 Windows PowerShell 引擎版本
# PowerShellVersion = ''

# 這個模組需要的 Windows PowerShell 主機名稱
# PowerShellHostName = ''

# 這個模組需要的最小 Windows PowerShell 主機版本
# PowerShellHostVersion = ''

# 此模組所需的最低 Microsoft .NET Framework 版本。此先決條件只對 PowerShell 電腦版有效。
# DotNetFrameworkVersion = ''

# 此模組需要的最低通用語言執行平台 (CLR) 版本。此先決條件只對 PowerShell 電腦版有效。
# CLRVersion = ''

# 這個模組需要的處理器架構 (無、X86、Amd64)
# ProcessorArchitecture = ''

# 匯入這個模組前,必須匯入全域環境的模組
# RequiredModules = @()

# 匯入這個模組前,必須載入的組件
# RequiredAssemblies = @()

# 匯入這個模組前,在呼叫者的環境中執行的指令檔 (.ps1)。
# ScriptsToProcess = @()

# 匯入這個模組時即將載入的類型檔案 (.ps1xml)
# TypesToProcess = @()

# 匯入這個模組時即將載入的格式檔案 (.ps1xml)
# FormatsToProcess = @()

# 要匯入為 RootModule/ModuleToProcess 中指定之模組的巢狀模組的模組
# NestedModules = @()

# 要從此模組匯出的函式。為獲得最佳效能,請勿使用萬用字元且不要刪除項目,若沒有要匯出的函式,請使用空陣列。
FunctionsToExport = @('ConvertTo-QRCode')

# 要從此模組匯出的 Cmdlet。為獲得最佳效能,請勿使用萬用字元且不要刪除項目,若沒有要匯出的 Cmdlet,請使用空陣列。
CmdletsToExport = @()

# 要從這個模組匯出的變數
VariablesToExport = '*'

# 要從此模組匯出的別名。為獲得最佳效能,請勿使用萬用字元且不要刪除項目,若沒有要匯出的別名,請使用空陣列。
AliasesToExport = @()

# 要從此模組匯出的 DSC 資源
# DscResourcesToExport = @()

# 與這個模組封裝在一起之所有模組的清單。
# ModuleList = @()

# 列出與這個模組封裝在一起的所有檔案
FileList = @("QRCoder.dll")

# 要傳遞給 RootModule/ModuleToProcess 中所指定之模組的私人資料。這可能也包含具有 PowerShell 所用之其他模組中繼資料的 PSData 雜湊表。
PrivateData = @{

    PSData = @{

        # 套用至此模組的標籤。這些標籤有助在線上程式庫中探索模組。
        # Tags = @()

        # 此模組之授權的 URL。
        # LicenseUri = ''

        # 此專案之主要網站的 URL。
        # ProjectUri = ''

        # 代表此模組之圖示的 URL。
        # IconUri = ''

        # 此模組的 ReleaseNotes
        # ReleaseNotes = ''

    } # PSData 雜湊表結尾

} # PrivateData 雜湊表結尾

# 此模組的 HelpInfo URI
# HelpInfoURI = ''

# 從此模組匯出之命令的預設前置詞。使用 Import-Module -Prefix 覆寫預設前置詞。
# DefaultCommandPrefix = ''

}

填完 .psd1,用 Test-ModuleManifest .\QRCodeTools.psd1 檢查是否格式正確,就可以準備將資料夾打包成模組。

但正式對外發行前,建議先確認程式碼符合一定水準,沒犯下低級錯誤。官方有個程式碼分析工具 - PSScriptAnalyzer (用 Install-Module PSScriptAnalyzer 安裝),整理好一些 Best Practice 檢查規則,使用 Invoke-ScriptAnalyzer -Path .\QRCodeTools\ 檢查,再依指示修改,像我就得到一個警示 Missing BOM encoding for non-ASCII encoded file 'QRCodeTools.psm1',才想到 psm1 忘記存成 UTF-8 with BOM 編碼,幸好被有攔下來,沒流出去丟人現臉。:P

最後一步,執行 Publish-Module -Path .\QRCodeTools\ -Repository MyPSRepo 將模組發佈到私有 Repsitory 便大功告成了。

要使用模組的客戶端,記得要先註冊私有 Repository (方法可參考前一篇),然後執行 Install-Module QRCodeTools -Repository MyPSRepo -Scope CurrentUser,模組便會被安裝在個人 Windows 環境,任何時候在 PowerShell 命令視窗下 ConvertTo-QRCode 都可使用,小工具用起來就更順手囉~

Tutorial of how to convert .ps1 to PowerShell module step by step.


Comments

Be the first to post a comment

Post a comment