2026-06-25 12:54:29 -05:00
2026-06-25 12:54:29 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:41:33 -05:00
2026-06-25 12:54:29 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:30:55 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:41:33 -05:00
2026-06-25 12:41:33 -05:00
2026-06-25 12:05:53 -05:00
2026-06-25 12:05:53 -05:00

POSHManager

POSHManager is an API-first PowerShell operations console for storing scripts, managing target hosts and encrypted credentials, building RunPlans, executing jobs, and reviewing per-host and system logs.

The current UI is a Vue/Vite view layer over the Express API. All create/update/delete/execute actions go through backend routes so another GUI, CLI, or automation client can reuse the same platform.

Features

  • Secure local login with JWT sessions.
  • Editable operator profile, avatar URL, local password change, and per-user theme preference.
  • Microsoft Entra ID interactive single sign-on (OAuth authorization-code flow), configuration fields, and Entra password-flow handoff support. Entra login auto-provisions the user and is offered only when fully configured; local login always remains available.
  • Script workspace modeled after the SysProv template editor:
    • left Script Library rail with folders and right-click context menu actions,
    • VS Code-style Monaco PowerShell editor with syntax highlighting, minimap, folding, hover, snippets, and autocomplete,
    • right Details rail for metadata, visibility, stats, and actions,
    • close/reopen controls for Script Library and Details rails,
    • Variable Studio for POSHManager runtime variables, PSADT reference variables, and custom variables,
    • inline suggestions for PowerShell cmdlets, PSADT functions/snippets, POSHManager variables, custom variables, and Asset Library tokens.
  • Script folders and script version history through the API.
  • Asset Library for deployment package content:
    • folder tree organization for installers, icons, configs, archives, detection scripts, and support files,
    • authenticated browser/API upload, preview, download, delete, and metadata updates,
    • link records that associate assets with scripts, RunPlans, PSADT profiles, or Intune deployment plans,
    • {{asset:<id>}} script tokens and POSHM_ASSET_MANIFEST runtime metadata for non-PSADT PowerShell usage.
  • PSAppDeployToolkit-focused authoring support covering the PSADT variable reference catalog.
  • Custom script variables with personal/shared/group visibility, encrypted values, editor insertion, and RunPlan injection.
  • PSADT Workbench with support matrix, deployment structure reference, Invoke-AppDeployToolkit parameters, persistent deployment profiles, generated scaffolds, migration wizard, and Intune-ready command lines.
  • Upstream PSADT repo coverage for module metadata, all public functions, launch helper commands, ADMX policies, and the v4 frontend template shape.
  • PSADT Best Practice Verifier for saved scripts, rendered profiles, and pasted script content with severity scoring, line-aware findings, remediation text, and rule metadata.
  • Intune Publishing Wizard for PSADT Win32 deployment plans, including package source, .intunewin tracking, command style, UI compatibility, requirements, detection rules, return-code policy, assignment rings, status, Graph app ID, and visibility.
  • Microsoft Graph Intune tracking for PSADT deployments, with Graph environment configuration managed under Config / Intune plus mobile app linking, status sync, failure review, device/user status rows, and sync run history.
  • Host Library with searchable/sortable/paginated table and modal add/edit flow.
  • Credential Vault as its own menu item with encrypted-at-rest secrets and modal add/edit flow.
  • RunPlan Library with searchable/sortable/paginated table, modal composer, and execute action.
  • Aggregate job log viewer with search/filter/pagination.
  • System log tab for Winston request/application logs.
  • Users & Groups administration with modal create flows.
  • Application Config screen with collapsible sections and boolean switch controls.
  • Floating searchable online Help Library from the top navigation with operations docs, API examples, PSADT/Intune deployment guidance, snippets, and common PowerShell cmdlet references.
  • Docker-friendly runtime configuration through environment variables.

Stack

  • Node.js 24, Express, CORS, Helmet, Multer
  • Built-in node:sqlite with prepared statements
  • Vue 3, Vite, Vuetify, Tailwind CSS, Monaco Editor, lucide icons
  • Winston request/system logging
  • AES-256-GCM credential encryption
  • Playwright for browser smoke checks

Project Layout

server/
  routes/          Express route registration only
  controllers/     HTTP validation, status codes, and request/response mapping
  models/          SQLite persistence and query functions
  forms/           Zod schemas and payload normalization
  middleware/      auth and request logging middleware
  services/        encryption, PowerShell runner, Winston logger

client/src/
  views/           top-level route/view components such as Login, Dashboard, Error
  plugins/         Vue plugin setup such as Vuetify theme/defaults
  components/
    dashboard/     dashboard widget frame/content and widget library modal
    assets/        asset tree and asset preview/details components
    scripts/       script-authoring helpers such as Variable Studio
    settings/      reusable settings/config section components
    ui/            reusable modal/table primitives
    AppSidebar.vue
    AppTopbar.vue
    ParticleBackdrop.vue
  api.js           fetch wrapper with auth token injection
  App.vue          page composition and orchestration
  style.css        Tailwind entrypoint plus shared Material/fluid visual system

The backend already follows route/controller/model/form/service separation. The frontend now keeps top-level pages in client/src/views and uses reusable table, modal, dashboard-widget, and config-section components for repeated UI patterns. Future large screens should become view components first, with smaller domain-specific pieces under folders such as components/scripts, components/hosts, and components/runplans.

Tailwind CSS is installed through the Vite plugin and imported from client/src/style.css. Vuetify setup is centralized in client/src/plugins/vuetify.js so Material-style defaults, theme roles, density, icons, and component behavior are controlled in one place instead of scattered through views.

Dashboard widgets are intentionally split:

  • client/src/views/DashboardView.vue owns dashboard page composition and emits layout actions.
  • client/src/components/dashboard/DashboardWidget.vue owns the reusable widget card frame and widget-body extension point.
  • client/src/components/dashboard/WidgetLibraryModal.vue owns restore/add UI for hidden widgets.
  • New widgets should be added to the dashboard catalog in App.vue, then rendered in DashboardWidget.vue.

Script authoring helpers are split under client/src/components/scripts. VariableEditorModal.vue owns the PSADT/POSHManager variable catalog browser and custom variable editor so future PSADT snippets, Intune deployment helpers, and variable packs can be added without bloating App.vue.

Quick Start

cp .env.example .env
npm install
npm run dev

Open http://localhost:3000.

Default development login:

Email: admin@posh.local
Password: change-me-now

Change default credentials and secrets before deploying.

Runtime Modes

npm run dev starts both services:

  • Vite UI: http://localhost:3000
  • Express API: http://127.0.0.1:5174
  • Vite proxies /api to the API, so the browser uses one frontend origin.

npm run prod serves the built Vue UI through Vite preview on port 3000 and starts the API on port 5174.

npm run build
npm run prod

Docker

docker compose up --build

The container exposes Vite on port 3000; /api is proxied internally to Express. SQLite data and logs are stored in the poshmanager-data volume. The Docker image installs PowerShell 7 for execution workflows.

Configuration

Configuration is visible in the UI under Config and is grouped into collapsible sections. Database-backed settings can be edited in the UI; environment-backed values are shown so Docker/reverse-proxy configuration remains visible.

Important environment variables:

Variable Description
NODE_ENV development or production.
PORT Internal Express API port. Defaults to 5174.
SERVER_FQDN Public app URL used for generated links and runtime display.
CLIENT_ORIGIN Primary allowed browser origin.
CLIENT_ORIGINS Comma-separated trusted origins for CORS.
API_PROXY_TARGET Vite proxy target for /api. Defaults to http://127.0.0.1:5174.
DATABASE_PATH SQLite file path.
LOG_DIR Winston log directory.
FILE_LOCKER_DIR Filesystem root for uploaded assets/packages/images/scripts/configs. Defaults to ./FileLocker. Mount this in Docker for persistence.
ASSET_DIR Deprecated compatibility alias for older deployments; new installs should use FILE_LOCKER_DIR.
MAX_ASSET_BYTES Maximum size of one uploaded asset in bytes. Defaults to 52428800 (50 MiB).
JSON_LIMIT Express JSON request body limit for non-file API calls. Asset uploads use multipart multer.
JWT_SECRET JWT signing key. Must be changed in production.
CREDENTIAL_STORE_KEY Encryption key material for credential secrets. Must be changed in production.
DEFAULT_ADMIN_EMAIL First-run admin email.
DEFAULT_ADMIN_PASSWORD First-run admin password.
DEFAULT_ADMIN_NAME First-run admin display name.
POWERSHELL_BIN PowerShell executable. Defaults to pwsh.
ALLOW_SCRIPT_EXECUTION Set false to disable actual RunPlan execution.
CATALOG_CHECK_INTERVAL_MINUTES Minutes between catalog upstream-version checks. 0 disables.
AUTO_PROMOTE_INTERVAL_MINUTES Minutes between auto-promote soak checks. 0 disables.
TOOLS_DIR Directory for bundled tools. Defaults to ./tools.
INTUNEWIN_UTIL_PATH Path to IntuneWinAppUtil.exe (defaults to <TOOLS_DIR>/IntuneWinAppUtil.exe).
INTUNEWIN_BUILD_COMMAND Cross-platform packager command template ({source},{setup},{output}).
ENTRA_ENABLED Enables Entra-related auth behavior when fully configured.
ENTRA_TENANT_ID Microsoft tenant id.
ENTRA_CLIENT_ID Microsoft app registration client id.
ENTRA_CLIENT_SECRET Microsoft app registration secret.
ENTRA_CALLBACK_URL Entra sign-in callback URL placeholder.
ENTRA_PASSWORD_CHANGE_URL URL opened for Entra-backed password changes.

API Basics

All protected routes require:

Authorization: Bearer <jwt>
Content-Type: application/json

Login:

curl -s http://localhost:3000/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@posh.local","password":"change-me-now"}'

The response includes:

{
  "token": "jwt-token",
  "user": {
    "id": "usr_...",
    "email": "admin@posh.local",
    "displayName": "POSH Admin",
    "role": "admin"
  }
}

API Routes

Health And Bootstrap

Method Route Auth Description
GET /api/health Public API health check.
GET /api/bootstrap User Summary counts and settings snapshot for the UI.

Help Library

The online Help Library is available from the top navigation and is backed by API routes so future clients can reuse the same searchable documentation. The floating panel can be moved around the screen and searches operations, API examples, PSADT/Intune guidance, snippets, and PowerShell cmdlet references.

Method Route Auth Description
GET /api/help User Search help article summaries. Supports q and category query parameters.
GET /api/help/:id User Return one full help article with sections, paragraphs, code examples, and tags.

PowerShell search example:

$base = "http://localhost:3000"
$token = (Invoke-RestMethod -Method Post -Uri "$base/api/auth/login" -ContentType "application/json" -Body (@{
  email = "admin@posh.local"
  password = "change-me-now"
} | ConvertTo-Json)).token
$headers = @{ Authorization = "Bearer $token" }

Invoke-RestMethod -Method Get -Uri "$base/api/help?q=psadt&category=PSADT" -Headers $headers
Invoke-RestMethod -Method Get -Uri "$base/api/help/psadt-intune-deploy" -Headers $headers

The help library includes local summaries for common Microsoft.PowerShell.Core cmdlets and links operators back to the Microsoft reference at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/?view=powershell-5.1.

Microsoft Graph And Intune Tracking

Graph environments store Microsoft Entra app registration details used to call Microsoft Graph for Intune app metadata and deployment reporting. In the UI, these are managed under Config / Intune. Client secrets are encrypted at rest and are never returned by the API. At minimum, read-only tracking should use app-only permissions such as DeviceManagementApps.Read.All; creating or changing app assignments requires broader permissions such as DeviceManagementApps.ReadWrite.All.

