Skip to content

Commit 27e845f

Browse files
authored
Merge pull request #4 from EUCPilots/improve
Refactor: bundle NerdioShellApps and improve observability
2 parents f352b17 + 3812087 commit 27e845f

28 files changed

Lines changed: 456 additions & 336 deletions

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"Bash(pwsh -NoProfile -Command \"Invoke-ScriptAnalyzer -Path ./EvergreenUI -Recurse -Severity Error,Warning | Where-Object { $_.ScriptName -match ''Format-LogEntry|Merge-ConfigSection|Get-SafeFolderName|Get-UIConfig|Write-UILog|Write-UpdateOutput|Invoke-IntunePackageBuild|Invoke-FilterUpdate|Invoke-LocalPackageInstall|Test-LocalPackageDetection|Get-InstallPackage|Set-UIConfig|Invoke-AzureSignIn|Invoke-IntuneGraphWin32Import'' } | Format-Table RuleName,Severity,ScriptName,Line -AutoSize\")",
1616
"Bash(pwsh -NoProfile -Command \"Invoke-Pester -Path ./tests/EvergreenUI.tests.ps1 -Output Detailed\")",
1717
"Bash(git stash:*)",
18-
"Bash(pwsh -NoProfile -Command \"Invoke-Pester -Path ./tests/EvergreenUI.tests.ps1 -Output Normal\")"
18+
"Bash(pwsh -NoProfile -Command \"Invoke-Pester -Path ./tests/EvergreenUI.tests.ps1 -Output Normal\")",
19+
"Bash(xargs grep:*)",
20+
"Bash(python3 -c \":*)"
1921
]
2022
}
2123
}

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ coverage.xml
1818
*.psprojs
1919

2020
# ── Evergreen runtime artefacts ─────────────────────────────────────────────
21-
# Config written by EvergreenUI at runtime not committed; each user has their own
21+
# Config written by EvergreenUI at runtime - not committed; each user has their own
2222
EvergreenUI/Private/settings.json
2323
**/settings.json
2424

CHANGELOG.md

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
# Changelog
22

3+
## [1.0.14] - 2026-03-30
4+
5+
### Added
6+
- Progress log entries are now written to a per-session log file at `%LocalAppData%\EvergreenUI\logs\EvergreenUI-<timestamp>.log` (UTF-8, no BOM); a new file is created on each launch of the Workbench
7+
- `Format-LogEntry` private function: shared timestamp and level-prefix formatting used by `Write-UILog` and `Write-UpdateOutput`, eliminating duplicated formatting logic
8+
- `Merge-ConfigSection` private function: merges missing default properties into a loaded config section, replacing six identical `foreach`/`Add-Member` blocks in `Get-UIConfig`
9+
- `Get-SafeFolderName` private function: sanitises a definition file's parent directory name for use as a working folder name; applied in `Invoke-IntunePackageBuild` and `Invoke-LocalPackageInstall`
10+
11+
### Changed
12+
- Navigation rail is now collapsible via a hamburger toggle button; nav items show icon and label when expanded (180 px) and icon only when collapsed (64 px); label visibility is toggled via named `TextBlock` controls (`NavAppsLabel`, `NavDownloadLabel`, etc.)
13+
- NerdioShellApps PowerShell module moved from `support/` into `Resources/` and is loaded automatically from the bundled path at runtime; the Nerdio Manager module-path setting and its associated Settings page controls have been removed
14+
- `Write-UILog` and `Write-UpdateOutput` now delegate to `Format-LogEntry` for consistent `[HH:mm:ss] [LEVEL]` formatting
15+
- `Get-UIConfig` simplified by replacing repeated merge loops with `Merge-ConfigSection` calls
16+
- `Format-LogEntry.ps1` is now dot-sourced into every background runspace before `Write-UILog.ps1` so log formatting is available in all runspaces
17+
- `Get-SafeFolderName.ps1` is now dot-sourced into the Intune import and Install runspaces so the helper is available where needed
18+
- Post-import Nerdio verification context (`PendingNerdioPostImportVerifyAppId`, `PendingNerdioPostImportExpectedEvergreenVersion`) is now stored in `$syncHash` at dispatch time rather than being re-read from captured local variables in the completion timer tick, fixing a strict-mode variable-not-set error after a successful Shell App version add
19+
- Install tab: elevation/UAC status indicator right-aligned to match sign-in status indicators on the Import tabs (DockPanel `LastChildFill` changed from `True` to `False`)
20+
- Download queue list view: padding removed from the wrapping border to tighten spacing
21+
- `GridViewColumnHeader` style extracted to a single shared style in `Window.Resources`, removing duplicated per-`ListView` header style definitions
22+
23+
### Fixed
24+
- Background runspaces (Download All, Library Update, Update-Evergreen, Install resolve/run, Intune import, M365 package build) all failed silently because `Format-LogEntry` was not dot-sourced into the runspace session; every `Write-UILog` call threw a "term not recognised" error that was caught and swallowed, leaving no log output and no work performed
25+
- Intune Win32 import and Install run runspaces failed with "term not recognised" for `Get-SafeFolderName` after the function was extracted in the observability refactor
26+
- Install tab "Find latest versions" logged an error (`Format-LogEntry` not recognised) and performed no version resolution
27+
- Nerdio Shell App "Add version" logged a strict-mode error (`$shellAppId` cannot be retrieved) when attempting to set post-import verification context in the completion handler
28+
- Install tab elevation status indicator no longer stretches across the full status bar width
29+
- NerdioShellApps module updated for Windows PowerShell 5.1 compatibility: PS 7-only ternary and null-coalescing operators replaced, temp directory detection rewritten using Windows-compatible environment checks, `PSStyle` fallback added for informational logging
30+
- Em dash characters (``) replaced with hyphens in string literals across private functions; UTF-8 em dashes were misread by PowerShell 5.1 as Windows-1252, causing the middle byte (`0x94`) to be interpreted as a closing double-quote and breaking script parsing on import
31+
332
## [1.0.13] - 2026-03-29
433

