對應 30天挑戰精通 PowerShell 該書第 19 章。
昨天介紹了基本的指令碼編寫,今天接續著 19 章後面的內容繼續給它看下去。
我已經讀完了整本書的一半以上,正如書中最初章節所提醒的,Get-Help
已成為我最常用的 cmdlet 之一。當我們為團隊編寫可重複使用的腳本時,按照規範在腳本中撰寫說明,讓使用者能透過 Get-Help
清楚了解程式的描述、目的、用法以及支援的參數,這確實是一件利人利己的事情。
PowerShell 支援基於註解的幫助(Comment-Based Help)。我們可以在腳本的開頭添加特殊格式的註解,PowerShell 會自動解析這些註解,並在使用 Get-Help
命令時顯示相應的信息。
<#
.SYNOPSIS
這是一個範例腳本的簡要說明。
.DESCRIPTION
這裡是腳本的詳細描述,解釋腳本的功能和工作原理。
.PARAMETER InputFile
輸入文件的路徑。
.PARAMETER OutputFile
輸出文件的路徑。
.EXAMPLE
.\MyScript.ps1 -InputFile "data.txt" -OutputFile "result.txt"
這是如何使用該腳本的範例。
.NOTES
作者:你的名字
日期:2023-10-01
#>
param(
[Parameter(Mandatory)]
[string]$InputFile,
[Parameter(Mandatory)]
[string]$OutputFile
)
# 腳本主體代碼
<#
.SYNOPSIS
獲取遠端或本地計算機上邏輯磁碟的大小及可用空間資訊。
.DESCRIPTION
此腳本透過 `Get-CimInstance` cmdlet 獲取指定計算機上的邏輯磁碟資訊,並篩選出硬碟驅動器 (DriveType=3)。
它將結果依據設備ID排序,並以表格格式顯示每個磁碟的設備ID、可用空間 (以MB為單位)、總大小 (以GB為單位) 以及可用空間的百分比。
腳本默認為 "server01" 作為計算機名稱,但可以更改為其他計算機。
.PARAMETER computername
目標計算機的名稱或IP地址。默認值為 "server01"。
.EXAMPLE
.\Get-DiskInfo.ps1 -computername "server01"
此命令將顯示 "server01" 計算機上的邏輯磁碟資訊。
.\Get-DiskInfo.ps1 -computername "localhost"
此命令將顯示本地計算機上的邏輯磁碟資訊。
.NOTES
作者:你的名字
日期:2023-10-07
#>
param (
$computername = 'server01'
)
Get-CimInstance -class Win32_LogicalDisk -computername $computername `
-Filter "DriveType=3" | Sort-Object -Property DeviceID |
Format-Table -Property DeviceID ,
@{Label='FreeSpace(MB)'; expression={$_.FreeSpace / 1MB -as [int]}},
@{Label='Size(GB)'; expression={$_.Size / 1GB -as [int]}},
@{Label='%Free'; expression={$_.FreeSpace / $_.Size * 100 -as [int]}}
編寫完指令碼後,嘗試用 Get-Help
查看時,卻發現中文變成亂碼了,後來發現主要是因為文件的編碼是 UTF-8
,但是需要將其編碼改成 UTF-8 with BOM
,以下是我透過 chatGPT 查詢後,針對兩者差異的描述及整理:
UTF-8
和 UTF-8 with BOM
(Byte Order Mark,位元組順序標記)之間的區別在於是否包含一個特殊的字節標記(BOM)在文件的開頭。
BOM(Byte Order Mark)是一個 Unicode 字符,用來標記文本的編碼方式。對於不同的編碼,BOM 的字節序會有所不同:
BOM 的主要目的是在某些需要區分字節順序的編碼(如 UTF-16 和 UTF-32)中,用來區分不同的字節順序(大端或小端),但在 UTF-8 中,因為它是字節無關的,所以 BOM 並不是必要的。
主要與 Windows 系統的兼容性有關係,Windows 在處理不同編碼時經常依賴 BOM 來區分不同的 Unicode 編碼格式。而如果文件沒有 BOM,系統可能會默認使用 ANSI 或其他本地語系編碼,導致無法正確顯示中文字符。通過添加 UTF-8 with BOM 編碼,這些應用可以正確識別編碼並顯示中文,而避免亂碼。
function Get-FileEncoding {
param (
[string]$FilePath
)
$stream = [System.IO.StreamReader]::new($FilePath, $true)
$encoding = $stream.CurrentEncoding
$stream.Close()
return $encoding
}
# 用法範例
$encoding = Get-FileEncoding -FilePath "C:\path\to\your\file.txt"
$encoding
可是透過上述的 function 並不能顯示出該編碼是否有包含 BOM,所以需要透過下面的 funtion 去確認。
function Get-FileEncoding {
param (
[string]$FilePath
)
$bom = Get-Content -Path $FilePath -Encoding Byte -TotalCount 3
$bomHex = ($bom | ForEach-Object { '{0:X2}' -f $_ }) -join ' '
switch ($bomHex) {
"EF BB BF" { "UTF-8 with BOM" }
"FF FE" { "UTF-16 LE" }
"FE FF" { "UTF-16 BE" }
default {
$stream = [System.IO.StreamReader]::new($FilePath, $true)
$encoding = $stream.CurrentEncoding.EncodingName
$stream.Close()
$encoding
}
}
}
# 用法範例
$encoding = Get-FileEncoding -FilePath "C:\path\to\your\file.txt"
$encoding
這樣就可以知道目前文件的編碼並未包含 BOM,因此,可以透過下面的 Function 去將既有的 script 編碼加入 BOM。
function ConvertToUtf8WithBom {
param (
[string]$FilePath
)
# 读取文件的内容
$content = Get-Content -Path $FilePath -Raw
# 使用 UTF-8 with BOM 编码将内容写回到文件
$utf8WithBom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($FilePath, $content, $utf8WithBom)
Write-Host "文件 $FilePath 已成功转换为 UTF-8 with BOM 编码。"
}
# 用法示例
ConvertToUtf8WithBom -FilePath "C:\path\to\your\file.ps1"
依據截圖,已經看到目前 script 的編碼已經轉為 UTF-8 with BOM
當然你也可以透過 notepad++ 等文字編輯器將編碼修改成 UTF-8 with BOM
書中透過了一張圖表現出當我們將多行 cmdlet 放在 script 裡去執行時的邏輯圖,我參考後重新整理了相關的邏輯,重新繪製如下:
圖示中顯示了 Get-Uptime 和 Get-Process 的執行流程。從流程圖來看,它展示了 PowerShell 如何將不同 Cmdlet 的輸出物件(Timespan 和 Process)傳送到管線,最後經由 Out-Default、Out-Host 輸出到主機應用。
透過這樣的 script 我們最終能看到的 output 為清單:
所以當 script 的功能是要透過執行後呈現想要的格式時,這部分要特別小心。
先看圖:
上圖展示了 PowerShell 中兩個不同 Cmdlet ( Get-Uptime 和 Get-Process ) 的執行順序和輸出處理過程,並將每個 Cmdlet 的輸出物件如何經過 PowerShell 的格式化系統( Out-Default 和 Out-Host )再輸出到主機應用程式(通常是 PowerShell 控制台或其他輸出介面),且這兩個 Cmdlet 的輸出不共享同一個管線,因此不會有因為物件類型不同而影響格式化的問題。
作用域是 PowerShell 某些元素類型的一種容器( container )形式,主要包含別名、變數和 Function。
Shell 本身是最上層的作用域,被稱爲「全域作用域」( global scope )。當你執行一個指令碼時,會爲該指令碼建立一個新的作用域,稱為「指令碼作用域」( script scope )。指分碼作用域是全域作用域的分支,或稱爲「子作用域」( child )。而函式也同樣擁有屬於它們自己的「私有作用域」( private scope )。
Reference - P.311
我的理解是:**作用域 **是指變數、函數、別名等項目的可見性和生存週期的範圍。簡單來說,就是在什麼地方可以存取到這些項目,以及它們存在多久。
這是整個 PowerShell 會話的最外層作用域。任何在全域作用域中定義的變數或函數,整個會話期間都可用。
這是你當前正在執行的作用域,例如一個函數或腳本區塊內部。在區域作用域中定義的變數,僅在該作用域內可見。
當你執行一個腳本時,該腳本有自己的作用域,稱為腳本作用域。腳本內定義的變數和函數不會影響到全域作用域。
使用 Private 關鍵字可以將變數或函數的可見性限制在當前作用域,防止它們被子作用域訪問。
$var = "全域作用域"
function Test-LocalScope {
$var = "區域作用域"
Write-Host "函數內:$var"
}
Test-LocalScope
Write-Host "函數外:$var"
在全域作用域(紅框)中,我們將 全域作用域 的字串賦值給 變數 var,接著我們建立一個 function - Test-LocalScope,並在 function 內( 黃框 )再次建立一個 var 並將 區域作用域 的字串賦值給 變數var,當第 8 行呼叫 Test-LocalScope 時,Write-Host 會顯示區域作用域裡面的值( 即 區域作用域 ),但黃框內的變數僅適用於黃框內,因此,當第 9 行 Write-Host 顯示 var 變數內的值時,其值為 全域作用域。
$scriptVar = "腳本作用域"
function Show-ScriptVar {
Write-Host "腳本內:$scriptVar"
}
Show-ScriptVar
$scriptVar
定義在腳本作用域中,僅在 script.ps1
中可見。$scriptVar
。因為執行該 script 的方式是透過點來源( **Dot Sourcing **),該方式會將腳本中的所有內容加載到當前作用域(通常是全域作用域)中。
Reference - Script scope and dot sourcing
$counter = 0
function IncrementCounter {
$Global:counter += 1
}
IncrementCounter
Write-Host "計數器:$counter"
$Global:
修飾符,直接修改全域作用域中的變數 counter。Reference - Scope modifiers
Day 24 - 改善你的參數化指令