Skip to content

About

Helper module for PowerShell Universal

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

synedgy.universal.helper

A bunch of PowerShell Universal (PSU) helpers to make your experience more productive.

UDComponents

Reusable Universal Dashboard components for rendering PSU job information, shared across consuming projects (e.g. ComsolCentral, PSUConfig):

  • New-UDPsuJobHeader - renders a summary header card for a PSU job (script path, status badge, created/started/completed timestamps, duration, copy-to-clipboard job id chip).
  • New-UDPsuJobTerminalView - renders a terminal-style view of a PSU job's output, with an optional structured events table tab, auto-refresh while the job is active, line-number and timestamp toggles, and ANSI-to-HTML color rendering.

Both components rely on private helpers (ConvertFrom-PsuJobOutputEntry, Convert-AnsiToHtml, Convert-PlainTextToHtml, Get-UDPsuJobThemePalette, Get-UDPsuJobThemeStyleBlock, ConvertTo-UDPsuThemedIconMarkup) and branding assets under images/synedgy_pwsh/ that ship with this module -- consuming projects do not need to duplicate them. See images/synedgy_pwsh/COPYRIGHT.md for the copyright/usage notice covering that artwork.

Usage

$job = Get-PSUJob -Id $jobId
New-UDPsuJobHeader -Job $job
New-UDPsuJobTerminalView -JobId $job.Id -JobStatus ([string]$job.Status) `
    -JobOutputSnapshot @($job.Output) -IncludeStructuredTable

New-UDPsuJobTerminalView's live-refresh New-UDDynamic content block imports the exact module path used by the parent dashboard runspace and calls its private helpers from that module scope, since private module functions are not otherwise accessible from a New-UDDynamic sync/refresh runspace. This also supports deployments where the module was loaded directly from a path that is not discoverable through the refresh runspace's PSModulePath.

Pass -JobOutputSnapshot @($job.Output) when the job object is already available. If a refresh runspace cannot retrieve live output (for example because its connection or authentication context differs from the parent dashboard runspace), the component displays that snapshot and a visible warning instead of rendering a blank terminal.

The component also writes failure diagnostics with PSU's built-in Write-PSULog command. View them under Observe > Logging > Live Logs, filtering Feature to App and Resource to New-UDPsuJobTerminalView:<ElementId>. Events include the job id, dynamic view, runspace stage, fallback usage, and exception message. App tokens are redacted from fetch errors, and neither app tokens nor job-output content are included as structured log properties.

Theming: light/dark, automatic or forced, with custom CSS overrides

Both components accept a -Theme parameter:

  • Auto (default): follows PSU's own theme switch live. PSU sets a data-theme="light" or data-theme="dark" attribute on <html> and updates it instantly (no page reload) when the user toggles its built-in theme switch. The component's colors and icon are driven by CSS custom properties (--psu-term-*) scoped to the component's own element id, defined once in a <style> block emitted alongside the component (see Get-UDPsuJobThemeStyleBlock), so they re-cascade automatically whenever PSU flips that attribute.
  • Light / Dark: forces that theme for this component instance regardless of the ambient PSU theme (renders only that palette's CSS rule, with no html[data-theme="dark"] override rule).

The light palette is inspired by the classic PowerShell ISE / VS Code light theme (white background, dark text, ISE-style syntax accent colors for the Error/Warning/Information/ Verbose/Debug job output streams). The dark palette preserves the component's original look.

Both components also accept a -CustomCss parameter: raw CSS appended after the theme rules in the same <style> block, so it naturally overrides them. For example, to override just the accent color for a specific instance:

New-UDPsuJobTerminalView -JobId $job.Id -ElementId 'my-terminal' `
    -CustomCss '#my-terminal { --psu-term-accent: #ff6600; }'

Icon assets always use the plain monochrome variants: pwsh_custom_black.svg for light theme and pwsh_custom_white.svg for dark theme. In Auto mode, both variants are rendered (wrapped in .psu-theme-light-only / .psu-theme-dark-only spans) and shown/hidden via the same CSS rules that drive the color variables, so the icon also switches live with no JavaScript required.

Default line numbers / timestamps visibility

New-UDPsuJobTerminalView accepts -HideLineNumbers and -HideTimestamps switches to control whether line numbers and timestamps are shown by default for a first-time viewer (shown by default when omitted). Once a viewer manually clicks the toggle buttons, their choice is remembered in the browser's localStorage and takes precedence over these defaults on later visits to the same page.