534
### Added
@@ -17,13 +46,13 @@
1746
### Changed
1847
- Import tab: Microsoft 365 Apps sub-tab inserted between Nerdio Manager Shell Apps and Authentication tabs
1948
- Import tab / Microsoft 365 Apps: Channel and Company Name XML placeholders (`#Channel`) are resolved at packaging time from the user's dropdown selection; Channel is no longer read from the XML for display purposes
20-
- Import tab / Intune and Nerdio Manager: connection status indicators updated to match the Microsoft 365 Apps tab 9×9 ellipse with border stroke, service-name prefix label ("Intune:" / "Nerdio Manager:"), and status text right-aligned in the count bar
49+
- Import tab / Intune and Nerdio Manager: connection status indicators updated to match the Microsoft 365 Apps tab - 9×9 ellipse with border stroke, service-name prefix label ("Intune:" / "Nerdio Manager:"), and status text right-aligned in the count bar
2150
- Import tab / Intune and Nerdio Manager: count bar `DockPanel` changed to `LastChildFill="False"` so the right-docked status indicators correctly snap to the right edge
2251
- Import tab / Microsoft Intune Win32 Apps: Import Win32 app button height pinned to 32 px to match the Nerdio Manager Shell Apps tab
2352
- Nerdio Manager authentication: `Connect-Nme` called with `-ErrorAction Stop` so non-terminating errors are promoted to terminating and caught by the existing error handler; return value checked for null with an explicit failure message; `Set-NmeCredentials` also called with `-ErrorAction Stop`; module-load failure path now writes to the progress log
2453

2554
### Fixed
26-
- Nerdio Manager authentication: failures produced no log output when the NerdioShellApps module could not be loaded silently (empty path) or when `Connect-Nme` wrote non-terminating errors rather than throwing both cases are now logged
55+
- Nerdio Manager authentication: failures produced no log output when the NerdioShellApps module could not be loaded silently (empty path) or when `Connect-Nme` wrote non-terminating errors rather than throwing - both cases are now logged
2756

2857
## [1.0.12] - 2026-03-28
2958

@@ -38,17 +67,17 @@
3867
## [1.0.11] - 2026-03-27
3968

