Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Resolve module version at runtime

Status: closed
Labels: wayfinder:research
Assignees:

## Question

What is the most reliable way for `New-LS2Dashboard` to obtain the running Locksmith2 module version (including any prerelease tag) at runtime, and does this differ between the development directory load, the PSGallery-installed module, and the GitHub-packed zip?

## Resolution

Use the existing `Get-Module -Name Locksmith2` call in `New-LS2Dashboard` and read:

- `$module.Version` for the CalVer version string.
- `$module.PrivateData.PSData.Prerelease` for the optional prerelease tag.

This works identically across development, PSGallery, and GitHub zip deployments because all scenarios load the same manifest. See the full findings in [research/01-resolve-module-version.md](../../research/01-resolve-module-version.md).

## Comments

- 2026-08-03: Research complete. No scenario-specific logic required.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Footer placement in PSWriteHTML

Status: closed
Labels: wayfinder:research
Assignees:

## Question

How should a bottom-right footer be added to a PSWriteHTML dashboard, and what is the recommended way to align text to the bottom-right of the page?

## Resolution

Use `New-HTMLFooter` inside the `New-HTML` script block, with `New-HTMLText -Alignment right` for bottom-right alignment. The footer renders as a semantic `<footer>` tag at the end of the document body and is unaffected by the `-Online` switch. See the full findings in [research/02-footer-pswritehtml.md](../../research/02-footer-pswritehtml.md).

## Comments

- 2026-08-03: Research complete. Right-aligned text inside `New-HTMLFooter` satisfies the requirement.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Version string format and fallback

Status: closed
Labels: wayfinder:grilling
Assignees:

## Question

What exact string should be displayed for the version (e.g., `2026.8.30507`, `2026.8.30507-pre`, `Locksmith 2 v2026.8.30507-pre`), and what should appear if the version cannot be resolved at runtime?

## Resolution

- Display the CalVer version plus the prerelease tag when present: `2026.8.30507-pre`.
- If the version cannot be resolved at runtime, omit the version field entirely from both the header and the footer rather than showing fallback text.

## Comments

- 2026-08-03: Decision made by user.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Implement dashboard version display

Status: open
Labels: wayfinder:task
Assignees:

## Question

What is the implementation change required to add the agreed version string to the dashboard header and footer, and how should it be tested?

## Context

All prerequisite decisions are resolved:

- [Resolve module version at runtime](01-resolve-module-version.md) — Use `Get-Module -Name Locksmith2`, read `Version` and `PrivateData.PSData.Prerelease`.
- [Footer placement in PSWriteHTML](02-footer-pswritehtml.md) — Use `New-HTMLFooter` with `New-HTMLText -Alignment right`.
- [Version string format and fallback](03-version-format-and-fallback.md) — Display `2026.8.30507-pre`; omit the field entirely if resolution fails.

## Acceptance criteria

1. In `Public/New-LS2Dashboard.ps1`, resolve the module version string once near the existing `Get-Module` call.
2. Insert the version into the header line between "Computer" and "Generated":
`Forest: X | User: Y | Computer: Z | Version: V | Generated: T`
3. If version resolution fails, omit the `Version:` segment entirely (no fallback text).
4. Add or update tests for `New-LS2Dashboard` to verify the version string appears in the generated HTML.

## Comments

- 2026-08-03: Ready for implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Runtime Module Version Resolution for New-LS2Dashboard

**Ticket:** [Resolve module version at runtime](../issues/01-resolve-module-version.md)
**Status:** Research Complete
**Date:** 2026-08-03

## Summary

The most reliable way to obtain the running Locksmith2 module version at runtime is via the `Get-Module` cmdlet, retrieving both `Version` and `PrivateData.PSData.Prerelease`. This works identically for development directory loads, PSGallery-installed modules, and GitHub-packed zips — no scenario-specific logic is required.

## Findings

### Manifest structure

Source: `Locksmith2.psd1`

- `ModuleVersion` is always present (CalVer: `yyyy.M.dHHmm`).
- `PrivateData.PSData.Prerelease` is optional and only set during build when `-Prerelease` is passed to `Build-Module.ps1`.

### Current New-LS2Dashboard usage

Source: `Public/New-LS2Dashboard.ps1` (line 171)

`New-LS2Dashboard` already calls `Get-Module -Name Locksmith2` to locate the logo image. The same call can provide the version.

Accessible properties on the returned `PSModuleInfo` object:

- `$module.Version` — `System.Version` object, always available.
- `$module.PrivateData.PSData.Prerelease` — prerelease tag, works in PS 5.1 and PS 7.x.

### Recommended pattern

```powershell
$module = Get-Module -Name Locksmith2 -ErrorAction SilentlyContinue
if ($null -eq $module) {
$versionString = 'Version unavailable'
} else {
$versionString = $module.Version.ToString()
$prerelease = try { $module.PrivateData.PSData.Prerelease } catch { $null }
if ($prerelease) {
$versionString = "$versionString-$prerelease"
}
}
```

This pattern:

- Works in PS 5.1 and PS 7.x without version checks.
- Handles stable releases (no prerelease key) and prereleases uniformly.
- Requires no scenario detection (dev / PSGallery / GitHub zip all load the same manifest).

### Edge cases

| Case | Handling |
|------|----------|
| Module not found | Display `Version unavailable` |
| Prerelease missing | Display version only |
| PrivateData malformed | Use `try`/`catch` or `2>$null` to avoid errors |

## Recommendation

Use the pattern above. Reuse the existing `Get-Module -Name Locksmith2` call in `New-LS2Dashboard` rather than adding a separate helper function, because the version is only needed in one place.

## Sources

- `Locksmith2.psd1` — manifest version and prerelease declaration.
- `Build/Build-Module.ps1` — conditional prerelease injection at build time.
- `Public/New-LS2Dashboard.ps1` — existing `Get-Module` usage for logo loading.
- Microsoft PowerShell docs: `Get-Module`, `PSModuleInfo`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Footer Placement in PSWriteHTML

**Ticket:** [Footer placement in PSWriteHTML](../issues/02-footer-pswritehtml.md)
**Status:** Research Complete
**Date:** 2026-08-03

## Summary

PSWriteHTML provides the `New-HTMLFooter` cmdlet for adding footer content. Text alignment is controlled via the `-Alignment` parameter of `New-HTMLText`. For right-aligned footer text, use `New-HTMLText -Alignment right` inside `New-HTMLFooter`. The footer renders as a semantic `<footer>` tag at the end of the document body.

## Findings

### New-HTMLFooter command

