3

PowerShell 小技巧:除錯好幫手 - Write-Verbose()

 3 years ago
source link: https://blog.darkthread.net/blog/ps-write-verbose/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
PowerShell 小技巧:除錯好幫手 - Write-Verbose()-黑暗執行緒

呼叫 PowerShell 模組函式或跑 .ps1 程式難免會遇到程式跑完但結果不如預期的狀況,要找出哪裡出錯,加 Log 是最直接有效的做法。而 PowerShell 有一件好用武器,允許事先在程式偷偷埋入偵錯資訊,平時不顯示,使用者遇到問題時可打開開關再跑一次,就能藉由顯示的偵錯訊息推測發生什麼事?

這種貼心設計,能讓你的 PowerShell 模組或小工具專業形象瞬間上升,是專業 PowerShell 開發者的必備技巧之一。

事實上,幾乎所有 PowerShell 官方函式,都內建這層貼心設計。例如:我遇到一個狀況,Repository 的 PS 模組已更新到 1.1.0 版,跑完 Update-Module 有看到下載進度,但用 Get-Module 模組卻還仍是 1.0.0。於是,我加上 -Verbose 參數再重新執行一次,這回會看到比較多的資訊,包含它有找到 Repository 目錄 X:\PrivNuGet,也找到 1.1.0 版,而且因為已經安裝過了所以略過 (Skipping Installed module):(這是初學開發模組常遇到的狀況,先賣個關子,未來再另開一篇方便需要的人 Google 查詢)

要提供這種隱藏版偵錯訊息很簡單 - PowerShell 有個 Write-Verbose Cmdlet,使用方法 Write-Host、Write-Output 差不多,差別在可以透過 $VerbosePreference 控制是否顯示,$VerbosePreference 的預設值是 SilentlyContinue - 不顯示且繼續執行;修改成 Continue 就會變成顯示後繼續執行。當然,在程式裡寫死 $VerbosePreference = "Continue" 就沒意思了,PowerShell 有所謂的 CommonParameters,提供所有 Cmdlet 都通用的參數,如 -Verbose、-Information、-Debug、-ErrorAction,可在執行階段設定 $$VerbosePreference,而舉一反三,除了 Wirte-Verbose、也有 Write-Debug (-Debug 參數在互動模式會切成 Inquire 一直問你要不要繼續,有點煩)、Write-Information (彈性最大但比較複雜)。

而要接收 CommonParameters,函式或 .ps1 參數宣告要加上 [CmdletBinding()],以前幾天介紹的 Merge-ModuleScripts.ps1 為例,我加上 [CmdletBinding()] 及 Write-Verbose() 在必要時提供更多偵錯訊息:

[CmdletBinding()]
param (
    [switch][bool]
    $publish,
    [switch][bool]
    $clear,
    [string]
    $repository = "",
    [string]
    $nugetApiKey = "NoKey"
)
$ErrorActionPreference = "STOP"
if ($publish -and [string]::IsNullOrWhiteSpace($repository)) {
    Write-Host "Repository parameter missing" -ForegroundColor Red
    Exit
}
$moduleName = [System.IO.Path]::GetFileName($PSScriptRoot.TrimEnd('\'))
$psm1Name = "$moduleName.psm1"
$outputPath = "$PSScriptRoot\$moduleName";
if ($clear) { # clear temp folder
    if (Test-Path -Path $outputPath) {
        Remove-item $outputPath -Recurse
        Write-Host "$outputPath deleted"
    }
    Exit
}
# prepare temp folder
[System.IO.Directory]::CreateDirectory($moduleName) | Out-Null
Write-Verbose "Temp folder [$moduleName] ready"
$functionsToExport = @()
"# Module $moduleName" | Out-File "$outputPath\$psm1Name" -Encoding utf8
# merge all .ps1 under scripts folder to create a single module_name.psm1
Get-ChildItem -Path "$PSScriptRoot\src" -Filter *.ps1 -ErrorAction SilentlyContinue | 
Sort-Object { $_.Name } | # order by ps1 filename
ForEach-Object {
    Write-Verbose "Merging $($_.FullName)..."
    Get-Content $_.FullName | Select-String -Pattern "##MOD_EXEC## Export-ModuleMember -Function ([-_A-Za-z0-9]+)" -AllMatches |
    ForEach-Object {
        $funcName = $_.Matches.Groups[1].Value
        $functionsToExport += $funcName
        Write-Verbose " * Function <$funcName> found"
    }
    $scriptContent = Get-Content $_.FullName -Raw -Encoding utf8
    $scriptContent = $scriptContent.Replace("##MOD_EXEC## ", "")
    $scriptContent | Out-File "$outputPath\$psm1Name" -Append  -Encoding utf8
}
# copy all non-.ps1 files
Get-ChildItem -Path "$PSScriptRoot\src" | Where-Object { !$_.Name.EndsWith('.ps1') } | ForEach-Object { 
    Copy-Item -Path $_.FullName -Destination $outputPath 
    Write-Verbose " * File $($_.FullName) copied"
}
$psd1Path = "$moduleName.psd1"
if (!(Test-Path $psd1Path -PathType Leaf)) {
    Write-Verbose "Creating psd1..."
    New-ModuleManifest -Path $psd1Path -RootModule $psm1Name -Author (Read-Host "Author of module") -ModuleVersion "1.0.0" -Description (Read-Host "Description of module")
}
$psd1 = Get-Content "$moduleName.psd1" -Raw -Encoding utf8
[System.Text.RegularExpressions.Regex]::Replace($psd1, "FunctionsToExport = ([-@()A-Za-z0-9 ,`"'*]+)", 'FunctionsToExport = @("' + ($functionsToExport -join '","') + '")') | 
Out-File "$outputPath\$moduleName.psd1" -Encoding utf8
if ($publish) {
    Publish-Module -Path $outputPath -Repository $repository -NuGetApiKey $nugetApiKey
    Write-Host "$moduleName published"
    Remove-Item $outputPath -Recurse -Force
    Write-Verbose "Temp folder deleted"
}

如此,加上 -Verbose 後,可以看到系統合併哪些 .ps1、發現哪些公開函式,以及暫存目錄的建立與刪除,有利於排除問題。

大家可以善用這個小技巧,讓開發的小工具或 PowerShell 模組更貼心更順手。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK