From dbfb6458a4b06abbdcce6a613a36ef6e6f7a0cca Mon Sep 17 00:00:00 2001 From: jessikitty Date: Wed, 23 Sep 2026 23:21:06 +1000 Subject: [PATCH] Add session context: initial AD Compare plugin build and iteration --- .../2026-04-28-ad-compare-plugin-build.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 .claude-context/sessions/2026-04-28-ad-compare-plugin-build.md diff --git a/.claude-context/sessions/2026-04-28-ad-compare-plugin-build.md b/.claude-context/sessions/2026-04-28-ad-compare-plugin-build.md new file mode 100644 index 0000000..d40826a --- /dev/null +++ b/.claude-context/sessions/2026-04-28-ad-compare-plugin-build.md @@ -0,0 +1,81 @@ +# Disco AD Compare Plugin - Session: Initial Build & Iteration + +**Date:** 2026-04-28 +**Project:** disco-ad-compare-plugin +**Status:** ongoing +**Tags:** disco-ict, plugin, active-directory, c-sharp, managed-by, uiextension + +## Context +Built a Disco ICT plugin from scratch that compares the AD computer `managedBy` attribute against the Disco `AssignedUserId` for devices, and reports mismatches. The plugin was developed iteratively through many build-fix cycles due to constraints of the target environment (no Visual Studio, only .NET Framework 4.0 MSBuild, Disco installed at `C:\Program Files\Disco\WebApp\bin`). + +## Key Decisions +- **C# 5 only** — The server has only .NET Framework MSBuild v4.0.30319 (no VS installed). No string interpolation (`$""`), no null-conditional (`?.`), no auto-property initializers, no expression-bodied members. All code uses `string.Format()`, explicit null checks, constructors for list init. +- **ToolsVersion 4.0 / TargetFramework v4.5** — Downgraded from 15.0/4.7.2 to avoid MSB3644 errors. The warnings about architecture mismatch (MSIL vs AMD64) are harmless. +- **Manual manifest.json** — No ManifestGenerator.exe available in the install. Build script creates manifest manually with correct field names matching `PluginFeatureManifest.cs`: `Id`, `Name`, `TypeName`, `PrimaryFeature`, `CategoryTypeName`. +- **CategoryTypeName for UIExtension** — Must be the generic type definition: `Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature``1` (backtick-1 for one generic param). Confirmed by comparing with working plugins (CasperSuite, PasswordManager, DocFill). +- **DN-to-DN comparison** — AD `managedBy` returns a Distinguished Name, not `DOMAIN\username`. Must look up the Disco assigned user in AD to get their DN, then compare DN-to-DN (case-insensitive). Initial version compared `DOMAIN\username` against DN which always mismatched. +- **Filter invalid devices** — Skip devices without `DOMAIN\ComputerName` format (no backslash) to avoid "The Id must include the Domain" errors. Also exclude devices not found in AD from the results table (counted separately). +- **Server-side rendering for web handler** — jQuery/AJAX approach failed because the standalone plugin page doesn't load Disco's layout/scripts. Rewrote to render full HTML server-side with inline CSS. The `/Run` action generates the complete page. +- **Plugin URL routing** — Disco route is `Plugin/{PluginId}/{PluginAction}` where PluginAction is required. URL must be `/Plugin/Disco.Plugins.ADCompare/Run` not `/Plugin/Disco.Plugins.ADCompare`. +- **Build as .zip then rename** — `Compress-Archive` only supports `.zip` extension. Build script creates `.zip` then renames to `.discoPlugin`. +- **HintPath regex replacement** — Single regex `[^<]+\\([^\\<]+\.dll)` replaces ALL assembly references to point at `DiscoBinPath`, handling Disco, Newtonsoft, MVC, WebPages, Razor, and EntityFramework in one pass. + +## Technical Details + +### Build Command +```powershell +.\Build-Plugin.ps1 -DiscoBinPath "C:\Program Files\Disco\WebApp\bin" +``` + +### Disco Plugin Paths +- Installed plugins: `C:\Disco\Plugins\\` +- Plugin packages: `C:\Disco\PluginPackages\` +- Plugin storage: `C:\Disco\PluginStorage\` +- Disco binaries: `C:\Program Files\Disco\WebApp\bin\` +- Disco logs: CSV format, viewable in Disco web UI under Configuration + +### Key Disco API Patterns +- `ActiveDirectory.RetrieveADMachineAccount(domainId, additionalProperties)` — returns AD computer, null if not found +- `ActiveDirectory.RetrieveADUserAccount(userId)` — looks up by `DOMAIN\username` +- `adAccount.GetPropertyValue("attributeName")` — reads AD attributes like `managedBy`, `lastLogonTimestamp` +- `UIExtensionFeature` — inject HTML on device pages +- `UIExtensionFeature` — inject HTML on user pages +- `PluginWebHandler.ExecuteAction(ActionName)` — web handler routing +- `PluginConfigurationHandler` with `WebViewPage` — required for plugin system + +### Assembly References Required +System, System.Core, System.Data, System.Data.Entity, System.Web, System.ComponentModel.DataAnnotations, EntityFramework, System.Web.Mvc, System.Web.WebPages, System.Web.Razor, Newtonsoft.Json, Disco.Models, Disco.Data, Disco.Services + +### Manifest Feature Format (exact JSON shape) +```json +{ + "Id": "ADCompareDeviceUI", + "Name": "Device Page - AD Compare", + "TypeName": "Disco.Plugins.ADCompare.Features.DeviceUIExtension", + "PrimaryFeature": false, + "CategoryTypeName": "Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature`1" +} +``` + +## Code Changes +- `ADComparePlugin.cs` — Main plugin class, empty Initialize (features self-register via manifest) +- `Features/DeviceCompareService.cs` — Core comparison: DN-to-DN matching, filters invalid/missing devices +- `Features/DeviceUIExtension.cs` — Device page panel: managedBy comparison, last login, AD status +- `Features/UserUIExtension.cs` — User page panel: last login, AD status, OU location, 30-day inactivity warning +- `Models/DeviceComparisonResult.cs` — Result + summary models with SkippedDevices counter +- `WebHandler/ADCompareWebHandler.cs` — Server-side rendered dashboard at `/Run`, CSV export at `/Export` +- `ConfigurationHandler/ADCompareConfigurationHandler.cs` — Required config handler with WebViewPage +- `Properties/AssemblyInfo.cs` — Version 1.3.1.0, author Jess Rogerson +- `Build-Plugin.ps1` — Build + package script, regex HintPath resolution, manual manifest generation +- `Disco.Plugins.ADCompare.csproj` — ToolsVersion 4.0, .NET 4.5, all references +- `.gitignore` — Standard .NET ignores +- `README.md` — Full documentation + +## Open Questions / Next Steps +- **UIExtension panels not rendering** — Features initialize without errors in Disco logs (`Initialized Plugin Feature: 'DeviceUIExtension'`), manifest format matches working plugins exactly, but HTML panels don't appear on device/user pages. The `Register()` call in `Initialize()` override runs. Need to investigate: does `Literal()` work? Does `Nothing()` suppress? Should we check how CasperSuite's `CasperSuiteUIExt_DeviceShow.ExecuteAction` returns its result vs our approach? May need to examine the Disco Views (`.cshtml`) that render UIExtension slots to understand where the HTML is injected. +- **Comparison dashboard works perfectly** — `/Plugin/Disco.Plugins.ADCompare/Run` renders correctly with summary stats, mismatch table, and CSV export. +- **Credit line** — Plugin should show "Created by: Jess Rogerson (yelling commands at Claude.AI)" in a footer or about section. + +## Artifacts +- Gitea repo: https://gitea.hideawaygaming.com.au/jessikitty/disco-ad-compare-plugin +- Source Disco repo (forked): https://gitea.hideawaygaming.com.au/jessikitty/Disco (branch: master)