# 🧱 B19｜Modular Scripts & Error Handling — Companion Assets

> **Series**: AI Office Practice: From Beginner to Expert
> **Article**: B19 (Track 3 Automation / Level 3 Intermediate)
> **Title (CN)**: AI 生成模块化脚本与错误处理
> **Title (EN)**: Modular Scripts & Error Handling

---

## 1. Who is this for? What problem does it solve?

For anyone who has written a script or two, but the moment it grows past a hundred lines it crashes, the red error text is unreadable, and on the second run you have to edit paths buried in the code. The root cause is three things missing:

- **No modularization**: a hundred-line monolith; change a path and you're paging through the file.
- **No error handling**: the third file fails, the whole script dies, and the first two have to be re-run.
- **No logging**: errors flash by in red; you can't tell which file or which step broke.

This folder ships a **real, runnable** skeleton: **modular + try/catch + logging + parameters**.

| File | Purpose |
|------|---------|
| `Logging_en.psm1` | Module: `Write-Log` function (console + file) |
| `Merge-OneCsv_en.ps1` | Function: merge ONE CSV into the master, with try/catch |
| `合并报表_en.ps1`     | Main flow: parameterized entry, merges all CSVs |
| `调用示例_en.ps1`     | Demo: three call patterns + a deliberately broken file |

Once you've run this folder, you'll have a "split-able, contained, traceable" template. Swap the merge step for your own work (rename, format-convert, email, …) and you have a robust automation tool.

---

## 2. How to use (5-step recipe)

### Step 1 — Environment

- Windows 10/11 includes PowerShell.
- First-run `.ps1` blocked? Run once:
  ```powershell
  Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
  ```

### Step 2 — Save all .ps1 / .psm1 as **UTF-8 with BOM**

Required by Windows PowerShell 5.1 (the moment you put Chinese or any non-ASCII in comments it really matters):

- **VS Code**: click *UTF-8* in the status bar → *Save with Encoding* → **UTF-8 with BOM**.
- **Notepad**: *Save As* → Encoding **UTF-8** (default).
- **Don't want to bother?** Install PowerShell 7 (`winget install Microsoft.PowerShell`) — it understands UTF-8 without BOM.

### Step 3 — Prepare test CSVs

```powershell
mkdir Reports
@'
name,amount
A,100
B,200
'@ | Out-File -Encoding UTF8 Reports\a.csv
@'
name,amount
C,300
'@ | Out-File -Encoding UTF8 Reports\b.csv
```

> Want to see error handling kick in? Just run `调用示例_en.ps1`; it builds the bad CSV for you.

### Step 4 — Run the main script

```powershell
cd "C:\Users\cynix\WorkBuddy\VBA.net\配套资产\B19_模块化脚本"

powershell -ExecutionPolicy Bypass -File .\合并报表_en.ps1 `
    -SourceFolder ".\Reports" `
    -OutputFile   ".\Master.csv" `
    -LogFile      ".\Run.log"
