iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
IT Operation

系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server系列 第 27 篇

Day 27|PowerShell CI:Git Commit 之後自動跑 Pester,不讓壞掉的 Module 進入下一步

  • 分享至 

  • xImage
  •  

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

內容:

==========================================

CI Test Runner

==========================================

$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 ""

==========================================

Step 1 - Validate Manifest

==========================================

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

}

==========================================

Step 2 - Import Module

==========================================

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

}

==========================================

Step 3 - Run Pester

==========================================

Write-Host ""
Write-Host "Running Pester tests..."
Write-Host ""

$TestResult =
Invoke-Pester -Path $TestPath
-Output Detailed `
-PassThru

==========================================

Step 4 - Evaluate Result

==========================================

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

不要:
CI Runner

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 使用方式
例如:

CI Validation

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 驗證通過」再往「可控地交付到執行環境」推進一步。

上一篇
Day 26|PowerShell 與 Git:讓每一次 Script 修改都有版本紀錄
下一篇
Day 28|PowerShell Release 與部署:測試通過後,怎麼安全更新 Automation Server?
系列文
系統工程師的 30 天自動化維運實戰:PowerShell × AD × Windows Server 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言