4069
### Added
41-
- Import tab / Microsoft Intune: `DisplayName` property added to all comparison rows and used as the **App** column matched rows show the Intune app name, unmatched rows show the definition name
70+
- Import tab / Microsoft Intune: `DisplayName` property added to all comparison rows and used as the **App** column - matched rows show the Intune app name, unmatched rows show the definition name
4271
- Import tab / Nerdio Shell Apps: **Versions** column shows the total count of versions present on the Shell App
4372

4473
### Changed
45-
- Import tab / Microsoft Intune: columns reduced from 9 to 6 **App**, **Publisher**, **Intune Version**, **Latest**, **Status**, **Action**; removed Definition, Matched, Update Required, and Definition Version columns
46-
- Import tab / Microsoft Intune: Action column values rationalised `Import new app` (definition not in Intune), `Import new version and supersede` (matched app with update available), `Fix in definition` (duplicate GUID across definitions), `-` (no action required)
47-
- Import tab / Microsoft Intune: row colours updated green tint for matched apps that are current; amber tint for matched apps with an update available; transparent background for all other rows
48-
- Import tab / Nerdio Shell Apps: columns restructured **App**, **Publisher**, **Shell App**, **Versions**, **Shell App Version**, **Latest**, **Status**, **Action**; removed Definition App column
49-
- Import tab / Nerdio Shell Apps: Action column values `Update`, `Import`, or `-`; same row colour scheme as the Intune tab
74+
- Import tab / Microsoft Intune: columns reduced from 9 to 6 - **App**, **Publisher**, **Intune Version**, **Latest**, **Status**, **Action**; removed Definition, Matched, Update Required, and Definition Version columns
75+
- Import tab / Microsoft Intune: Action column values rationalised - `Import new app` (definition not in Intune), `Import new version and supersede` (matched app with update available), `Fix in definition` (duplicate GUID across definitions), `-` (no action required)
76+
- Import tab / Microsoft Intune: row colours updated - green tint for matched apps that are current; amber tint for matched apps with an update available; transparent background for all other rows
77+
- Import tab / Nerdio Shell Apps: columns restructured - **App**, **Publisher**, **Shell App**, **Versions**, **Shell App Version**, **Latest**, **Status**, **Action**; removed Definition App column
78+
- Import tab / Nerdio Shell Apps: Action column values - `Update`, `Import`, or `-`; same row colour scheme as the Intune tab
5079
- Import tab / Authentication: sign-in buttons are disabled while a session is already authenticated and re-enabled when the user signs out
51-
- Settings tab: Preferences section reorganised Theme selector occupies the left half of the row; **Show Import tab** and **Show Install tab** toggle switches are grouped on the right half within the same row
80+
- Settings tab: Preferences section reorganised - Theme selector occupies the left half of the row; **Show Import tab** and **Show Install tab** toggle switches are grouped on the right half within the same row
5281
- Import tab / Microsoft Intune: `IsUpdate` flag passed to the import runspace is now derived from `IsMatched` and `UpdateRequired` row properties rather than the `ImportAction` string, making the distinction between a new app and a supersedence update independent of the display label
5382

5483
### Fixed
@@ -146,7 +175,7 @@
146175
### Added
147176
- **Export CSV**: Apps view results can be exported to a CSV file via the toolbar
148177
- **Open folder** button in the Downloads view to open the output directory in Explorer
149-
- Library GridView is now fully dynamic columns are generated from the properties returned by Evergreen rather than being hardcoded
178+
- Library GridView is now fully dynamic - columns are generated from the properties returned by Evergreen rather than being hardcoded
150179

151180
### Fixed
152181
- Prevented duplicate entries appearing in the download queue when the same app is added multiple times

CLAUDE.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
1313
## Commands
1414

1515
### Testing
16+
1617
```powershell
1718
Invoke-Pester -Path .\tests\EvergreenUI.tests.ps1 -Output Detailed
1819
```
1920

2021
### Linting
22+
2123
```powershell
2224
Invoke-ScriptAnalyzer -Path .\EvergreenUI -Recurse
2325
```
2426