```

Outputs:
- `Master.csv`: merged data with an extra `SourceFile` column for traceability.
- `Run.log`: `[timestamp][level] message` for every step, including failure reasons.

### Step 5 — Run the demo to see all three call patterns

```powershell
powershell -ExecutionPolicy Bypass -File .\调用示例_en.ps1
```

It (1) dot-sources `Merge-OneCsv`, (2) invokes `合并报表_en.ps1` directly, (3) drops a broken CSV to verify try/catch.

---

## 3. File list

| File | Type | Purpose | Lines |
|------|------|---------|-------|
| `B19_ModularScript_EnglishREADME.md` | Markdown | This file | — |
| `B19_模块化脚本_中文README.md`        | Markdown | Chinese README | — |
| `Logging_en.psm1` | Module | `Write-Log` function (English) | ~60 |
| `Logging_zh.psm1` | Module | `Write-Log` function (Chinese) | ~60 |
| `Merge-OneCsv_en.ps1` | Function | Merge one CSV + try/catch (English) | ~50 |
| `Merge-OneCsv_zh.ps1` | Function | Same, Chinese | ~50 |
| `合并报表_en.ps1` | Main | Parameterized main flow (English) | ~70 |
| `合并报表_zh.ps1` | Main | Same, Chinese | ~70 |
| `调用示例_en.ps1` | Demo | Three call patterns + bad-file demo | ~90 |
| `调用示例_zh.ps1` | Demo | Same, Chinese | ~90 |

---

## 4. Practical code (key snippets explained)

### ① Write-Log in Logging_en.psm1

```powershell
function Write-Log {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Message,
        [ValidateSet('INFO', 'WARN', 'ERROR')]
        [string]$Level = 'INFO'
    )
    $time = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
    $line = "[$time][$Level] $Message"

    # Mirror to screen + log file; -Encoding UTF8 prevents mojibake
    Add-Content -Path $script:LogFile -Value $line -Encoding UTF8
    Write-Host $line
}
```

**Three core lines**: `Get-Date` for the timestamp; `Add-Content -Encoding UTF8` for the file (must be explicit UTF8, otherwise Chinese turns into mojibake on a different machine); `Write-Host` for the console.
**`ValidateSet`** limits `$Level` to `INFO / WARN / ERROR` — typos fail loud, not silent.

### ② try/catch in Merge-OneCsv_en.ps1

```powershell
function Merge-OneCsv {
    param([string]$SourceFile, [string]$OutputFile)
    try {
        $rows = Import-Csv -Path $SourceFile -Encoding UTF8 -ErrorAction Stop   # <- Stop
        $rows | Export-Csv -Path $OutputFile -Encoding UTF8 -NoTypeInformation `
                           -Append -ErrorAction Stop
        Write-Log -Message "Merged: $SourceFile" -Level 'INFO'
    }
    catch {
        Write-Log -Message "FAILED: $SourceFile -- $($_.Exception.Message)" -Level 'ERROR'
        # Do NOT rethrow — let the caller continue with the next file
    }
}
```

**Two key bits**:
- `Import-Csv -ErrorAction Stop`: by default PowerShell only warns; only `Stop` makes the error truly enter `catch`.
- No `throw` inside `catch`: just log and move on — this is what makes "one bad file doesn't break the batch" work.

### ③ Parameterized entry in 合并报表_en.ps1

```powershell
# ========== Parameterized entry (must be first) ==========
param(
    [string]$SourceFolder = '.\Reports',
    [string]$OutputFile   = '.\Master.csv',
    [string]$LogFile      = '.\Run.log'
)
```

**Three wins**: change paths without touching code; defaults make it work out of the box; anyone reading the script knows which knobs exist.

---

## 5. FAQ / Troubleshooting

1. **Mojibake / "missing terminator" parse errors?**
   99% of the time the `.ps1` is not saved as UTF-8 with BOM. Use VS Code → *Save with Encoding → UTF-8 with BOM*, or upgrade to PowerShell 7.

2. **Errors don't enter `catch`?**
   PowerShell only warns by default. Add `-ErrorAction Stop`. Every data cmdlet in these scripts does so.

3. **Chinese characters become `?????` in the log?**
   `Add-Content -Encoding UTF8` must be explicit. PowerShell 5.1 doesn't default to UTF-8 when writing.

4. **`Export-Csv -Append` errors out?**
   `-Append` is PowerShell 3.0+. On older Windows (2.0) collect rows to a variable first, then export once.

5. **Excel COM objects stuck in Task Manager?**
   Always pair `try { … } finally { $excel.Quit(); ReleaseComObject($excel) }`. The `finally` block is the "no-matter-what" cleanup.

---

## 6. Next steps

After this folder, you have a reusable skeleton. Two natural next moves:

- **B20 — Automation Pipelines**: wire `合并报表_en.ps1` into Task Scheduler (run nightly), or skip code entirely with Excel *Power Query → From Folder*.
- **B17 / B18 refresher**: if black-boxes still make you nervous, start with B17 (first command); if you want to redo batch Office, B18 (Script Batch Office) is the right next read.

---

> 📌 **Quick checklist** before running:
> - [ ] All `.ps1` / `.psm1` are UTF-8 **with BOM** (PowerShell 5.1);
> - [ ] `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser` already run;
> - [ ] Paths in `-SourceFolder / -OutputFile / -LogFile` updated;
> - [ ] Test CSVs have matching header columns;
> - [ ] `Import-Csv` / `Export-Csv` both specify `-Encoding UTF8 -ErrorAction Stop`.