Method Route Auth Description
GET /api/graph/connections User List visible Graph environments without secrets.
POST /api/graph/connections User Create a Graph environment with encrypted client secret.
PUT /api/graph/connections/:id User Update Graph environment metadata or rotate the client secret.
DELETE /api/graph/connections/:id User Delete a Graph environment.
POST /api/graph/connections/:id/test User Acquire a token and verify Intune app read access.
GET /api/graph/connections/:id/mobile-apps User Search Intune mobile apps for linking to POSHManager deployment plans. Supports q.
POST /api/psadt/intune/deployments/:id/graph/link User Link an Intune Publishing Wizard plan to a Graph environment and mobileApp id.
POST /api/psadt/intune/deployments/:id/graph/sync User Sync device/user install status and failure rows from Graph Intune reports.
GET /api/psadt/intune/deployments/:id/graph/status User Return stored status rows, failure rows, summary counts, and sync run history.
POST /api/psadt/intune/deployments/:id/graph/publish User Create/update the win32LobApp metadata in the tenant from the plan. Requires a write-enabled Graph connection.
POST /api/psadt/intune/deployments/:id/graph/upload-content User Upload + commit .intunewin content (from an Asset Library assetId) to the published app.
POST /api/psadt/intune/deployments/:id/graph/assign User Replace the app's assignments (rings, filters, exclusions, auto-update).
POST /api/psadt/intune/deployments/:id/graph/relationships User Replace the app's supersedence/dependency relationships.
POST /api/psadt/intune/deployments/:id/graph/drift User Compare the plan against the live tenant app/assignments/relationships.
POST /api/psadt/intune/deployments/:id/graph/reconcile User Re-apply the plan (metadata+assignments+relationships) to clear drift.
POST /api/psadt/intune/deployments/:id/new-version User Clone the plan into the next version, linked to the same catalog application.
POST /api/psadt/intune/deployments/:id/promote User Advance a rollout ring's assignment intent (e.g. available → required).
GET /api/psadt/intune/builder/status User Whether this host can build .intunewin (Windows + tool, or external packager).
POST /api/psadt/intune/deployments/:id/build User Build a .intunewin from a source folder and ingest it into the Asset Library.
GET /api/packaging/installer-types User List known installer technologies and their silent-switch catalog.
POST /api/packaging/analyze User Detect installer technology from a file name and recommend install/uninstall commands.
POST /api/packaging/detection User Generate an Intune detection rule (MSI product code, file+version, or registry).

Installer intelligence (packaging)

/api/packaging/analyze turns an installer file name into a detected technology (MSI, MSP, Inno Setup, NSIS, InstallShield, WiX Burn, MSIX/AppX, Squirrel, or generic EXE) with recommended silent install/uninstall commands, a PSADT Start-ADT* form, and a confidence level; ambiguous .exe files return all candidate technologies so the operator can pick. /api/packaging/detection generates a detection rule in the deployment's detectionType/detectionRule shape — an MSI product-code rule, or an Intune-style custom PowerShell script for file-version or registry detection. The deployment modal's Analyze installer control fills the install/uninstall commands directly. | GET | /api/psadt/intune/deployments/:id/graph/audit | User | Return the audit trail of tenant-changing Graph actions for this deployment. | | GET | /api/intune/applications | User | List catalog applications with computed update state. | | POST | /api/intune/applications | User | Create a catalog application (groups versioned deployments). | | POST | /api/intune/applications/import | User | Adopt an existing tenant Win32 app into the catalog (Graph read). | | GET | /api/intune/applications/:id | User | Read one catalog application. | | PUT | /api/intune/applications/:id | User | Update a catalog application. | | DELETE | /api/intune/applications/:id | User | Delete a catalog application. | | POST | /api/intune/applications/:id/check-update | User | Look up the upstream version and record the latest known version. | | POST | /api/intune/applications/check-all | Admin | Run the upstream check for every auto-check application now. | | GET | /api/intune/change-requests | User | List tenant-change approval requests (visible deployments; admins see all). | | POST | /api/intune/change-requests/:id/approve | Admin | Approve a pending change request. | | POST | /api/intune/change-requests/:id/reject | Admin | Reject a pending change request. | | GET | /api/intune/reporting/overview | User | Rollout reporting: deployments by status, install-state counts, top errors, write-action outcomes. | | GET | /api/intune/deployments/:id/audit/export | User | CSV export of the tenant write-back audit trail for a deployment. |

Governance (Phase 6)

A Graph connection can require approval (requireApproval, admin-only). When set, any tenant-changing action (publish / upload-content / assign / relationships) does not run immediately — it creates a change request and returns 202. An admin approves it via /change-requests/:id/approve, and the next attempt executes and marks the request executed. Governance flags (allowWrite, requireApproval) can only be granted by admins and are preserved on non-admin edits. The audit trail is CSV-exportable, and /reporting/overview aggregates rollout health across the deployments an operator can see. The scheduled catalog auto-checker runs when CATALOG_CHECK_INTERVAL_MINUTES is greater than zero.

A connection can also name authorized publishers (publisherIds — who may trigger writes; empty means any user with a write-enabled connection) and authorized approvers (approverIds — who may approve change requests alongside admins). Both lists are admin-managed. Drift detection (/graph/drift) compares the stored plan against the live tenant app, assignments, and relationships, returning field-level differences plus missing/extra assignments and relationships so operators can spot out-of-band tenant changes. Reconcile (/graph/reconcile) re-applies the full plan to clear drift. The promotion pipeline clones a plan into the next version (/new-version, linked to the same catalog application) and advances rollout rings (/promote, e.g. a Broad ring from available to required); the operator builds/uploads and assigns the new version.

Packaging (.intunewin build) and auto-promote

