用 Pester 整備 PowerShell 測試 ── 讓維運腳本不易損壞的實務做法
· 小村 豪 · PowerShell, Pester, Windows, 測試, 自動化, CI, 既有資產活用
1. 首先要掌握的事
PowerShell 腳本,最初往往是從一些小型作業的自動化開始的。
蒐集檔案。 搜尋記錄檔。 產生 CSV。 搬移舊檔案。 確認服務狀態。
這些工作只要幾十行,用眼睛看就能確認動作是否正確。但實務上持續使用之後,就會出現這些變化。
- 增加要處理的資料夾
- 加入排除條件
- 修改 CSV 的欄位
- 刪除前先封存
- 從工作排程器或 CI 執行
- 發生錯誤時要通知
到了這個階段,「曾經在自己手邊跑起來」已經不夠了。
PowerShell 的可怕之處,正好和它的方便性是一體兩面。 如果只是讀取資料,可以放心嘗試,但刪除、移動、覆寫、重啟服務、變更權限這類操作,只要條件判斷稍有失誤,就會直接演變成事故。
這時就該用上 PowerShell 的測試框架 Pester。本文不打算涵蓋 Pester 的全部功能,而是整理如何為既有的 PowerShell 腳本,在實務上進行「測試整備」,讓它們變得不容易壞掉。
PowerShell 的測試,不只是為了寫出乾淨的程式碼。它是在修改之前降低不安、在修改之後能有依據地確認結果的工具。
順帶一提,本文出現的程式碼,已作為一整套可用 Invoke-Pester 執行的範例(測試對象腳本、Pester 測試、CI 用執行腳本)公開在 GitHub 上。
pester-powershell-test-maintenance - komurasoft-blog-samples (GitHub)
2. Pester 要守護什麼
引入 Pester,並不代表一切都會自動變安全。首先該決定的是「要用測試守護什麼」。
在 PowerShell 的維運腳本中,優先確認這 4 項,通常最容易看到效果。
| 想守護的事 | 測試要看的內容 |
|---|---|
| 條件判斷 | 要以哪些檔案、行、使用者、服務為對象 |
| 輸出格式 | 輸出到 CSV 的欄位名稱、回傳值的屬性、筆數 |
| 危險操作之前 | 刪除、移動、停止的對象是否符合預期 |
| 外部依賴 | 檔案系統、API、命令執行、日期時間、環境變數的處理 |
尤其最先該測試的,不是刪除處理本身,而是挑選刪除對象的處理。
舉例來說,若是刪除舊記錄檔的腳本,不要一開始就直接測試 Remove-Item,而是先測試「哪些記錄檔會被選為對象」。
用這種切分方式,會比較容易測試。
蒐集對象的函式
↓
確認、記錄對象的處理
↓
移動、刪除、通知等變更處理
PowerShell 的測試整備,並不是要立刻對既有腳本進行大改造。首先,把危險處理之前的判斷部分切成函式,用 Pester 確認它的回傳值。
3. 先統一版本
本文以 Pester v5 為前提。
在較舊的 Windows 環境中,即使原本就內建了 Pester,也可能是 v3 系列。不要直接沿用既有環境安裝的版本,先確認版本。
Get-Module Pester -ListAvailable |
Sort-Object Version -Descending |
Select-Object Name, Version, Path
若要重新安裝,可從 PowerShell Gallery 安裝。
Install-Module -Name Pester -Scope CurrentUser -Force -SkipPublisherCheck
Import-Module Pester
Get-Module Pester
團隊使用時,要確認開發端的機器、建置伺服器、任務執行環境中的 Pester 版本是否一致。
PowerShell 測試中常見的混亂,往往不是程式碼本身的問題,而是來自測試執行器版本的差異。
尤其是舊文章或內部筆記中,可能還殘留著 Pester v4 以前的寫法。若要重新整備,建議往 v5 的寫法靠近,之後會更好維護。
4. 決定檔案的擺放位置
在 Pester 中,一般會把測試檔案命名為 *.Tests.ps1。
最小的配置如下:
scripts/
Get-OldLogFile.ps1
Get-OldLogFile.Tests.ps1
規模稍大一點時,可以把 src 和 tests 分開。
src/
public/
Get-OldLogFile.ps1
Remove-OldLogFile.ps1
tests/
public/
Get-OldLogFile.Tests.ps1
Remove-OldLogFile.Tests.ps1
兩種方式都可以,重要的是先決定好規則。
- 每個函式對應一個測試檔案
- 測試檔案名稱以
.Tests.ps1結尾 - 統一測試對象的載入方式
- 不要把單元測試和整合測試混在一起
一開始,把測試對象的 .ps1 和測試檔 .Tests.ps1 放在同一目錄下就足夠了。
5. 跑通最小的測試
先準備一個單純的函式 Get-OldLogFile.ps1。
function Get-OldLogFile {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string] $Path,
[int] $Days = 30,
[string] $Filter = '*.log',
[datetime] $Now = (Get-Date)
)
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
throw "Folder not found: $Path"
}
$limit = $Now.AddDays(-1 * $Days)
Get-ChildItem -LiteralPath $Path -Filter $Filter -File |
Where-Object { $_.LastWriteTime -lt $limit } |
Sort-Object -Property LastWriteTime |
Select-Object FullName, Name, Length, LastWriteTime
}
這裡為了方便測試,讓 $Now 可以透過參數傳入。
如果在函式內每次都直接呼叫 Get-Date,結果就會隨測試執行日而改變。只要把日期做成參數,就能把「以 2026 年 6 月 1 日為基準、超過 30 天的檔案」這種條件固定下來測試。
接著,把測試寫在 Get-OldLogFile.Tests.ps1 中。
BeforeAll {
. $PSScriptRoot\Get-OldLogFile.ps1
}
Describe 'Get-OldLogFile' {
BeforeEach {
$script:Root = Join-Path $TestDrive 'logs'
New-Item -ItemType Directory -Path $script:Root -Force | Out-Null
$oldLog = Join-Path $script:Root 'old.log'
$newLog = Join-Path $script:Root 'new.log'
$oldTxt = Join-Path $script:Root 'old.txt'
Set-Content -LiteralPath $oldLog -Value 'old log' -Encoding UTF8
Set-Content -LiteralPath $newLog -Value 'new log' -Encoding UTF8
Set-Content -LiteralPath $oldTxt -Value 'old text' -Encoding UTF8
(Get-Item -LiteralPath $oldLog).LastWriteTime = [datetime]'2026-05-01T00:00:00'
(Get-Item -LiteralPath $newLog).LastWriteTime = [datetime]'2026-05-31T00:00:00'
(Get-Item -LiteralPath $oldTxt).LastWriteTime = [datetime]'2026-05-01T00:00:00'
}
It '只回傳超過指定天數的 .log 檔案' {
$result = Get-OldLogFile `
-Path $script:Root `
-Days 30 `
-Now ([datetime]'2026-06-01T00:00:00')
$result | Should -HaveCount 1
$result[0].Name | Should -Be 'old.log'
}
It '對不存在的資料夾會失敗' {
{ Get-OldLogFile -Path (Join-Path $TestDrive 'missing') } |
Should -Throw
}
}
執行測試。
Invoke-Pester -Output Detailed .\Get-OldLogFile.Tests.ps1
這裡用到的 $TestDrive,是 Pester 提供的測試用暫存區域。不需要動用真正的 C:\Logs 或共用資料夾,就能使用只在測試中建立的檔案。對於包含檔案操作的 PowerShell 腳本,先養成使用 $TestDrive 的習慣會比較安全。
6. 把測試名稱寫成規格
Pester 的 It 裡寫的字串,不只是單純的說明,對日後閱讀的人而言,其實是一份小小的規格書。
舉例來說,這樣的命名就有點弱。
It 'works' {
# ...
}
看不出來到底該動起來的是什麼。
實務上,把條件和期望結果寫進名稱,會更容易閱讀。
It '只回傳超過指定天數的 .log 檔案' {
# ...
}
It '剛好到期日的檔案不視為對象' {
# ...
}
It '對不存在的資料夾會失敗' {
# ...
}
好的測試名稱,在失敗時特別有用。CI 的紀錄一旦出現這樣的訊息,就能馬上知道壞掉的是哪裡。
[-] Get-OldLogFile.剛好到期日的檔案不視為對象
測試名稱,其實是寫給未來的自己看的備忘。
7. 補一個邊界條件
前面的 Get-OldLogFile,是用這個條件判斷舊檔案的。
$_.LastWriteTime -lt $limit
由於用的是 -lt,和到期日剛好相同時間的檔案不會被視為對象。
這個判斷看似很小,但在實務上很重要。因為「超過 30 天」和「包含第 30 天」,結果的筆數會不一樣。
把邊界條件加進測試中。
It '剛好到期日的檔案不視為對象' {
$border = Join-Path $script:Root 'border.log'
Set-Content -LiteralPath $border -Value 'border log' -Encoding UTF8
(Get-Item -LiteralPath $border).LastWriteTime = [datetime]'2026-05-02T00:00:00'
$result = Get-OldLogFile `
-Path $script:Root `
-Days 30 `
-Now ([datetime]'2026-06-01T00:00:00')
$result.Name | Should -Not -Contain 'border.log'
}
測試不是寫得越多越好。不過,日期、數字、筆數、權限、檔案名稱模式這類具有邊界的處理,測試的價值特別高。
8. 固定回傳值的形狀
PowerShell 腳本的回傳值形狀,有時會在不知不覺間改變。
一開始是直接回傳 FileInfo。
中途加入了 Select-Object。
之後為了 CSV 又改了欄位名稱。
這類變更會影響後續處理,所以先測試回傳值的屬性,就能察覺到未預期的變化。
It '回傳後續處理需要的屬性' {
$result = Get-OldLogFile `
-Path $script:Root `
-Days 30 `
-Now ([datetime]'2026-06-01T00:00:00')
$propertyNames = $result[0].PSObject.Properties.Name
$propertyNames | Should -Contain 'FullName'
$propertyNames | Should -Contain 'Name'
$propertyNames | Should -Contain 'Length'
$propertyNames | Should -Contain 'LastWriteTime'
}
對於傳給 CSV 輸出或報表產生的函式,不只值本身,欄位名稱也是規格的一部分。
要確認的不只是「動起來了」,而是「以後續處理所期望的形狀回傳」。
9. 把刪除處理和對象選定分開
接著來看刪除處理。先看一個不好的例子。
Get-ChildItem C:\Logs -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -Force
寫法簡短又方便,但不容易測試。因為對象選定和刪除連在同一條管線裡,很難分清楚該確認哪個部分。
實務上,會這樣拆開:
function Remove-OldLogFile {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string] $Path,
[int] $Days = 30,
[datetime] $Now = (Get-Date)
)
$targets = Get-OldLogFile -Path $Path -Days $Days -Now $Now
foreach ($target in $targets) {
if ($PSCmdlet.ShouldProcess($target.FullName, 'Remove old log file')) {
Remove-Item -LiteralPath $target.FullName -Force
}
}
}
這裡加上了 SupportsShouldProcess,讓函式端可以接受 -WhatIf。
Remove-OldLogFile -Path C:\Logs -Days 30 -WhatIf
對於刪除類的 PowerShell 函式,盡可能設計成能用 -WhatIf 先行演練,會比較安全。
10. 用 Mock 取代危險操作
在 Pester 中,可以用 Mock 取代實際的命令執行。
測試刪除處理時,不需要真的執行 Remove-Item。
該呼叫的場合有沒有呼叫。 不該呼叫的場合有沒有誤呼叫。
只要看這兩點就夠了。
以下是 Remove-OldLogFile.Tests.ps1 的範例。
BeforeAll {
. $PSScriptRoot\Get-OldLogFile.ps1
. $PSScriptRoot\Remove-OldLogFile.ps1
}
Describe 'Remove-OldLogFile' {
It '對舊記錄檔呼叫 Remove-Item' {
Mock Get-OldLogFile {
[pscustomobject]@{
FullName = 'C:\Logs\old.log'
Name = 'old.log'
Length = 10
LastWriteTime = [datetime]'2026-05-01'
}
}
Mock Remove-Item {}
Remove-OldLogFile `
-Path 'C:\Logs' `
-Days 30 `
-Now ([datetime]'2026-06-01')
Should -Invoke Remove-Item `
-Times 1 `
-Exactly `
-ParameterFilter { $LiteralPath -eq 'C:\Logs\old.log' }
}
It '使用 WhatIf 時不呼叫 Remove-Item' {
Mock Get-OldLogFile {
[pscustomobject]@{
FullName = 'C:\Logs\old.log'
Name = 'old.log'
Length = 10
LastWriteTime = [datetime]'2026-05-01'
}
}
Mock Remove-Item {}
Remove-OldLogFile `
-Path 'C:\Logs' `
-Days 30 `
-Now ([datetime]'2026-06-01') `
-WhatIf
Should -Invoke Remove-Item -Times 0
}
}
這個測試中,Get-OldLogFile 和 Remove-Item 都被 Mock 掉了,所以就算真正的 C:\Logs\old.log 不存在也沒關係。要確認的是 Remove-OldLogFile 的判斷邏輯。
- 有對象時就呼叫
Remove-Item - 使用
-WhatIf時不呼叫Remove-Item - 呼叫時傳入的是預期的路徑
危險程度越高的處理,測試呼叫條件,會比測試實際執行本身更安全。
11. 不要過度使用 Mock
Mock 很方便,但用太多會讓測試失去價值。因為如果全部都用 Mock,就會離真實的 PowerShell 行為太遠。
大致的參考標準如下。
| 處理 | 建議做法 |
|---|---|
| 日期 | 用參數固定 |
| 建立檔案 | 使用 $TestDrive |
| 刪除、移動 | 用 Mock 和 -WhatIf 確認 |
| 呼叫 Web API | 對 Invoke-RestMethod 等做 Mock |
| 寄送郵件、通知 | 對發送用命令做 Mock |
| CSV 讀寫 | 在 $TestDrive 建立小型的實際檔案 |
如果連檔案讀寫都全部 Mock 掉,可能會漏掉真正的字元編碼、換行、欄位名稱之類的問題。
另一方面,刪除、通知、外部 API、停止服務這類處理,最好真的不要去執行。
要把「使用真實物件的地方」和「Mock 的地方」分開。
12. 把既有腳本改成容易測試的樣子
引入 Pester 後,既有腳本的寫法也會稍微改變。不過一開始不需要進行大規模的設計變更,先做到這個程度就足夠了。
修改前
$limit = (Get-Date).AddDays(-30)
Get-ChildItem C:\Logs -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt $limit } |
Remove-Item -Force
修改後
function Get-OldLogFile {
param(
[string] $Path,
[int] $Days = 30,
[datetime] $Now = (Get-Date)
)
$limit = $Now.AddDays(-1 * $Days)
Get-ChildItem -LiteralPath $Path -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt $limit }
}
function Remove-OldLogFile {
[CmdletBinding(SupportsShouldProcess)]
param(
[string] $Path,
[int] $Days = 30,
[datetime] $Now = (Get-Date)
)
Get-OldLogFile -Path $Path -Days $Days -Now $Now |
ForEach-Object {
if ($PSCmdlet.ShouldProcess($_.FullName, 'Remove old log file')) {
Remove-Item -LiteralPath $_.FullName -Force
}
}
}
改動的地方並不大。
- 把日期改成參數
- 把對象選定變成函式
- 把刪除處理拆成另一個函式
- 加上
SupportsShouldProcess
光是這樣,就會變得容易測試許多。
在 PowerShell 的測試整備中,與其從設計論談起,把「日期」「路徑」「外部命令」「變更操作」變成可以從外部替換,會更有效果。
13. 決定測試的分類
在 Pester 中,可以為 Describe、Context、It 加上標籤。
例如,把快速的單元測試和真正接觸環境的整合測試分開。
Describe 'Get-OldLogFile' -Tag 'Unit' {
It '只回傳超過指定天數的 .log 檔案' {
# 使用 TestDrive 的快速測試
}
}
Describe 'Log maintenance smoke test' -Tag 'Smoke' {
It '可以讀取真正的記錄檔資料夾' {
Test-Path -LiteralPath 'C:\Logs' | Should -BeTrue
}
}
只執行單元測試。
Invoke-Pester -TagFilter Unit
排除較慢或依賴環境的測試。
Invoke-Pester -ExcludeTagFilter Slow, RequiresAdmin, Network
現場實務中,如果每次都想跑完所有測試,有時會難以持續。
先把快速又沒有副作用的測試設為標準,再用標籤把依賴環境的測試分開,在需要的時機再執行。
14. 在 CI 上執行
Pester 就算只在本機執行也很有幫助,但若要以團隊的方式管理腳本,能在 CI 上執行會更讓人安心。
例如,可以準備一個像 tools/Invoke-ProjectTests.ps1 這樣的檔案。
$ErrorActionPreference = 'Stop'
Import-Module Pester
$config = New-PesterConfiguration
$config.Run.Path = @(
Join-Path $PSScriptRoot '..\tests'
)
$config.Run.Exit = $true
$config.Output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'JUnitXml'
$config.TestResult.OutputPath = Join-Path $PSScriptRoot '..\test-results.xml'
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = @(
Join-Path $PSScriptRoot '..\src'
)
$config.CodeCoverage.OutputPath = Join-Path $PSScriptRoot '..\coverage.xml'
Invoke-Pester -Configuration $config
在 CI 端,執行這個腳本。
pwsh -NoProfile -File .\tools\Invoke-ProjectTests.ps1
重點是,不要把 CI 專屬的設定寫得太深入到測試檔案裡。
測試檔案是用來寫規格的地方。 CI 用的輸出格式、覆蓋率、結束代碼等,整理到執行用腳本裡會比較清爽。
15. 把覆蓋率當成地圖,而不是目標
Pester 也能輸出程式碼覆蓋率。不過,最好不要一開始就過度追求數字。因為覆蓋率本身並不等於測試的品質。
舉例來說,只要呼叫一次刪除對象的抽取函式,那一行就算是通過了。但如果邊界條件或排除條件沒有被確認過,並不會帶來實務上的安心感。
覆蓋率可以這樣使用。
- 找出完全沒有被執行過的函式
- 找出重要卻沒有測試的分支
- 依變更頻率高的腳本排出優先順序
- 在 CI 中留下測試執行的證據
重點不在於把數字拉高,而是要看「重要的判斷有沒有被測試到」。
16. 導入既有腳本的順序
要為既有的 PowerShell 資產導入 Pester 時,不要一開始就把整體都納入測試範圍,會比較容易推進。以下列出建議的順序。
1. 挑選失敗會造成困擾的腳本
一開始,適合挑選這類腳本。
- 包含刪除、移動、覆寫
- 每天或每月都會執行
- 寫在操作手冊上,但實際上依賴特定人員
- 過去曾發生過條件判斷失誤
- 輸出的 CSV 會被其他業務使用
從方便但壞掉會很麻煩的腳本開始。
2. 先把讀取部分切出來變成函式
一開始要測試的,不是變更處理,而是讀取處理。
讀取記錄檔
篩選對象
計算筆數
整理成 CSV 用的格式
這部分容易用 $TestDrive 測試,也不容易發生事故。
3. 從外部傳入日期和路徑
如果日期或路徑是寫死的,就很難測試。
# 想避免的寫法
$root = 'C:\Logs'
$limit = (Get-Date).AddDays(-30)
容易測試的寫法。
param(
[string] $Path,
[datetime] $Now = (Get-Date)
)
只要能從外部傳入值,測試的穩定性就會大幅提升。
4. 把危險操作放在最後
刪除、移動這類操作,統一放在最後處理。
建立對象
↓
把對象記錄下來
↓
用 -WhatIf 先行確認
↓
真正執行
測試時也依照相同的順序確認。
17. 常見的卡關情況
| 症狀 | 原因 | 對策 |
|---|---|---|
| 本機能通過但 CI 失敗 | 目前的工作目錄不同 | 以 $PSScriptRoot 為基準 |
| 結果隨每天不同而改變 | 直接使用了 Get-Date |
準備類似 -Now 的參數 |
| 測試差點刪掉真實檔案 | 使用了真實資料夾 | 使用 $TestDrive 和 Mock |
| Mock 沒有生效 | 模組邊界或作用範圍不同 | 檢查 -ModuleName 或載入方式 |
| 不知道該測到什麼程度 | 規格沒有拆分到函式裡 | 拆成對象選定、整形、變更處理 |
| 測試很慢 | 碰到了外部服務或網路 | 單元測試中對外部依賴做 Mock |
| 看測試名稱看不出內容 | 名稱寫成 It 'works' 這種形式 |
把條件和期望結果寫進名稱 |
表面上看起來是 Pester 的問題,實際上很多時候是腳本結構本身造成的。
不容易測試的部分,通常在維運上也是容易壞掉的部分。
18. 測試整備中要先定好的規則
若要以團隊方式管理 PowerShell 腳本,比起細部寫法,更該優先決定規則。
例如可以訂出這類規則。
- 測試檔案統一命名為
*.Tests.ps1 - 測試對象一律從
$PSScriptRoot載入 - 檔案操作的測試使用
$TestDrive - 刪除、移動、通知、API 呼叫原則上都做
Mock - 日期做成參數,讓它可以被固定
- 在
Describe或It加上Unit、Smoke、RequiresAdmin等標籤 - CI 中以
Unit為標準執行 - 對變更類函式盡可能加上
SupportsShouldProcess - 把過去的不良狀況留下來作為回歸測試
規則太多反而守不住。一開始,光靠這 3 條就足夠了。
使用 TestDrive
固定日期
危險操作做 Mock
只要守住這 3 條,PowerShell 的測試就會相當穩定。
19. 也要決定哪些不測試
在測試整備中,「要測試什麼」和「不測試什麼」同樣重要。
舉例來說,這些內容不需要在單元測試中過度確認。
- Windows 本身的
Get-ChildItem是否正常運作 Remove-Item是否真的會刪除檔案- PowerShell 標準命令的內部規格
- 外部 API 是否永遠有回應
- 網路共用資料夾是否永遠可用
該測試的,是自己團隊的判斷邏輯。
- 用什麼條件決定對象
- 傳入哪個路徑
- 輸出哪些欄位
- 失敗時該怎麼處理
- 危險操作能不能先行演練
把「相信標準命令的部分」和「守護自己邏輯的部分」分開。
20. 總結
PowerShell 是能快速自動化日常工作的方便工具。但在實務上長期使用的腳本,責任會一點一點變重。一開始只是自己用的單行命令,漸漸變成每天執行的維運處理,最後開始影響其他人的工作和業務資料。
用 Pester 進行測試整備,就是配合這種變化來守護腳本的工作。
把要點列出來。
- 先測試對象選定
- 讓日期和路徑可以從外部傳入
- 把檔案操作封閉在
$TestDrive裡 - 對刪除、移動、通知、API 呼叫做
Mock - 讓變更類函式可以用
-WhatIf先行演練 - 讓測試名稱可以當成規格來讀
- 在 CI 中先跑快速又沒有副作用的測試
PowerShell 的安全維運,不是要一次導入龐大的機制。
拆成小函式。 寫小測試。 在危險處理之前,先確保能夠確認狀況。
累積這些做法,PowerShell 就能從「方便但有點可怕的腳本」,逐漸變成「改了也能確認的業務工具」。
參考連結
- 本文的完整範例程式碼(測試對象腳本、Pester 測試、CI 用執行腳本) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/pester-powershell-test-maintenance
- Pester Quick Start
- Pester Installation and Update
- Pester File placement and naming
- Pester TestDrive
- Pester Mocking
- Pester Tags
- Pester Configuration
- Pester Test Results
- Pester Code Coverage
- PowerShell Gallery: Pester
- PowerShell Documentation - Microsoft Learn
相關文章
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
在 C#(CSharp)中執行 PowerShell 並以物件形式接收結果
本文從實務角度整理如何從 C# 啟動 PowerShell,並以 PSObject 而非字串來接收結果,涵蓋 PowerShell SDK、AddCommand、AddParameter、BaseObject、Properties 到錯誤處理。
PowerShell 實用指令集錦 ── 累積日常工作常用的小工具
本文整理 PowerShell 日常工作中常用的實用指令,說明 Measure-Object、Group-Object、Select-String、Compare-Object、Tee-Object、Start-Transcript 等指令的使用場景與時機。
PowerShell 腳本應用 ── 安全地自動化日誌調查、封存與報表化
本文整理用 PowerShell 腳本安全地推進日誌調查、CSV 報表、舊日誌封存、留存紀錄,一直到工作排程器定期執行的實務步驟。
PowerShell 指令基礎 ── 該先學會的操作與安全使用方式
為了讓 PowerShell 初學者在實務上不會迷路,本文整理了如何尋找 Cmdlet、管線、檔案操作、CSV 處理、執行原則,以及避免事故的確認步驟。
為 VBScript 停用做準備:VBA・企業內部工具盤點指南
本文整理 VBScript 分階段停用前,VBA、Excel 巨集與企業內部工具的資產盤點、靜態偵測、執行紀錄蒐集、替代技術選型、測試與分階段導入的做法。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
既有資產活用 & 遷移支援
在持續活用 COM / ActiveX / OCX 資產、原生程式碼與 32 位元相依的同時,協助規劃階段性的遷移。
常見問題
整理諮詢這個主題時常見的問題。
- 使用 Pester 時,最先該測試什麼?
- 不是刪除處理本身,而是從挑選刪除對象的處理(對象選定)開始測試。維運腳本中,優先確認「條件判斷」「輸出格式」「危險操作之前」「外部依賴」這 4 項,通常最容易看到效果。不需要一開始就大幅改造既有腳本,只要把危險處理之前的判斷部分切成函式,先用 Pester 確認它的回傳值即可。
- Pester 的 TestDrive 是什麼?
- `$TestDrive` 是 Pester 提供的測試用暫存區域。不需要動用真正的 `C:\Logs` 或共用資料夾,只用測試中建立的檔案就能驗證檔案操作。在包含檔案操作的 PowerShell 腳本測試中,先養成使用 `$TestDrive` 的習慣,就能避免不小心刪掉真實檔案這類事故。
- Pester 的 Mock 該用到什麼程度?
- 刪除、移動、呼叫 Web API、寄送郵件、發送通知這類不該真正執行的處理,適合用 Mock 取代;日期則用參數固定;檔案建立和 CSV 讀寫,比較適合在 `$TestDrive` 建立小型的實際檔案來處理。如果全部都用 Mock,會離真實的 PowerShell 行為太遠,可能會漏掉字元編碼、換行、欄位名稱之類的問題,因此重點在於把「使用真實物件的地方」和「Mock 的地方」分開。
- 為什麼在本機能通過的 Pester 測試,在 CI 上卻會失敗?
- 常見原因是目前工作目錄不同,把測試對象的載入基準改成 `$PSScriptRoot` 就能解決。如果結果每天都不一樣,通常是因為直接使用了 `Get-Date`,可以用類似 `-Now` 的參數把日期固定下來。如果 Mock 不生效,要懷疑是模組邊界或作用範圍不同,檢查 `-ModuleName` 或載入方式。表面看起來是 Pester 的問題,實際上很多時候是腳本結構的問題。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。