2527
### Running the UI
28+
2629
```powershell
2730
Import-Module .\EvergreenUI\EvergreenUI.psd1
2831
Start-EvergreenWorkbench
@@ -31,6 +34,7 @@ Start-EvergreenWorkbench
3134
## Architecture
3235

3336
### Module Structure
37+
3438
The module exposes a single public function (`Start-EvergreenWorkbench`) that orchestrates everything. All internal logic lives in private helper functions dot-sourced by `EvergreenUI.psm1`.
3539

3640
```
@@ -50,6 +54,7 @@ EvergreenUI/
5054
```
5155

5256
### Threading Model
57+
5358
WPF requires STA (Single Threaded Apartment). `Start-EvergreenWorkbench` ensures STA thread on startup. Background operations (app downloads, library updates, Intune imports) run in isolated STA runspaces created by `New-WpfRunspace`. Communication between runspaces and the UI uses a `$syncHash` (synchronized hashtable) with `Dispatcher.Invoke` for thread-safe UI updates.
5459

5560
### Key Private Functions
@@ -73,13 +78,15 @@ WPF requires STA (Single Threaded Apartment). `Start-EvergreenWorkbench` ensures
7378
| `Test-LocalPackageDetection` | Detect installed app versions for comparison |
7479

7580
### Providers / Tabs
81+
7682
The UI has four tabs with distinct workflows:
77-
1. **Apps** Browse Evergreen app catalog, apply filters, queue downloads
78-
2. **Library** Manage local Evergreen app library
79-
3. **Import** Nerdio Manager and Intune Win32 packaging workflows
80-
4. **Install** Local package installation from definition files
83+
1. **Apps** - Browse Evergreen app catalog, apply filters, queue downloads
84+
2. **Library** - Manage local Evergreen app library
85+
3. **Import** - Nerdio Manager and Intune Win32 packaging workflows
86+
4. **Install** - Local package installation from definition files
8187

8288
### Configuration
89+
8390
User settings persist to `$env:APPDATA\EvergreenUI\settings.json`. `Get-UIConfig` creates defaults on first run. Settings include output/library paths, theme, log verbosity, and per-provider config (Nerdio, Intune, Install).
8491

8592
## Code Conventions
@@ -89,6 +96,10 @@ User settings persist to `$env:APPDATA\EvergreenUI\settings.json`. `Get-UIConfig
8996
- CRLF line endings for all PowerShell files (enforced via `.gitattributes`)
9097
- WPF controls are named with a consistent prefix scheme (see `EvergreenUI.xaml`)
9198
- Always use named parameters for PowerShell cmdlet calls (e.g. `Start-Sleep -Seconds 3`, not `Start-Sleep 3`)
99+
- Never use em dashes in any code or markdown files
100+
- Ensure PowerShell commands use compatibility with PowerShell 5.1, for example Join-Path does not support -AdditionalChildPaths on PowerShell 5.1, so use an approach that is compatible on both PowerShell 5.1 or PowerShell 7 and above
101+
- Never use emojis
102+
- Never use horizontal lines in markdown files
92103

93104
### Logging
94105

@@ -100,12 +111,12 @@ Log the following at a minimum:
100111
- Cache hit/miss with age information
101112
- Version resolution outcomes
102113

103-
`Format-LogEntry` is the shared helper for timestamp+prefix formatting. It is called internally by both `Write-UILog` and `Write-UpdateOutput` do not inline the formatting in new output functions.
114+
`Format-LogEntry` is the shared helper for timestamp+prefix formatting. It is called internally by both `Write-UILog` and `Write-UpdateOutput` - do not inline the formatting in new output functions.
104115

105116
### Error Handling
106117

107118
- Use `-ErrorAction Stop` on all cmdlets inside a `try/catch` block that is intended to catch that cmdlet's errors.
108-
- Every `catch {}` (empty catch) **must** include a comment that explains why silence is intentional, e.g.: `# best-effort failure here must not abort the caller`. Also add `Write-Verbose` of the caught exception so failures surface when running with `-Verbose`.
119+
- Every `catch {}` (empty catch) **must** include a comment that explains why silence is intentional, e.g.: `# best-effort - failure here must not abort the caller`. Also add `Write-Verbose` of the caught exception so failures surface when running with `-Verbose`.
109120
- Polling/retry loops must log each failed attempt with attempt number and exception message rather than silently swallowing errors.
110121