POSHManager can build the .intunewin itself: place IntuneWinAppUtil.exe in the tools/ directory (see tools/README.md) and use the Build package action — it runs the Content Prep Tool over the deployment's source folder and ingests the result into the Asset Library, ready for content upload. The build host must be Windows; on other platforms set INTUNEWIN_BUILD_COMMAND to a cross-platform packager ({source}, {setup}, {output} placeholders). The UI disables Build when no packager is available.

A deployment can also auto-promote: set "auto-promote after (hours)", a ring, and an intent. Publishing starts the soak clock; the scheduled worker (AUTO_PROMOTE_INTERVAL_MINUTES > 0) advances the ring's intent once the soak window elapses, and — when the linked connection is write-enabled and not approval-gated — pushes the new assignments to the tenant automatically.

Application catalog (Phase 5)

A catalog application groups versioned deployment plans (intune_deployments.application_id) and tracks the currently published mobileApp. Each read computes updateState.updateAvailable by comparing currentVersion to latestKnownVersion. A version source can be manual, a url returning JSON with a version field, or a winget community index ref; check-update fetches and records the latest version. import adopts an existing tenant Win32 app (display name / publisher / version / app id) into the catalog as a migration aid. Version lookups and import hit live sources; the comparison, update-state, and Graph→catalog mapping are unit-tested.

Tenant write-back (Phase 1)

Publishing to Intune is opt-in and gated. A Graph connection must have Allow tenant write-back enabled (admin-only), which requires the broader DeviceManagementApps.ReadWrite.All application permission. Read-only tracking connections can never change a tenant.

POST .../graph/publish builds a Microsoft Graph win32LobApp object from the deployment plan (display name/publisher from the linked PSADT profile, install/ uninstall commands, install experience, return codes, and detection rules) and creates or updates it in the tenant. The created mobileApp id is stored back on the deployment and the plan is marked published. Every attempt — success or failure — is recorded in the Graph audit log with the actor, the exact request payload, and the Graph response.

After publishing the app definition, .../graph/upload-content makes it installable: it reads a .intunewin package from the Asset Library, parses the encrypted payload and Detection.xml encryption info locally, then runs the Graph content-version → Azure block upload → commit flow and sets the committed content version. .../graph/assign replaces the app's assignments (group / all-devices / all-users targets, include/exclude filters, exclusions, and available-app auto-update), and .../graph/relationships replaces supersedence (update/replace) and dependency (detect/autoInstall) relationships with 11-node and cycle validation.

The content upload and assignment/relationship writes drive a live tenant and are validated end to end only against a real Intune tenant; the payload mapping, .intunewin parsing, and validation logic are unit-tested. See PLAN-INTUNE.md.

Graph connection payload:

{
  "name": "Production Intune",
  "tenantId": "contoso.onmicrosoft.com",
  "clientId": "00000000-0000-0000-0000-000000000000",
  "clientSecret": "client-secret-value",
  "cloud": "global",
  "graphBaseUrl": "https://graph.microsoft.com",
  "authorityHost": "https://login.microsoftonline.com",
  "defaultApiVersion": "v1.0",
  "enabled": true,
  "visibility": "shared"
}

Deployment link and sync examples:

$body = @{ connectionId = "graph_123"; graphAppId = "intune-mobile-app-id" }
Invoke-RestMethod -Method Post -Uri "$base/api/psadt/intune/deployments/intune_123/graph/link" -Headers $headers -ContentType "application/json" -Body ($body | ConvertTo-Json)

$sync = @{ connectionId = "graph_123"; graphAppId = "intune-mobile-app-id"; mode = "reports" }
Invoke-RestMethod -Method Post -Uri "$base/api/psadt/intune/deployments/intune_123/graph/sync" -Headers $headers -ContentType "application/json" -Body ($sync | ConvertTo-Json)
Invoke-RestMethod -Method Get -Uri "$base/api/psadt/intune/deployments/intune_123/graph/status" -Headers $headers

Status rows preserve the Intune app id, scope (device or user), device/user identifiers, install state, install detail, error code, inferred error description, last sync time, and raw Graph/report payload for troubleshooting. POSHManager prefers Intune reporting endpoints for tracking because older deviceStatuses and userStatuses relationships have changed over time; legacy status mode is kept as a fallback for tenants where it still works.

Auth And Profile

Method Route Auth Description
POST /api/auth/login Public Local login. Rate limited per client IP.
GET /api/auth/entra/login Public Redirect to Microsoft sign-in (404 when Entra login is not fully configured).
GET /api/auth/entra/callback Public Entra OAuth redirect target; provisions the user and hands the SPA a session token.
GET /api/auth/me User Current session user.
PUT /api/auth/profile User Update display name, contact fields, timezone, avatar URL.
PUT /api/auth/theme User Update theme preference.
POST /api/auth/change-password User Change local password or return Entra redirect metadata.

Profile payload:

{
  "displayName": "Automation Lead",
  "email": "operator@example.com",
  "jobTitle": "Platform Engineer",
  "phone": "+1 555 0100",
  "timezone": "America/Chicago",
  "avatarUrl": ""
}

Theme payload:

{ "theme": "dashtreme" }

Users And Groups

Admin-only routes create users and groups. Groups are used for group-visible scripts, credentials, and RunPlans.

Method Route Auth Description
GET /api/users Admin List users with group memberships.
POST /api/users Admin Create a local user.
GET /api/groups User List groups.
POST /api/groups Admin Create a group.

Create user:

{
  "email": "operator@example.com",
  "displayName": "Automation Operator",
  "password": "temporary-password",
  "role": "user",
  "groupIds": ["grp_..."]
}

Create group:

{
  "name": "Operations",
  "description": "Shared scripts and RunPlans",
  "userIds": []
}

Credential Vault

Credential reads never return the secret. Secrets are encrypted with AES-256-GCM before storage.

Method Route Auth Description
GET /api/credentials User List visible credentials.
POST /api/credentials User Create encrypted credential.
PUT /api/credentials/:id User Update metadata or replace secret.
DELETE /api/credentials/:id User Delete visible credential.

