Day 25,我們加入了:
Pester
讓自己的 PowerShell Function 可以被測試。
Day 26,我們又把整個:
SysAdmin Automation Toolkit
放進:
Git
現在開發流程已經變成:
修改 PowerShell
↓
git diff
↓
Invoke-Pester
↓
Tests Passed
↓
git add
↓
git commit
但目前還有一個問題:
Pester 還是要靠工程師自己記得執行。
假設今天我:
修改 Get-HealthEventType
↓
測試了一下
↓
覺得應該沒問題
↓
忘記 Invoke-Pester
↓
直接 Commit / Push
Git 並不會阻止我。
甚至:
20 個 Test
其中 3 個已經壞掉
只要我沒有執行測試,就可能完全不知道。
所以 Day 27 要把:
Git
+
Pester
真正串起來。
變成:
Continuous Integration
也就是:
Code Change
↓
Git Push
↓
CI 自動啟動
↓
載入 PowerShell Module
↓
檢查 Manifest
↓
跑 Pester
↓
┌────────────────┐
│ Tests Passed ? │
└───────┬────────┘
│
┌────┴────┐
↓ ↓
Pass Fail
↓ ↓
下一步 Pipeline Stop
今天真正要解決的是:
不要依靠「我記得要測試」,而是讓流程本身強迫 Code 經過測試。
先釐清:Task Scheduler 跟 CI 不一樣
Day 20 我們已經使用過:
Task Scheduler
所以可能會有一個問題:
Task Scheduler 不是也可以自動跑 PowerShell 嗎?
可以。
但兩者目的不同。
Task Scheduler
主要回答:
什麼時間執行維運工作?
例如:
每天 06:00
↓
Server Health Check
每天 07:00
↓
AD Inactive User Report
也就是:
Schedule
→ Operations Automation
CI
主要回答:
Code 被修改之後,這份 Code 還能不能用?
例如:
Developer
↓
修改 SysAdminToolkit
↓
Git Push
↓
CI
↓
Pester
↓
Pass / Fail
也就是:
Code Change
→ Validation
所以可以簡單分成:
工具 主要目的
Task Scheduler 定時執行維運工作
CI 驗證 Automation Code
Pester 執行 PowerShell Test
Git 保存 Code 與修改歷史
它們不是互相取代,而是可以組合。
CI 到底是什麼?
CI 全名:
Continuous Integration
翻成:
持續整合。
名字看起來很大,但放到我們這個專案裡其實很好理解。
以前:
Ted 修改 Code
↓
Ted 自己測試
↓
Ted 覺得沒問題
現在變成:
Ted 修改 Code
↓
Push Git
↓
CI Server
↓
自動取得最新版 Code
↓
自動執行 Test
↓
產生結果
所以 CI 的其中一個核心目的就是:
讓每一次 Code Change 都有一致、自動化的驗證流程。
我們目前有哪些東西可以放進 CI?
現在 SysAdmin Toolkit 已經有:
SysAdmin-Automation/
│
├── Modules/
│ └── SysAdminToolkit/
│ ├── SysAdminToolkit.psd1
│ ├── SysAdminToolkit.psm1
│ ├── Public/
│ └── Private/
│
├── Scripts/
│
├── Config/
│
├── Tests/
│ ├── Module.Tests.ps1
│ ├── Get-HealthEventType.Tests.ps1
│ ├── Write-Log.Tests.ps1
│ └── Save-HealthState.Tests.ps1
│
└── README.md
所以 CI 很適合做:
Repository 可以讀嗎?
↓
Module Manifest 正常嗎?
↓
Module 可以 Import 嗎?
↓
Public Commands 存在嗎?
↓
Pester Tests 全部通過嗎?
這些都不需要連 Production。
CI 最好不要直接拿 Production 當測試環境
例如:
Get-ServerHealth
裡面可能有:
Test-WSMan
Invoke-Command
而 AD Automation 更可能有:
New-ADUser
Disable-ADAccount
Remove-ADGroupMember
CI 不應該變成:
Git Push
↓
Pester
↓
直接連 Production AD
↓
建立測試帳號
↓
停用使用者
這會很危險。
所以在 CI 裡我們主要執行:
Unit Tests
Smoke Tests
Module Validation
真的要測:
AD
WinRM
SMTP
應該使用:
Lab
Test Domain
Test OU
Test Server
Integration Environment
並另外控制執行條件。
先建立一支 CI 用的 Test Script
為了讓:
Jenkins
GitHub Actions
甚至未來其他 CI
都可以用同一個入口,
我會先建立:
Scripts/
└── Invoke-CITests.ps1
內容:
$ErrorActionPreference =
"Stop"
$ProjectRoot =
Split-Path -Path $PSScriptRoot
-Parent
$ModuleManifest =
Join-Path $ProjectRoot
"Modules\SysAdminToolkit\SysAdminToolkit.psd1"
$TestPath =
Join-Path $ProjectRoot
"Tests"
Write-Host ""
Write-Host "===== CI Validation Started ====="
Write-Host ""
try {
Write-Host "Validating module manifest..."
Test-ModuleManifest `
-Path $ModuleManifest `
-ErrorAction Stop |
Out-Null
Write-Host "Module manifest: PASS"
}
catch {
Write-Host "Module manifest: FAIL"
Write-Error `
$_.Exception.Message
exit 1
}
try {
Write-Host "Importing SysAdminToolkit..."
Import-Module `
$ModuleManifest `
-Force `
-ErrorAction Stop
Write-Host "Module import: PASS"
}
catch {
Write-Host "Module import: FAIL"
Write-Error `
$_.Exception.Message
exit 1
}
Write-Host ""
Write-Host "Running Pester tests..."
Write-Host ""
$TestResult =
Invoke-Pester -Path $TestPath
-Output Detailed `
-PassThru
Write-Host ""
Write-Host "===== Test Summary ====="
Write-Host `
"Total : $($TestResult.TotalCount)"
Write-Host `
"Passed : $($TestResult.PassedCount)"
Write-Host `
"Failed : $($TestResult.FailedCount)"
Write-Host `
"Skipped: $($TestResult.SkippedCount)"
if (
$TestResult.FailedCount -gt 0
) {
Write-Host ""
Write-Host "CI Result: FAILED"
exit 1
}
Write-Host ""
Write-Host "CI Result: PASSED"
exit 0
為什麼最後一定要 exit 1?
這裡非常重要。
假設:
Pester
20 Tests
18 Passed
2 Failed
如果 Script 最後仍然:
exit 0
CI 可能會認為:
Pipeline Success
這跟 Day 20 的 Task Scheduler 是同樣概念。
Exit Code 再次派上用場
我們前面定義:
0
→ Success
1
→ Failed
現在 CI 可以直接利用。
Invoke-CITests.ps1
↓
Pester
↓
exit 0
↓
CI = Pass
如果:
Test Failed
↓
exit 1
↓
CI = Fail
所以:
PowerShell Process 的 Exit Code,就是 PowerShell 跟 CI 系統溝通的一種方式。
先在本機跑 CI Script
正式放到 CI Server 前:
.\Scripts\Invoke-CITests.ps1
假設全部正常:
===== CI Validation Started =====
Module manifest: PASS
Module import: PASS
Running Pester tests...
Tests Passed: 18
Tests Failed: 0
CI Result: PASSED
然後:
$LASTEXITCODE
應該:
0
故意弄壞一個 Test
例如把:
Get-HealthEventType
修改錯誤。
Pester:
Passed : 17
Failed : 1
CI Result: FAILED
最後:
Exit Code = 1
這就是 CI 最基本的:
Quality Gate
也就是:
測試沒有通過,就不要假裝這份 Code 可以進入下一步。
第一種 CI:Jenkins
我們之前談 CI/CD 時,提過 Jenkins。
Jenkins 很適合:
企業內網
Windows Server
Internal Git
Self-hosted Environment
尤其我們這個系列主要是:
Windows Server
Active Directory
PowerShell
所以企業裡用 Jenkins 搭配 Windows Agent,是很合理的架構。
Jenkins 架構可以想成
Git Repository
↓
Jenkins
↓
Windows Agent
↓
PowerShell
↓
Pester
↓
Pass / Fail
Jenkins 本身不是幫我們測 PowerShell。
真正執行測試的仍然是:
PowerShell
+
Pester
Jenkins 負責:
什麼時候啟動這個流程、流程分幾個 Stage、結果如何顯示。
最簡單 Jenkins Pipeline
Repository Root 可以建立:
Jenkinsfile
例如:
pipeline {
agent {
label 'windows'
}
stages {
stage('Environment') {
steps {
bat '''
powershell.exe -NoProfile -Command "$PSVersionTable.PSVersion"
'''
}
}
stage('Test') {
steps {
bat '''
powershell.exe -NoProfile -NonInteractive -File ".\\Scripts\\Invoke-CITests.ps1"
'''
}
}
}
post {
success {
echo 'PowerShell CI passed.'
}
failure {
echo 'PowerShell CI failed.'
}
}
}
Jenkins Pipeline 在做什麼?
第一個:
Environment
確認 PowerShell Environment。
第二個:
Test
執行:
Invoke-CITests.ps1
如果:
Exit 0
Jenkins:
Success
如果:
Exit 1
Jenkins:
Failed
這樣就串起來了。
Windows Agent 為什麼重要?
如果 Jenkins Server 本身是 Linux,
但是我們的 Automation 依賴:
Windows PowerShell 5.1
ActiveDirectory Module
Windows-specific Behavior
不能假設 Linux Agent 可以完全模擬。
所以可以:
Jenkins Controller
↓
Windows Build Agent
↓
PowerShell 5.1
↓
Pester
這就是:
選擇符合目標環境的 Runner / Agent。
CI Environment 要明確
Day 20 我們已經遇過:
手動執行成功
Task Scheduler 失敗
因為 Execution Context 不同。
CI 也一樣。
你的電腦可能:
PowerShell 5.1
Pester 5.x
AD Module
CI Agent 可能:
PowerShell 7
Pester 不存在
AD Module 不存在
然後 Test 直接爆掉。
所以 CI 第一個要確定:
OS
PowerShell Version
Pester Version
Required Modules
把 Environment 輸出
例如 CI Script 開頭:
Write-Host `
"Computer: $env:COMPUTERNAME"
Write-Host `
"PowerShell: $($PSVersionTable.PSVersion)"
$PesterModule =
Get-Module -ListAvailable
Pester |
Sort-Object Version -Descending |
Select-Object -First 1
Write-Host `
"Pester: $($PesterModule.Version)"
Build Log 就會有:
Computer : CI-WIN01
PowerShell : 5.1...
Pester : 5.x
Troubleshooting 方便很多。
CI Agent 不應該使用 Domain Admin
這點跟 Task Scheduler 一樣重要。
假設 Jenkins Agent 的 Service Account:
CONTOSO\jenkins
不要因為方便就:
加入 Domain Admins
Unit Test 通常根本不需要 Domain Admin。
CI 的核心工作只是:
Checkout Code
Read Files
Import Module
Execute Tests
Write Test Result
所以仍然應該:
Least Privilege。
CI 跟 Integration Environment 要分清楚
如果哪天真的要:
測 Get-ADUser
測 WinRM
測 Test Server
可以建立:
Integration Test
例如:
CI Agent
↓
TEST-DC01
TEST-SERVER01
而不是:
CI Agent
↓
Production Domain Controller
第二種 CI:GitHub Actions
如果 Repository 在 GitHub,
可以使用:
GitHub Actions
Repository 加:
.github/
└── workflows/
└── powershell-ci.yml
例如:
name: PowerShell CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Show PowerShell version
shell: powershell
run: |
$PSVersionTable
- name: Check Pester
shell: powershell
run: |
Get-Module -ListAvailable Pester |
Sort-Object Version -Descending |
Select-Object -First 1
- name: Run PowerShell CI tests
shell: powershell
run: |
.\Scripts\Invoke-CITests.ps1
Workflow 在什麼時候跑?
這裡:
on:
push:
表示:
Push
↓
Run CI
另外:
pull_request:
表示有人準備把修改合進:
main
時也跑一次。
所以流程可以是:
feature branch
↓
Pull Request
↓
CI
↓
Pester
↓
Passed?
↓
Review
↓
Merge main
這比:
直接 main 修改
更容易控制。
PowerShell 5.1 和 PowerShell 7 要特別注意
我們目前這個系列主要以:
Windows PowerShell 5.1
為基礎。
但很多新的 CI Environment 很常使用:
PowerShell 7
pwsh
兩者並不是完全一樣。
尤其像:
Windows-specific Module
Legacy Module
ActiveDirectory
可能會有不同的相容性行為。
如果我們要明確測:
Windows PowerShell 5.1
CI 也應該明確使用對應環境。
而不是:
我的 Production 是 5.1,但 CI 只測 PowerShell 7,然後假設一定沒問題。
更成熟可以做 Matrix Test
假設未來 Toolkit 同時宣稱支援:
Windows PowerShell 5.1
+
PowerShell 7
可以:
CI
├── Test on Windows PowerShell 5.1
└── Test on PowerShell 7
兩邊都 Pass,才代表:
至少目前測試涵蓋的功能,在兩個 Runtime 都正常。
但目前我們先把目標鎖定一個環境,不需要一次把事情做得太複雜。
CI 第一個 Stage:Validate Module
除了 Pester,我會先:
Test-ModuleManifest
為什麼?
因為如果 Module 根本:
psd1 壞掉
RootModule 找不到
Version 格式錯誤
沒有必要繼續跑所有測試。
流程:
Manifest Validation
↓
成功
↓
Import Module
↓
成功
↓
Pester
第二個 Stage:Smoke Test
例如:
$RequiredCommands = @(
"Get-ServerHealth"
"Get-HealthEventType"
"Write-Log"
"Save-HealthState"
)
然後:
foreach ($Command in $RequiredCommands) {
$Result =
Get-Command `
$Command `
-Module SysAdminToolkit `
-ErrorAction SilentlyContinue
if ($null -eq $Result) {
throw `
"Required command missing: $Command"
}
}
這就是:
Module 基本功能存在嗎?
第三個 Stage:Pester
再:
$Result =
Invoke-Pester -Path $TestPath
-Output Detailed `
-PassThru
如果:
$Result.FailedCount -gt 0
直接:
exit 1
CI Pipeline 可以想成多道關卡
┌──────────────────────────┐
│ Stage 1 │
│ Module Manifest │
└────────────┬─────────────┘
↓
PASS
↓
┌──────────────────────────┐
│ Stage 2 │
│ Module Import / Smoke │
└────────────┬─────────────┘
↓
PASS
↓
┌──────────────────────────┐
│ Stage 3 │
│ Pester Tests │
└────────────┬─────────────┘
↓
PASS
↓
CI SUCCESS
任何一層:
FAIL
就停止。
測試失敗不要繼續 Deploy
例如:
Pester
Passed = 27
Failed = 2
這時:
CI = Failed
後面如果未來加入:
Package
Deploy
Release
都不應該繼續。
這就是:
Quality Gate
為什麼叫 Gate?
可以想像:
Code
↓
┌─────────┐
│ Gate │
└────┬────┘
│
Pass?
┌───┴───┐
↓ ↓
Yes No
↓ ↓
Go Stop
Pester 就可以成為其中一道 Gate。
不只是測試通過,也可以要求 Coverage
更成熟的專案還可能看:
Code Coverage
例如:
1000 行 Code
Pester 執行期間
有 700 行被測試走到
大概:
Coverage = 70%
但 Coverage 高:
不代表測試一定好。
例如:
100% Coverage
也可能只是每一行有跑過,但根本沒有正確 Assertion。
所以現階段:
先把重要 Business Logic 建立 Tests,比追求漂亮的 Coverage 數字更重要。
哪些 Function 最值得優先放 Quality Gate?
以我們目前 Toolkit 來說:
Get-HealthEventType
非常適合。
因為它決定:
NewAlert
Recovery
Escalated
VisibilityLost
如果這個 Function 壞掉,
後面的 Notification 都可能錯。
Save-HealthState
也很重要。
如果 State 寫壞:
Alert 判斷
可能跟著失效。
Write-Log
雖然不是核心 Business Logic,
但如果所有 Automation 都依賴它,也值得有基本測試。
Get-ServerHealth 怎麼辦?
這個 Function 比較麻煩。
因為有:
WinRM
Remote Server
CIM
我們可以先:
Unit Test
→ Mock 外部 Command
再建立:
Integration Test
→ TEST-SERVER01
而不要把 Production Server 當 CI 的必要條件。
CI Fail 是好事,不是壞事
假設 Git Push 後:
❌ CI Failed
第一反應可能:
又出問題了。
但其實:
CI 在合併或部署以前幫我們發現問題,正是它應該做的事情。
最糟的是:
CI 綠燈
↓
Production 爆掉
或者:
根本沒有 CI
↓
Production 才第一次測試
把 CI 當成第二個 Reviewer
假設:
你
是第一個 Reviewer。
你檢查:
Code
Diff
Logic
CI 則像另一個 Reviewer:
Manifest 能不能讀?
Module 能不能 Import?
Test 有沒有 Pass?
兩者一起降低錯誤風險。
Pull Request 的價值開始出現
Day 26 我們使用:
feature branch
例如:
git switch -c feature/new-alert
修改:
Get-HealthEventType
Push 後:
Pull Request
↓
CI
↓
Tests
↓
Pass
這時 Reviewer 可以看:
Code Diff
+
CI Result
再決定要不要 Merge。
一個比較完整的 Git Workflow
main
│
│
├──── feature/state-improvement
│ │
│ Modify Code
│ ↓
│ Pester Local
│ ↓
│ Git Commit
│ ↓
│ Git Push
│ ↓
│ Pull Request
│ ↓
│ CI Pipeline
│ ↓
│ ┌───────┴────────┐
│ ↓ ↓
│ PASS FAIL
│ ↓ ↓
│ Review Fix Code
│ ↓
└── Merge
這就很接近真正團隊的開發流程。
CI 不要保存 Production Secret
Git Repository 不應該有:
AD Password
SMTP Password
API Token
Certificate Private Key
CI Pipeline 裡也不要:
run: |
$password = "Company123!"
如果未來 CI 真正需要:
API Credential
Test Environment Credential
應使用 CI 平台提供的:
Secret / Credential Store
並遵守公司權限政策。
Build Log 也要避免把 Secret 印出來
例如不要:
Write-Host `
"Password = $Password"
即使 Secret 本身沒有寫進 Git,
如果跑 CI 時:
Console Output
把它印出來,
還是可能造成曝光。
CI 的 Repository 權限也很重要
假設:
Jenkins
可以讀:
SysAdmin Automation Repo
這個 Repository 裡可能包含:
AD Logic
Internal Group Naming
Server Logic
Automation Workflow
所以:
誰可以 Read?
誰可以 Push?
誰可以 Merge main?
都值得控制。
Automation Code 已經是一種:
Infrastructure Asset。
不要讓 CI 自動執行任何 PR 裡的高權限 Code
這是一個進階但很重要的概念。
假設 CI Agent 本身可以:
連 Production AD
控制 Windows Server
有人只要修改 Repository:
Invoke-Command ...
再 Push,
CI 就幫忙執行。
這會造成非常大的風險。
所以比較安全的架構是:
CI Agent
→ Low Privilege
→ Unit Test
Deployment / Production Automation
→ 另外的受控 Identity
→ Approval
Domain Admin Automation Server
CI 跟 CD 又差在哪?
我們之前談過:
CI
主要是:
Code
↓
Build / Validate / Test
而:
CD
開始處理:
測試通過
↓
交付 / 部署
放到 SysAdminToolkit:
CI
Git Push
↓
Test-ModuleManifest
↓
Pester
↓
Pass
CD
可能:
Pass
↓
Package SysAdminToolkit
↓
部署到 Automation Server
↓
更新 Module
↓
Smoke Test
Day 27 今天:
先把 CI 做好,不急著自動 Deploy Production。
為什麼我不急著做全自動 CD?
因為我們的 Automation 已經會接觸:
Windows Server
AD
Account Lifecycle
Notification
這跟單純部署靜態網頁不一樣。
正式環境是否可以:
Test Pass
↓
Auto Deploy
要看:
Change Policy
Approval
Maintenance Window
Risk Level
Rollback Plan
所以 Automation 程度不是越高越好。
而是:
適合自動化的地方自動化,需要人判斷的地方保留 Gate。
建立 CI 專用資料夾也可以
如果未來不同平台很多,可以:
SysAdmin-Automation/
│
├── .github/
│ └── workflows/
│ └── powershell-ci.yml
│
├── Jenkinsfile
│
├── Modules/
├── Scripts/
├── Tests/
├── Config/
└── README.md
這時 Repository 本身就完整描述:
Code
Tests
CI Pipeline
Documentation
README 也要加入 CI 使用方式
例如:
Run locally:
.\Scripts\Invoke-CITests.ps1
The validation includes:
1. Module manifest validation
2. Module import test
3. Public command smoke test
4. Pester unit tests
Exit code:
- 0: Pass
- 1: Fail
這樣新接手的人知道:
> 修改完應該怎麼驗證。
---
# 可以在 README 放 CI Badge 嗎?
如果是 GitHub 等平台,未來可以放:
```text
PowerShell CI
Passing
讓 Repository 首頁就知道:
目前最新版本測試是不是正常。
這不是必要功能,但對專案可視性很方便。
今天完整 CI Validation Script 再整理一次
到這裡,我會讓:
Scripts/Invoke-CITests.ps1
至少包含:
1. Environment Info
2. Manifest Validation
3. Module Import
4. Public Command Smoke Test
5. Pester
6. Exit Code
完整結構:
# ==========================================
# SysAdminToolkit CI
# ==========================================
$ErrorActionPreference =
"Stop"
$ProjectRoot =
Split-Path `
$PSScriptRoot `
-Parent
$Manifest =
Join-Path `
$ProjectRoot `
"Modules\SysAdminToolkit\SysAdminToolkit.psd1"
$Tests =
Join-Path `
$ProjectRoot `
"Tests"
# ==========================================
# Environment
# ==========================================
Write-Host "===== Environment ====="
Write-Host `
"Computer: $env:COMPUTERNAME"
Write-Host `
"PowerShell: $($PSVersionTable.PSVersion)"
$Pester =
Get-Module `
-ListAvailable `
Pester |
Sort-Object Version -Descending |
Select-Object -First 1
if ($null -eq $Pester) {
Write-Error `
"Pester is not installed."
exit 1
}
Write-Host `
"Pester: $($Pester.Version)"
# ==========================================
# Manifest
# ==========================================
Write-Host ""
Write-Host "===== Manifest ====="
try {
Test-ModuleManifest `
-Path $Manifest `
-ErrorAction Stop |
Out-Null
Write-Host `
"Manifest validation: PASS"
}
catch {
Write-Host `
"Manifest validation: FAIL"
Write-Error `
$_.Exception.Message
exit 1
}
# ==========================================
# Module Import
# ==========================================
Write-Host ""
Write-Host "===== Module Import ====="
try {
Import-Module `
$Manifest `
-Force `
-ErrorAction Stop
Write-Host `
"Module import: PASS"
}
catch {
Write-Host `
"Module import: FAIL"
Write-Error `
$_.Exception.Message
exit 1
}
# ==========================================
# Smoke Tests
# ==========================================
Write-Host ""
Write-Host "===== Smoke Test ====="
$RequiredCommands = @(
"Get-ServerHealth"
"Get-HealthEventType"
"Write-Log"
"Save-HealthState"
)
foreach (
$Command in $RequiredCommands
) {
$Found =
Get-Command `
$Command `
-Module SysAdminToolkit `
-ErrorAction SilentlyContinue
if ($null -eq $Found) {
Write-Error `
"Required command missing: $Command"
exit 1
}
Write-Host `
"$Command : PASS"
}
# ==========================================
# Pester
# ==========================================
Write-Host ""
Write-Host "===== Pester ====="
$Result =
Invoke-Pester `
-Path $Tests `
-Output Detailed `
-PassThru
# ==========================================
# Result
# ==========================================
Write-Host ""
Write-Host "===== CI Summary ====="
Write-Host `
"Total : $($Result.TotalCount)"
Write-Host `
"Passed : $($Result.PassedCount)"
Write-Host `
"Failed : $($Result.FailedCount)"
Write-Host `
"Skipped : $($Result.SkippedCount)"
if (
$Result.FailedCount -gt 0
) {
Write-Host ""
Write-Host "CI RESULT: FAILED"
exit 1
}
Write-Host ""
Write-Host "CI RESULT: PASSED"
exit 0
現在本機跟 CI Server 使用同一個入口
工程師本機:
.\Scripts\Invoke-CITests.ps1
Jenkins:
powershell.exe
-File Invoke-CITests.ps1
GitHub Actions:
run: |
.\Scripts\Invoke-CITests.ps1
這樣的好處是:
Testing Logic 不需要在 Jenkins、GitHub Actions、本機各寫一份。
CI 平台只負責:
啟動它。
真正 Testing Logic 仍放在:
Repository
這樣比較容易維護。
Day 27 小結
今天我們正式把:
Git
+
Pester
串成:
CI Pipeline
前幾天:
Day 24
Module
Day 25
Pester
Day 26
Git
到了今天:
Day 27
Git Push
↓
CI
↓
Module Validation
↓
Smoke Test
↓
Pester
↓
Pass / Fail
今天最重要的第一個觀念是:
CI 不是拿來執行每天的 Server Health Check。
每天的巡檢:
Task Scheduler
→ Operations
Code 修改後的驗證:
CI
→ Code Quality
第二個重要觀念:
測試失敗時,PowerShell 要回傳正確的非零 Exit Code。
例如:
Tests Passed
→ exit 0
Tests Failed
→ exit 1
這樣:
Jenkins
GitHub Actions
其他 CI
才能知道結果。
第三個:
CI 不應該隨便拿 Production 當測試環境。
我們應該把:
Unit Test
Smoke Test
Module Validation
盡可能做到:
不依賴 Production
真正:
AD
WinRM
SMTP
的 Integration Test 則放在專門的:
Lab / Test Environment
最後則是:
CI Agent 本身也要遵守 Least Privilege。
不要因為 Automation 最後可能管理 AD,就讓執行 Unit Test 的 CI Runner:
直接 Domain Admin
這反而會讓:
Git Code
↓
CI
↓
High Privilege Execution
變成新的安全風險。
到現在整個工程流程已經變成:
Feature Branch
↓
PowerShell Code
↓
Local Pester
↓
Git Commit
↓
Git Push
↓
CI
↓
Manifest Validation
↓
Smoke Test
↓
Pester
↓
┌───────────────┐
│ Quality Gate │
└───────┬───────┘
↓
PASS
↓
Review / Merge
我們從 Day 1:
Get-Service
一路走到現在,已經開始真正接觸到:
Automation
+
Testing
+
Version Control
+
Continuous Integration
這也就是為什麼 PowerShell 學到後面,會慢慢跟:
DevOps / Platform Engineering
產生交集。
Day 28 預告
Day 28|PowerShell Release 與部署:測試通過後,怎麼安全更新 Automation Server?
現在我們已經做到:
Code
↓
Git
↓
CI
↓
Pester Passed
但是還差最後一個很現實的問題:
CI 綠燈之後,Production Server 上的 SysAdminToolkit 要怎麼更新?
如果現在仍然是:
工程師
↓
Copy SysAdminToolkit.psm1
↓
貼到 Automation Server
↓
覆蓋舊檔
就可能遇到:
Copy 錯 Server
漏 Copy 檔案
Module Version 不一致
更新到一半失敗
不知道 Production 正在跑哪一版
Day 28 我們會建立:
Git Tag
↓
Release Version
↓
Package
↓
Validation
↓
Deploy to Automation Server
↓
Smoke Test
↓
確認 Version
並討論:
自動部署
vs
Approval Gate
以及最重要的一件事情:
如果新版 Module 上線後出問題,怎麼知道要回到哪個版本?
也就是正式把我們的 SysAdmin Automation Toolkit 從「CI 驗證通過」再往「可控地交付到執行環境」推進一步。