111122
### Structured Return Pattern
@@ -123,18 +134,18 @@ $fail = {
123134
}
124135
```
125136

126-
Follow this pattern for new functions that return structured results. The shape is intentionally function-specific do not try to genericise it.
137+
Follow this pattern for new functions that return structured results. The shape is intentionally function-specific - do not try to genericise it.
127138

128139
### Shared Helper Patterns
129140

130141
- **Nested config merging**: use `Merge-ConfigSection -Loaded $json.Section -Default $default.Section` rather than repeating the `foreach`/`Add-Member` pattern.
131-
- **Definition folder names**: use `Get-SafeFolderName -DefinitionPath $path` (returns the sanitised parent directory name). Only applicable when the folder name derives from a definition file path for display-name-based names, apply the regex directly.
142+
- **Definition folder names**: use `Get-SafeFolderName -DefinitionPath $path` (returns the sanitised parent directory name). Only applicable when the folder name derives from a definition file path - for display-name-based names, apply the regex directly.
132143
- **Log entry formatting**: use `Format-LogEntry -Message $msg -Level Info` if writing a new log-output helper function.
133144

134145
## Release Process
135146

136147
Releases are automated via two GitHub Actions workflows:
137-
1. **tag-release.yml** Watches `EvergreenUI.psd1` for version changes; creates a git tag `v{version}` when the version is bumped
138-
2. **publish-psgallery.yml** Triggered by tag creation; publishes to PowerShell Gallery using `$env:NUGET_API_KEY`
148+
1. **tag-release.yml** - Watches `EvergreenUI.psd1` for version changes; creates a git tag `v{version}` when the version is bumped
149+
2. **publish-psgallery.yml** - Triggered by tag creation; publishes to PowerShell Gallery using `$env:NUGET_API_KEY`
139150

140151
Version format is `Major.Minor.Patch` (e.g., `1.0.9`). Bump the version in `EvergreenUI.psd1` (`ModuleVersion`) to trigger a release.

EvergreenUI/EvergreenUI.psd1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#Requires -Version 5.1
22
@{
33
# Module identity
4-
ModuleVersion = '1.0.13'
4+
ModuleVersion = '1.0.14'
55
GUID = 'e63b3f34-4e6c-433d-8544-fe497c21ad98'
66
Author = 'Aaron Parker (stealthpuppy)'
77
CompanyName = 'EUC Pilots'
@@ -32,6 +32,7 @@
3232
'EvergreenUI.psm1'
3333
'en-US\EvergreenUI-help.xml'
3434
'Resources\EvergreenUI.xaml'
35+
'Resources\NerdioShellApps.psm1'
3536
'Public\Start-EvergreenWorkbench.ps1'
3637
'Private\Get-EvergreenAppList.ps1'
3738
'Private\Get-InstallPackageDefinitions.ps1'
@@ -63,7 +64,7 @@
6364
Tags = @('Evergreen', 'GUI', 'WPF', 'EUC', 'EvergreenUI', 'Windows')
6465
LicenseUri = 'https://github.com/EUCPilots/evergreen-ui/blob/main/LICENSE'
6566
ProjectUri = 'https://github.com/EUCPilots/evergreen-ui'
66-
ReleaseNotes = 'Pre-release. Adds support for import Microsoft 365 Apps packages into Intune and Nerdio Manager.'
67+
ReleaseNotes = 'Pre-release. Adds collapsible sidebar with menu item icons. Integrates NerdioShellApps.psm1. Updates loggin and improved code base.'
6768
Prerelease = 'beta'
6869
}
6970
}

EvergreenUI/EvergreenUI.psm1

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@
1717
Set-StrictMode -Version Latest
1818
$ErrorActionPreference = 'Stop'
1919

20-
# Guard: Windows and WPF only
21-
# if (-not $IsWindows -and $PSVersionTable.PSVersion.Major -ge 6) {
22-
# throw 'EvergreenUI requires Windows. This module cannot be used on Linux or macOS.'
23-
# }
24-
2520
# Dot-source Private functions
2621
$privatePath = Join-Path -Path $PSScriptRoot -ChildPath 'Private'
2722

0 commit comments

Comments
 (0)