Payload:

{
  "name": "Domain automation account",
  "kind": "username_password",
  "username": "svc-poshmanager",
  "secret": "super-secret-value",
  "visibility": "group",
  "groupId": "grp_..."
}

kind can be username_password or api_key. visibility can be personal, shared, or group.

Hosts

Method Route Auth Description
GET /api/hosts User List host library.
POST /api/hosts User Create host.
PUT /api/hosts/:id User Update host.
DELETE /api/hosts/:id User Delete host.

Payload:

{
  "name": "SQL-PROD-01",
  "fqdn": "sql-prod-01.contoso.local",
  "address": "sql-prod-01.contoso.local",
  "osFamily": "windows",
  "transport": "winrm",
  "port": null,
  "credentialId": "cred_...",
  "tags": ["production", "database"],
  "notes": "Primary SQL host",
  "visibility": "shared",
  "groupId": null
}

transport can be winrm, ssh, local, or api. visibility can be personal, shared, or group; hosts default to shared. RunPlans can only target hosts visible to the operator who creates or edits them.

Script Library

Method Route Auth Description
GET /api/folders User List visible folders.
POST /api/folders User Create folder.
PUT /api/folders/:id User Update folder.
DELETE /api/folders/:id User Delete folder.
GET /api/scripts User List visible scripts.
POST /api/scripts User Create script and initial version.
GET /api/scripts/:id User Read script.
PUT /api/scripts/:id User Update script and create a version record.
DELETE /api/scripts/:id User Delete script.
GET /api/scripts/:id/versions User List script versions.

Create/update script:

{
  "folderId": "fld_...",
  "name": "Sample Health Check.ps1",
  "description": "Simple smoke-test script for local execution.",
  "content": "Write-Output \"Hello\"",
  "visibility": "shared",
  "groupId": null
}

Create folder:

{
  "parentId": null,
  "name": "Examples",
  "visibility": "personal",
  "groupId": null
}

Asset Library

The Asset Library stores package-supporting files on disk under FileLocker and tracks metadata in SQLite. Use it for PSADT/Intune source content such as Files/setup.exe, SupportFiles/config.json, icons, detection scripts, archives, and reusable content referenced by normal PowerShell scripts.

Assets can be personal, shared, or group-visible. Folder organization is independent from script folders and is exposed through a tree view in the UI. Uploads use multipart form-data with a file field handled by multer; metadata is submitted alongside the file.

New files are staged in FileLocker/_incoming, then promoted to:

FileLocker/
  assets/
    packages/<assetId>/<filename>   # installers, .intunewin, archives
    images/<assetId>/<filename>     # icons, banners, screenshots
    scripts/<assetId>/<filename>    # ps1, psm1, cmd, bat
    configs/<assetId>/<filename>    # json, xml, yaml, admx/adml, reg
    documents/<assetId>/<filename>  # txt, md, pdf, docs
    generic/<assetId>/<filename>    # everything else
Method Route Auth Description
GET /api/assets/folders User List visible asset folders.
POST /api/assets/folders User Create an asset folder.
PUT /api/assets/folders/:id User Rename, move, describe, or rescope an asset folder.
DELETE /api/assets/folders/:id User Delete a visible asset folder. Child folders are removed and assets become unfiled.
GET /api/assets User List visible asset metadata.
POST /api/assets User Upload an asset with multipart form-data; file field name is file.
GET /api/assets/:id User Read one asset metadata record.
PUT /api/assets/:id User Update folder, name, description, package path, visibility, or group.
DELETE /api/assets/:id User Delete asset metadata and its stored file.
GET /api/assets/:id/view User Stream an asset inline for preview.
GET /api/assets/:id/download User Download the stored asset file.
GET /api/assets/:id/links User List link records for an asset.
POST /api/assets/:id/links User Link an asset to a script, RunPlan, PSADT profile, or Intune deployment.
DELETE /api/assets/links/:linkId User Delete an asset link record.

Create an asset folder:

{
  "parentId": null,
  "name": "Contoso VPN",
  "description": "Installers, icons, and support files for the VPN deployment.",
  "visibility": "shared",
  "groupId": null
}

Upload asset form fields:

Field Required Description
file Yes Uploaded binary file handled by multer.
folderId No Asset folder id. Omit for unfiled assets.
name Yes Display name.
description No Operational notes.
packagePath No Package-relative path such as Files/setup.exe or SupportFiles/config.json.
visibility No personal, shared, or group.
groupId When group Group id for group-visible assets.

PowerShell upload example:

$file = "/packages/ContosoVpnSetup.exe"
$form = @{
  file = Get-Item $file
  folderId = "afld_..."
  name = "Contoso VPN Installer"
  description = "Primary installer used by the PSADT package."
  packagePath = "Files/ContosoVpnSetup.exe"
  visibility = "shared"
}
Invoke-RestMethod -Method Post -Uri "$base/api/assets" -Headers $headers -Form $form

Link an asset to a target:

{
  "targetType": "psadt_profile",
  "targetId": "psadt_...",
  "linkRole": "installer",
  "packagePath": "Files/ContosoVpnSetup.exe"
}

targetType can be script, runplan, psadt_profile, or intune_deployment. linkRole can be reference, package-file, installer, support-file, icon, or detection-script.

Each asset returns a referenceToken like {{asset:ast_123}}. During RunPlan execution, POSHManager replaces that token with the local stored file path and exposes all visible assets in POSHM_ASSET_MANIFEST as JSON. For PSADT/Intune package building, use packagePath to place the file into the correct package-relative location such as Files/setup.exe, SupportFiles/config.json, or Assets/app.ico.

Variables And PSADT Authoring

Variable APIs expose the built-in POSHManager runtime variables, the PSAppDeployToolkit reference variables, and user-defined custom variables. The Vue editor consumes the same API used by external clients.

Method Route Auth Description
GET /api/variables/catalog User Return POSHManager built-ins, PSADT variables, and visible custom variables.
GET /api/variables/custom User List visible custom variables.
POST /api/variables/custom User Create a custom variable.
PUT /api/variables/custom/:id User Update a custom variable; omit value to keep an existing encrypted value.
DELETE /api/variables/custom/:id User Delete a visible custom variable.

Custom variable payload:

{
  "name": "CompanyName",
  "description": "Deployment branding value used by PSADT install scripts.",
  "category": "Deployment",
  "value": "Contoso",
  "valueType": "string",
  "sensitive": false,
  "visibility": "group",
  "groupId": "grp_..."
}

Custom variable names must be valid PowerShell identifiers without the $ prefix. They can be inserted as $CompanyName or token syntax such as {{CompanyName}}. Values are encrypted at rest with the same AES-GCM service used by the Credential Vault. Non-sensitive custom values are returned for editing; sensitive values are available to execution but hidden from API/UI reads.

POSHManager runtime variables available during execution include:

$POSHM_JOB_ID
$POSHM_RUNPLAN_ID
$POSHM_SCRIPT_ID
$POSHM_SCRIPT_NAME
$POSHM_HOST_ID
$POSHM_HOST_NAME
$POSHM_HOST_ADDRESS
$POSHM_HOST_TRANSPORT
$POSHM_TRIGGERED_BY

PSADT variables are cataloged from the PSAppDeployToolkit variable reference for authoring help and insertion. PSADT itself creates those values when the deployment session is opened by Invoke-AppDeployToolkit.ps1 or exported into session state.

PSADT Workbench

The PSADT Workbench models the PSAppDeployToolkit deployment lifecycle as first-class API data. It currently supports the first implementation part of the PSADT integration:

  • support matrix for the referenced PSADT documentation areas,
  • upstream repo metadata for PSAppDeployToolkit module version/GUID,
  • deployment structure reference table,
  • Invoke-AppDeployToolkit parameter reference table,
  • full public-function catalog from the upstream module,
  • ADMX policy surface catalog,
  • launch/test command catalog,
  • UI and installer snippets moved into the searchable Help Library,
  • persistent deployment profiles,
  • generated Invoke-AppDeployToolkit.ps1 scaffold content,
  • generated package/Intune command lines,
  • best-practice verification for scripts and profiles,
  • script migration planning from legacy PSADT scripts to the latest supported PSADT template/function names,
  • stored Intune deployment plans for Win32 app command, detection, return-code, assignment-ring, and rollout-status tracking.
Method Route Auth Description
GET /api/psadt/catalog User Return PSADT source links, module metadata, support matrix, structure rows, invoke parameters, public functions, ADMX policies, launch commands, and snippets.
GET /api/psadt/validation-rules User Return the PSADT verifier rule catalog.
POST /api/psadt/validate User Validate raw script content, a saved script, or a rendered PSADT profile.
POST /api/psadt/migration/plan User Build a PSADT migration plan for raw content or a saved script.
POST /api/psadt/migration/apply User Apply automatic migration replacements and optionally create a migrated script copy.
GET /api/psadt/intune/deployments User List visible PSADT-aware Intune deployment plans.
POST /api/psadt/intune/deployments User Create an Intune deployment plan with commands, detection, return codes, and assignments.
GET /api/psadt/intune/deployments/:id User Read a visible Intune deployment plan.
PUT /api/psadt/intune/deployments/:id User Update an Intune deployment plan.
DELETE /api/psadt/intune/deployments/:id User Delete an Intune deployment plan.
GET /api/psadt/profiles User List visible PSADT deployment profiles.
POST /api/psadt/profiles User Create a PSADT deployment profile.
GET /api/psadt/profiles/:id User Read a visible PSADT profile.
PUT /api/psadt/profiles/:id User Update a visible PSADT profile.
DELETE /api/psadt/profiles/:id User Delete a visible PSADT profile.
GET /api/psadt/profiles/:id/render User Render a profile into script scaffold and command-line metadata.

Profile payload:

{
  "name": "Microsoft Edge Enterprise",
  "appVendor": "Microsoft",
  "appName": "Edge",
  "appVersion": "126.0.0",
  "appArch": "x64",
  "appLang": "EN",
  "appRevision": "01",
  "templateVersion": "v4",
  "deploymentType": "Install",
  "deployMode": "Silent",
  "requireAdmin": true,
  "zeroConfig": false,
  "suppressReboot": true,
  "closeProcesses": [
    { "name": "msedge", "description": "Microsoft Edge" }
  ],
  "installTasks": [
    {
      "type": "exe",
      "phase": "Install",
      "filePath": "setup.exe",
      "arguments": "/S",
      "secureArguments": false
    }
  ],
  "uiPlan": { "welcome": true },
  "configPlan": {},
  "admxPlan": {},
  "visibility": "shared",
  "groupId": null
}

Render response:

{
  "scriptName": "Microsoft-Edge-Enterprise.ps1",
  "deployCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Install -DeployMode Silent -SuppressRebootPassThru",
  "intuneInstallCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Install -DeployMode Silent -SuppressRebootPassThru",
  "intuneUninstallCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Uninstall -DeployMode Silent -SuppressRebootPassThru",
  "script": "generated PowerShell content"
}

Zero-config MSI profiles set zeroConfig to true, intentionally leave appName empty, and render a checklist-oriented scaffold for the one-MSI Files-folder deployment pattern. The Workbench does not ship or redistribute PSADT template/module files; it generates the authoring scaffold and operational metadata that belong in a deployment package.

The current upstream audit was performed against psappdeploytoolkit/psappdeploytoolkit commit 9d244f4 from 2026-06-24. POSHManager now models:

  • module version 4.2.0,
  • module GUID 8c3c366b-8606-4576-9f2d-4051144f7ca2,
  • all 142 public *-ADT* functions from src/PSAppDeployToolkit/Public,
  • all 40 ADMX policies from src/PSAppDeployToolkit/opt/ADMX/PSAppDeployToolkit.admx,
  • upstream launch helper command patterns from examples/Launching,
  • the upstream v4 Invoke-AppDeployToolkit.ps1 frontend template shape.

Generated PSADT scripts now follow the upstream v4 frontend structure: phase scriptblocks via New-Variable, $adtSession metadata, local-package module import by GUID/version when available, Remove-ADTHashtableNullOrEmptyValues, Open-ADTSession, extension module imports, phase invocation, and Close-ADTSession. DeployMode defaults to Auto; POSHManager only emits explicit -DeployMode when a profile selects Interactive, NonInteractive, or Silent.

Verifier payloads can target content directly:

{
  "platform": "intune",
  "content": "Invoke-AppDeployToolkit.ps1 content here"
}

Or a saved script/profile:

{ "platform": "intune", "scriptId": "scr_..." }
{ "platform": "intune", "profileId": "psadt_..." }

Verifier response:

{
  "ok": false,
  "score": 58,
  "counts": {
    "total": 4,
    "critical": 1,
    "error": 1,
    "warning": 2,
    "info": 0
  },
  "findings": [
    {
      "ruleId": "INTUNE-SERVICEUI",
      "severity": "critical",
      "category": "Intune/security",
      "line": 12,
      "title": "ServiceUI usage found",
      "remediation": "Remove ServiceUI workarounds and rely on PSADT v4.1 native user-session UI handling."
    }
  ]
}

The first verifier rule pack covers PSADT v4/v4.1 requirements and known gotchas from the release notes, requirements, v4-to-v4.1 upgrade guidance, FAQ, installer guidance, and exit-code reference. Current checks include unsupported #requires versions, legacy v3 function names, Deploy-Application naming, legacy $appName style variables, ServiceUI.exe, Fluent UI prompt -Icon, raw msiexec, missing -WaitForChildProcesses, reserved PSADT exit-code ranges, global module imports, Intune defer retry concerns, and user-context RequireAdmin issues.

Migration plan payload:

{
  "targetVersion": "4.2.0",
  "scriptId": "scr_..."
}

Raw content can be migrated without a saved script:

{
  "targetVersion": "4.2.0",
  "content": "Deploy-Application.ps1\nExecute-Process -Path setup.exe\n$appName = 'Legacy App'",
  "createScript": true,
  "nameSuffix": "PSADT 4.2 migrated"
}

Migration response:

{
  "targetVersion": "4.2.0",
  "summary": {
    "steps": 5,
    "automatic": 3,
    "manual": 2,
    "replacements": 3
  },
  "steps": [
    {
      "id": "MIG-FN-EXECUTE-PROCESS",
      "type": "replace",
      "automatic": true,
      "status": "ready",
      "title": "Execute-Process to Start-ADTProcess",
      "lines": [2],
      "replacement": "Start-ADTProcess"
    }
  ],
  "migratedContent": "updated PowerShell content",
  "migratedScript": null
}

Automatic migration currently covers common PSADT legacy names such as Execute-Process, Execute-MSI, Execute-MSP, Show-InstallationWelcome, Show-InstallationPrompt, Write-Log, Deploy-Application.ps1/.exe, and legacy $appName style metadata variables. Manual review items flag ServiceUI launch wrappers, older function-based deployment phase shape, raw msiexec, missing Open-ADTSession, and Intune deferral flows that need -DeferRunInterval.

Intune deployment plan payload:

{
  "name": "Edge Enterprise - Pilot",
  "profileId": "psadt_...",
  "scriptId": "scr_...",
  "sourceFolder": "C:\\Packages\\Edge\\PSADT",
  "intunewinFile": "EdgeEnterprise.intunewin",
  "commandStyle": "v4",
  "installBehavior": "system",
  "restartBehavior": "return-code",
  "uiMode": "native-v4",
  "requirements": {
    "architecture": "x64",
    "minOs": "Windows 10 22H2",
    "diskSpaceMb": 512,
    "runAs32Bit": false
  },
  "status": "ready",
  "installCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Install",
  "uninstallCommand": "Invoke-AppDeployToolkit.exe -DeploymentType Uninstall",
  "detectionType": "custom-script",
  "detectionRule": "PowerShell detection script or rule data",
  "returnCodes": [
    { "code": 0, "type": "success", "meaning": "Install completed successfully." },
    { "code": 1602, "type": "retry", "meaning": "User deferred or cancelled; let Intune retry." },
    { "code": 1703, "type": "softReboot", "meaning": "Legacy PSADT soft reboot mapping used by some Intune examples." },
    { "code": 3010, "type": "softReboot", "meaning": "Reboot required." }
  ],
  "assignments": [
    { "ring": "Pilot", "intent": "available", "groupName": "IT Pilot", "notes": "Validation ring." },
    { "ring": "Broad", "intent": "required", "groupName": "All Windows Devices", "notes": "Production rollout." },
    { "ring": "Exclusion", "intent": "exclude", "groupName": "App Exclusions", "notes": "Break-glass exclusions." }
  ],
  "graphAppId": "optional Intune mobileApp id",
  "visibility": "shared"
}

The PSADT screen presents these records through an Intune Publishing Wizard carousel:

  1. Prepare source: record the PSADT source folder, Files, SupportFiles, Assets, Config, Strings, and generated .intunewin.
  2. Program commands: choose PSADT v4 Invoke-AppDeployToolkit.exe commands or legacy v3 Deploy-Application.exe compatibility commands.
  3. Requirements: capture architecture, minimum OS, disk space, and 32-bit execution mode.
  4. Detection: model MSI product code, file/version, registry, or custom PowerShell detection.
  5. Return codes: map success, retry/defer, soft reboot, PSADT failure, and custom ranges.
  6. Assignments: track pilot, broad, uninstall, and exclusion rings.
  7. Review: score compatibility for package readiness, command style, UI context, detection, return codes, and assignments.