# Hide both by default for first-time viewers; still toggleable per-viewer afterwards
New-UDPsuJobTerminalView -JobId $job.Id -HideLineNumbers -HideTimestamps

AiTool: declarative AI tool registration (like APIEndpoint, for New-PSUAiTool)

synedgy.universal.helper also ships an AiTool class attribute, an [System.Attribute] you can put on any public function in a consuming module to expose it as a PowerShell Universal AI tool, mirroring the existing APIEndpoint attribute pattern used for REST endpoints:

function Find-UserByCity
{
    [CmdletBinding()]
    [AiTool(
        Description   = 'Finds users by city'
        Authenticated = $false
        Role          = ('admin','user')
    )]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $City
    )

    # ...
}

Then, in the consuming module's .universal/*.ps1 resource file, a single call discovers and registers every [AiTool()]-decorated function in the module:

Import-PSUAiTool -Module PSUConfig -Environment 'PSUConfig'

Import-PSUAiTool uses Get-ModuleAiTool to scan the module's exported functions for the AiTool attribute, then for each one:

  1. Registers (or re-registers -- both cmdlets are idempotent, safe to call on every PSU config reload) a backing PSU Script resource via New-PSUScript -Module <Module> -Command <Function>.
  2. Registers the AI tool itself via New-PSUAiTool -ScriptFullPath '<Module>\<Function>' ....

This differs from the APIEndpoint/Import-PSUEndpoint pattern: New-PSUEndpoint accepts an inline -Endpoint {scriptblock}, so Import-PSUEndpoint can synthesize a wrapper scriptblock on the fly with no separate script registration needed. New-PSUAiTool, however, only accepts -ScriptFullPath referencing an existing PSU Script resource (the same <Module>\<Command> naming convention used by New-PSUScript/Get-PSUScript), so Import-PSUAiTool always (re-)declares that backing script alongside the AI tool registration.

AiTool attribute properties, all optional:

Property Default Description
Name function name Name of the registered AI tool.
Description comment-based help synopsis Description shown to AI agents.
Authenticated $false Whether the tool requires an authenticated user.
Role (none) Role(s) required to access/invoke the tool.
Mcp $true Whether the tool is exposed through Model Context Protocol (MCP).
Environment (none) PSU environment the backing script should run in.

Authorization: prefer an app token and server URL over -Integrated

When fetching the $job passed into these components, prefer an app token via the Management API (Get-PSUJob -Id $jobId -AppToken $token -ComputerName $url) over -Integrated. -Integrated bypasses PSU's Management API -- and therefore its role/permission checks -- entirely, so code using it can read any job in the instance regardless of role configuration. An app token, by contrast, is bound to whatever role it is assigned, is revocable, and is auditable. Treat -Integrated as a fallback for quick local setups without a configured token, not the default.

New-UDPsuJobTerminalView follows the same connection model for output retrieval. When -UniversalServerUrl is supplied, it passes that value as ComputerName and includes -AppToken when supplied. When the URL is omitted, it calls Get-PSUJobOutput -Integrated; the helper module does not discover a URL from Get-ModuleConfig or assume a localhost address. An app token therefore requires an explicit -UniversalServerUrl.

This does not yet achieve fine-grained per-script job scoping -- today the only permission that grants job-read access at all (automation/read) grants it for every job in the instance, not just a specific script's jobs (a known PSU platform limitation, already submitted to Devolutions and confirmed as being fixed). But using a token still keeps access inside PSU's authorization model rather than bypassing it altogether, and will become properly scoped once that fix ships.

Known PowerShell Universal platform behaviors

These are general PSU platform quirks worth knowing when building on top of this module or PSU apps that consume it:

  • Get-PSUScript -Name requires the full <ModuleName>\<Command> path (e.g. 'PSUConfig\Get-myUser'), not just the command name.
  • Invoke-PSUScript -Integrated -Wait reliably returns a job object with an empty Id on this PSU version (100% reproducible across 130 test invocations). Omit -Wait when the job Id is needed immediately (e.g. to redirect to a job detail page) -- without -Wait, Id is reliably populated.
  • Never directly modify files under PSU's live Repository folder (C:\ProgramData\UniversalAutomation\Repository on Windows); only a build-driven deploy task should change files there.
  • When deploying a Sampler-built module to PSU, always run the pack task explicitly before deploy -- Invoke-Build's incremental build detection can silently skip pack, leaving stale or missing .nupkg files and causing PSU deployment to fail without a clear error in the build output.

About

Helper module for PowerShell Universal

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages