--- note type: - ess - note date: 2026-06-11 done: --- ## Reference script (TMC `Session.ps1`) ### 1. What is the pool key? The **server name** string (`$serverName`). ```mermaid flowchart LR serverName["serverName: APP01"] pool["remoteSessionPool hashtable"] session["PSSession object"] serverName -->|"used as key"| pool pool --> session ``` Same server name → same pool entry. Different server → different entry. **Caveat for your task:** if the same server could be reached with **different credentials**, keying only on server name can return the wrong session. For a first version, matching the reference (key = server name) is fine if credentials are consistent per server. --- ### 2. What happens on the second call for the same server? ```mermaid sequenceDiagram participant Caller participant GetRemoteSession as Get-RemoteSession participant Pool participant NewPSSession as New-PSSession Caller->>GetRemoteSession: serverName = APP01 GetRemoteSession->>Pool: lookup APP01 Pool-->>GetRemoteSession: session found GetRemoteSession-->>Caller: existing session Note over NewPSSession: not called ``` - Pool lookup hits. - Logs something like “Using existing remote session”. - Returns the cached session. - **`New-PSSession` is not called** — that’s the whole point of the pool. --- ### 3. When is `New-PSSession` called vs skipped? | Condition | Action | |-----------|--------| | Pool has an entry for `$serverName` | Skip `New-PSSession`, return cached session | | Pool has **no** entry for `$serverName` | Call `New-PSSession`, store result in pool, return it | ```mermaid flowchart TD start[Get-RemoteSession called] lookup{Pool contains serverName?} reuse[Log: using existing session] create[Log: creating session] newPs[New-PSSession] store[Store in pool] return[Return session] start --> lookup lookup -->|yes| reuse --> return lookup -->|no| create --> newPs --> store --> return ``` **Important:** `Get-RemoteSession` creates/reuses the session. **`Invoke-Command -Session $session`** is a separate step the caller does afterward (e.g. `Test-Path` on the remote box). --- ### 4. What happens when session creation fails? Does that fit ESS? **In the reference:** if `$session` is falsy after `New-PSSession`, it logs an error and runs **`exit`** (ends the whole PowerShell process). **In ESS capabilities:** that does **not** fit. Look at [`Remove-Folder.psm1`](deploy/scripts/modules/capabilities/Remove-Folder.psm1) — failures use **`throw`**, not `exit`. | | TMC reference | ESS pattern | |---|---------------|-------------| | Failure | `exit` | `throw "message"` | | Why | Script runner stops everything | Pester, pipelines, and orchestrators can catch/handle it | For your module: log with `Write-Log -Level ERROR`, then **throw** so deployment fails cleanly and tests can use `Should -Throw`. --- ## Design decisions (for your implementation) ### Parameters Match the reference, adapted to ESS style: - **`ServerName`** (mandatory) — pool key and `New-PSSession` target. - **`Credential`** (optional) — pass through when present; omit when using default/current-user auth. Do **not** put remote script logic (e.g. `Test-Path`) inside `Get-RemoteSession`. Callers get a session, then run `Invoke-Command` themselves. --- ### Pool scope — where does the hashtable live? **At module scope** in the `.psm1`, **above** the function — not inside the function body. ```mermaid flowchart TB subgraph psm1 [Get-RemoteSession.psm1] poolVar["script-scoped pool hashtable"] func[Get-RemoteSession function] poolVar --> func end call1[First call] --> func call2[Second call] --> func func --> poolVar ``` If the pool is **inside** the function, it is recreated every call and caching never works. Use **`$script:`** scope so the pool belongs to the module instance, not the global session (unless you deliberately want global — you don’t for this task). --- ### Naming: `Get-RemoteSession` vs `New-RemoteSession` | Verb | Meaning here | |------|----------------| | `New-*` | Always creates something new | | `Get-*` | Retrieves existing **or** obtains one if needed | Your function is “get from pool, or create and cache” → **`Get-RemoteSession`** is the right approved verb. --- ### Logging Use **`Write-Log`** (from [`Core.Logging.psm1`](deploy/scripts/modules/core/Core.Logging.psm1)), not TMC’s `Logging`. Worth logging: - **INFO** — “Using existing…” vs “Creating…” - **INFO** — elapsed time for **creation only** (optional but matches the reference and helps debug slow WinRM) - **ERROR** — before you throw on failure --- ### Failure behavior ```mermaid flowchart LR fail[New-PSSession fails or returns nothing] log[Write-Log ERROR] throwNode[throw with clear message] fail --> log --> throwNode ``` No `exit`. Callers and Pester expect exceptions, not process termination. --- ### Pool key edge case (same server, different credentials) Reference keys **only on server name**. Implication: ```mermaid flowchart TD callA["Call: APP01 + CredA"] --> pool1[Pool stores APP01 → sessionA] callB["Call: APP01 + CredB"] --> pool2[Pool returns sessionA] pool2 --> wrong[Wrong cred — sessionA reused] ``` For learning / v1: **same as reference** is OK if each server always uses one credential set. If that’s not true later, the key would need to include something credential-related — out of scope unless your manifests require it. --- ## How this connects to `Invoke-Command` ```mermaid sequenceDiagram participant Step as Deployment step participant Get as Get-RemoteSession participant IC as Invoke-Command Step->>Get: ServerName, Credential Get-->>Step: $session Step->>IC: -Session $session -ScriptBlock { Test-Path ... } IC-->>Step: $fileExists ``` `Get-RemoteSession` = connection management. `Invoke-Command` = run code on the remote machine. --- ## Testing note (relevant to your attached snippet) For “fails when session cannot be created”, assert like other capabilities: - **`{ Get-RemoteSession ... } | Should -Throw`** — proves the function throws. - Optionally assert **`Write-Log`** was called with `-Level ERROR`. `Should -Invoke Throw` is **not** valid Pester — `throw` is a language statement, not a mockable cmdlet. Same idea as [`Remove-Folder.Tests.ps1`](deploy/tests/modules/capabilities/Remove-Folder.Tests.ps1): use `Should -Throw` on the script block, and `Should -Invoke Write-Log` for the error log line. For pool behavior tests: mock **`New-PSSession`**, call the function twice with the same server, expect **`New-PSSession` invoked once**. --- ## One-line summary | Question | Answer | |----------|--------| | Pool key | Server name | | Second call | Return cached session; no `New-PSSession` | | When to create | Only when key missing from pool | | On failure | Reference: `exit` → **you:** `Write-Log` + `throw` | | Pool location | Module-level `$script:` hashtable, not inside the function |