Source: [EvotecIT/PSWriteHTML Public/New-HTMLFooter.ps1](https://github.com/EvotecIT/PSWriteHTML/blob/master/Public/New-HTMLFooter.ps1)

```powershell
New-HTMLFooter [[-HTMLContent] <scriptblock>]
```

- Optional building block inside `New-HTML`.
- Renders as `<footer>` at the end of the document body.

### Text alignment

Source: [EvotecIT/PSWriteHTML Public/New-HTMLText.ps1](https://github.com/EvotecIT/PSWriteHTML/blob/master/Public/New-HTMLText.ps1)

`New-HTMLText` accepts `-Alignment` with values `left`, `center`, `right`, `justify`. For bottom-right placement, use `-Alignment right`.

### Recommended usage

```powershell
New-HTML -TitleText 'Dashboard' -FilePath 'out.html' {
# ... tabs and content ...
New-HTMLFooter {
New-HTMLText -Text "Locksmith 2 $versionString" -Alignment right -Color '#666' -FontSize 12
}
}
```

### Interaction with -Online

The `-Online` switch only affects whether CSS/JS are loaded from CDN or embedded. It does not affect footer rendering.

### Fixed bottom-right corner vs. end-of-page footer

`New-HTMLFooter` places the footer at the end of the document in normal flow. If a fixed viewport bottom-right corner is required, custom CSS (`position: fixed; bottom: 10px; right: 10px;`) would be needed. The user's request says "bottom-right in the footer", which is satisfied by right-aligning text inside `New-HTMLFooter`.

## Recommendation

Add a `New-HTMLFooter` block at the end of the `New-HTML` script block, containing `New-HTMLText -Text "Locksmith 2 $versionString" -Alignment right -Color '#666' -FontSize 12`.

## Sources

- PSWriteHTML source: `New-HTMLFooter.ps1`, `New-HTMLText.ps1`, `New-HTML.ps1`.
- PSWriteHTML examples: `Example34-HeaderMainFooter`.
- Installed module: `c:\Users\Administrator\Documents\PowerShell\Modules\PSWriteHTML\1.41.0\PSWriteHTML.psd1`.
28 changes: 28 additions & 0 deletions .scratch/version-number-in-dashboard/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Dashboard Version Number

Status: wayfinder:map

## Destination

The Locksmith 2 dashboard generated by `New-LS2Dashboard` displays the running module version in the persistent header between "Computer" and "Generated on".

## Notes

- Domain: `New-LS2Dashboard` in `Public/New-LS2Dashboard.ps1`, PSWriteHTML API, module manifest (`Locksmith2.psd1`).
- The header currently renders as: `Forest: X | User: Y | Computer: Z | Generated: T`. The version should sit between Computer and Generated.
- The footer does not currently exist; PSWriteHTML's `New-HTMLFooter` (if available) or an inline bottom panel will be used.
- CalVer module version lives in the manifest; prerelease tag lives in `PrivateData.PSData.Prerelease`.

## Decisions so far

- [Resolve module version at runtime](issues/01-resolve-module-version.md) — Use `Get-Module -Name Locksmith2`, read `Version` and `PrivateData.PSData.Prerelease`; identical across dev, PSGallery, and GitHub zip.
- [Footer placement in PSWriteHTML](issues/02-footer-pswritehtml.md) — Use `New-HTMLFooter` with `New-HTMLText -Alignment right` inside the `New-HTML` script block.
- [Version string format and fallback](issues/03-version-format-and-fallback.md) — Display `2026.8.30507-pre` (CalVer + prerelease tag); omit the version field entirely if resolution fails.

## Not yet specified

_None — the route is clear._

## Out of scope

_None recorded._
2 changes: 1 addition & 1 deletion Locksmith2.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
FormatsToProcess=@('LS2Issue.format.ps1xml')
FunctionsToExport=@('*')
GUID='e32f7d0d-2b10-4db2-b776-a193958e3d69'
ModuleVersion='2026.8.20917'
ModuleVersion='2026.8.30507'
PowerShellVersion='5.1'
PrivateData=@{
PSData=@{
Expand Down
29 changes: 24 additions & 5 deletions Public/New-LS2Dashboard.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,31 @@
$scanUser = if ($script:Credential) { $script:Credential.UserName } else { [System.Security.Principal.WindowsIdentity]::GetCurrent().Name }
$scanComputer = "$env:USERDOMAIN\$env:COMPUTERNAME"

# Resolve module version (and optional prerelease tag) for header/footer display
$moduleVersionString = $null
$locksmithModule = Get-Module -Name Locksmith2 -ErrorAction SilentlyContinue
if ($null -ne $locksmithModule) {
$moduleVersion = if ($locksmithModule.Version -is [array]) { $locksmithModule.Version[0] } else { $locksmithModule.Version }
$moduleVersionString = $moduleVersion.ToString()
$prereleaseTag = try { $locksmithModule.PrivateData.PSData.Prerelease } catch { $null }
if ($prereleaseTag) {
$moduleVersionString = "$moduleVersionString-$prereleaseTag"
}
}

$headerMetadata = [System.Collections.Generic.List[string]]::new()
$headerMetadata.Add("Forest: $forestName")
$headerMetadata.Add("User: $scanUser")
$headerMetadata.Add("Computer: $scanComputer")
if ($moduleVersionString) {
$headerMetadata.Add("Version: $moduleVersionString")
}
$headerMetadata.Add("Generated: $generatedAt")
$headerLine = $headerMetadata -join ' | '

# Resolve logo and encode as base64 data URI for self-contained HTML
$logoSource = $null
$moduleBase = (Get-Module -Name Locksmith2 -ErrorAction SilentlyContinue).ModuleBase
$moduleBase = if ($null -ne $locksmithModule) { $locksmithModule.ModuleBase } else { $null }
if ($null -ne $moduleBase) {
foreach ($candidate in @(
(Join-Path $moduleBase 'Images\Locksmith2.png'),
Expand Down Expand Up @@ -270,7 +292,7 @@ header img { max-width: 50%; height: auto; display: inline-block; }
if ($logoSource) {
New-HTMLImage -Source $logoSource -Width '50%' -AlternativeText 'Locksmith 2'
}
New-HTMLText -Text "Forest: $forestName | User: $scanUser | Computer: $scanComputer | Generated: $generatedAt" -FontSize 12 -Color '#555' -Alignment center
New-HTMLText -Text $headerLine -FontSize 12 -Color '#555' -Alignment center
}

# NOTE: No New-HTMLSection/Panel before the first New-HTMLTab — PSWriteHTML counts every
Expand Down Expand Up @@ -371,7 +393,4 @@ document.addEventListener('DOMContentLoaded', function() {
}

Write-Verbose "Dashboard generated: $FilePath"
if (-not $Show) {
Write-Host "Dashboard saved to: $FilePath"
}
}
17 changes: 17 additions & 0 deletions Tests/Public/New-LS2Dashboard.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,23 @@ InModuleScope 'Locksmith2' {
Should -Invoke 'New-HTML' -Times 1 -ParameterFilter { $TitleText -match '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' }
}

It 'should include the module version in the header when available' {
$expectedVersion = (Get-Module -Name Locksmith2).Version.ToString()
New-LS2Dashboard
Should -Invoke 'New-HTMLText' -Times 1 -ParameterFilter { $Text -like "*Version: $expectedVersion*" }
}

It 'should place the version between Computer and Generated in the header' {
$expectedVersion = (Get-Module -Name Locksmith2).Version.ToString()
New-LS2Dashboard
Should -Invoke 'New-HTMLText' -Times 1 -ParameterFilter { $Text -match "Computer:.*Version: $expectedVersion.*Generated:" }
}

It 'should omit the version from the header when the module is not found' {
Mock 'Get-Module' { $null } -ParameterFilter { $Name -eq 'Locksmith2' }
New-LS2Dashboard
Should -Invoke 'New-HTMLText' -Times 1 -ParameterFilter { $Text -notlike '*Version:*' -and $Text -like '*Generated:*' }
}
}

Context 'Clickable summary cards' -Skip:(-not (Get-Module PSWriteHTML -ListAvailable)) {
Expand Down
Loading