When a deployment plan links a PSADT profile and commands are omitted, the API renders the profile and fills intuneInstallCommand and intuneUninstallCommand. commandStyle: "legacy-v3" generates the older Deploy-Application.exe -DeploymentType "Install" -DeployMode "Interactive" style used by many historical Intune examples; commandStyle: "v4" uses the current Invoke-AppDeployToolkit.exe naming from PSADT v4. uiMode: "native-v4" is preferred for PSADT v4.1+ because user prompts work from Intune SYSTEM installs without ServiceUI, while uiMode: "serviceui-legacy" is retained only for intentional older/v3 compatibility packages.

The catalog returned by /api/psadt/catalog also includes intune coverage for the publishing carousel, Win32 app packaging, requirements, UI compatibility modes, return code mapping, detection rule types, assignment rings, management actions, and PSADT/Intune gotchas such as avoiding ServiceUI.exe on v4.1+, using -DeferRunInterval, and keeping custom PSADT app codes out of reserved built-in ranges.

RunPlans And Jobs

RunPlans pair one script with one or more host IDs. Execution creates a job with per-host log rows.

Method Route Auth Description
GET /api/runplans User List visible RunPlans.
POST /api/runplans User Create RunPlan.
GET /api/runplans/:id User Read RunPlan.
PUT /api/runplans/:id User Update RunPlan.
DELETE /api/runplans/:id User Delete RunPlan.
POST /api/runplans/:id/execute User Queue/execute a RunPlan.
GET /api/jobs User List jobs.
GET /api/jobs/:id User Read job with logs.

RunPlan payload:

{
  "name": "Patch validation sweep",
  "description": "Validate core services after patching.",
  "scriptId": "scr_...",
  "visibility": "shared",
  "groupId": null,
  "parallel": true,
  "hostIds": ["hst_..."]
}

Execute:

curl -X POST http://localhost:3000/api/runplans/rp_123/execute \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{}'

Settings And Logs

Method Route Auth Description
GET /api/settings User List app settings with source metadata.
PUT /api/settings Admin Update database-backed settings.
GET /api/logs/system?limit=300 Admin Read Winston system/request logs.

Settings update:

{
  "server_fqdn": "https://poshmanager.example.com",
  "trusted_origins": "https://poshmanager.example.com",
  "allow_script_execution": "true",
  "entra_enabled": "false"
}

Execution Notes

The runner writes a temporary PowerShell wrapper and launches pwsh with environment variables instead of shell interpolation.

  • local hosts run the script directly in the API container.
  • winrm hosts use Invoke-Command -ComputerName.
  • ssh hosts use Invoke-Command -HostName.
  • Host credential resolution happens inside backend execution flows.
  • POSHManager runtime variables are injected as PowerShell variables and POSHM_* environment variables.
  • Custom variables are decrypted server-side, token-rendered for {{VariableName}}, and injected into the script block as PowerShell variables before execution.
  • Asset references are token-rendered for {{asset:<assetId>}} and exposed as POSHM_ASSET_MANIFEST JSON. Local paths point to the API container asset store; remote scripts must copy or otherwise stage those files before using them on target systems.
  • PSADT variables are not emulated by POSHManager; they are available when the script runs inside a valid PSAppDeployToolkit session.
  • Set ALLOW_SCRIPT_EXECUTION=false to disable execution while keeping planning and UI workflows active.

Remote PowerShell prerequisites, firewall rules, remoting policy, SSH remoting, and target credentials must be configured outside POSHManager.

Security Notes

  • SQLite access uses prepared statements through node:sqlite.
  • Credential secrets are encrypted with AES-256-GCM and are not returned by API reads.
  • Request logs redact authorization and cookie headers.
  • Protected routes use JWT auth.
  • Admin-only APIs are guarded by requireAdmin.
  • Jobs and their logs are scoped to the operator who triggered them, users who can see the linked RunPlan, and admins; they are not globally readable.
  • The login endpoint is rate limited per client IP to slow credential guessing.
  • Hosts carry personal/shared/group visibility; RunPlans cannot target hosts the operator cannot see.
  • User-supplied Microsoft Graph endpoint URLs are validated against an allow-list of Microsoft Graph/login hosts (SSRF protection), enforced on write and again before every outbound call.
  • allow_script_execution can be flipped at runtime from the Config screen; the database value takes precedence over the boot-time ALLOW_SCRIPT_EXECUTION environment variable.
  • In production (NODE_ENV=production) the API refuses to start while JWT_SECRET, CREDENTIAL_STORE_KEY, or DEFAULT_ADMIN_PASSWORD are left at their development defaults.
  • The default admin credential is for first-run development only.
  • Store JWT_SECRET and CREDENTIAL_STORE_KEY as container secrets or environment variables in production.

Developer Workflow

Useful commands:

npm run dev       # API + UI with Vite proxy on port 3000
npm run build     # production build
npm run prod      # API + Vite preview on port 3000
npm run check     # Node syntax check for server files
npm test          # node:test suite (visibility, settings, Entra, SSRF allow-list)

Browser smoke testing uses Playwright:

node --input-type=module path/to/smoke-script.mjs

Recommended implementation boundaries:

  • Add API behavior in server/routes, server/controllers, server/forms, and server/models.
  • Keep secrets and execution logic in server/services.
  • Keep reusable Vue primitives in client/src/components/ui.
  • Keep top-level Vue screens in client/src/views (LoginView, DashboardView, ErrorView, and future full pages).
  • Keep dashboard widgets and widget-library UI in client/src/components/dashboard.
  • Keep script authoring helpers in client/src/components/scripts.
  • Keep domain-specific Vue components in folders such as components/scripts, components/hosts, components/runplans, and components/settings.
  • Avoid adding more large page logic to App.vue; move new full views into dedicated files as the app grows.
Description
No description provided
Readme 3.9 MiB
Languages
JavaScript 50.5%
Vue 36.8%
CSS 12.6%