[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-10929":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":13,"stars7d":12,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":17,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":15,"starSnapshotCount":15,"syncStatus":13,"lastSyncTime":27,"discoverSource":28},10929,"PsUi","jlabon2\u002FPsUi","jlabon2","For building UIs in PowerShell without the misery.",null,"PowerShell",137,7,2,3,0,10,6,2.71,"MIT License",false,"main",true,[],"2026-06-12 02:02:28","# PsUi\n\n[![PowerShell Gallery](https:\u002F\u002Fimg.shields.io\u002Fpowershellgallery\u002Fv\u002FPsUi?label=PSGallery&color=blue)](https:\u002F\u002Fwww.powershellgallery.com\u002Fpackages\u002FPsUi)\n![Downloads](https:\u002F\u002Fimg.shields.io\u002Fpowershellgallery\u002Fdt\u002FPsUi?color=green)\n![PowerShell 5.1+](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FPowerShell-5.1%2B-5391FE?logo=powershell&logoColor=white)\n![Tests](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Fjlabon2\u002FPsUi\u002Ftest.yml?label=tests)\n![Stars](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fstars\u002Fjlabon2\u002FPsUi)\n[![Changelog](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fchangelog-v1.0.4-orange)](CHANGELOG.md)\n![MIT License](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-blue.svg)\n\nFor building UIs in PowerShell without the misery.\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Ffeature-showcase.gif\" alt=\"PsUi Feature Showcase\">\u003C\u002Fp>\n\nTurn existing scripts into interactive tools, or easily build custom forms from scratch. Define layouts with PsUi functions, write standard PowerShell in attached scriptblocks. The framework handles threading - async execution, output routing, UI responsiveness.\n\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'PsUi Demo' -Width 500 -Height 250 -Content {\n    New-UiInput -Label 'Server' -Variable 'server' -Placeholder 'web-prod-01'\n    New-UiDropdown -Label 'Action' -Variable 'action' -Items @('Health Check', 'Restart', 'Deploy')\n    New-UiToggle -Label 'Verbose logging' -Variable 'verbose'\n    New-UiButton -Text 'Run' -Icon 'Play' -Accent -Action {\n        Write-Host \"Connecting to $server...\" -ForegroundColor Cyan\n        Write-Progress -Activity $action -Status 'Starting...' -PercentComplete 25\n        Start-Sleep -Milliseconds 500\n        \n        if ($action -eq 'Restart') {\n            $confirm = Read-Host \"Type YES to restart $server\"\n            if ($confirm -ne 'YES') { Write-Host 'Cancelled.' -ForegroundColor Yellow; return }\n        }\n        \n        Write-Progress -Activity $action -PercentComplete 75\n        Start-Sleep -Milliseconds 500\n        Write-Host \"$action complete on $server\" -ForegroundColor Green\n        Write-Warning \"Latency: 42ms\"\n        Write-Progress -Activity $action -Completed\n    }\n}\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fintro-snippet.png\" alt=\"Intro snippet\">\u003C\u002Fp>\n\nThe window stays responsive while code runs in background runspaces. Results land in a sortable grid. Console output goes to its own panel. No threading code required.\n\n---\n\n## The Problem\n\nBuilding GUIs in PowerShell is difficult. Your options:\n\n**Single-threaded.** Run everything on the UI thread. Works until you hit a network call or slow disk read. Then the window freezes and the title bar says \"(Not Responding)\".\n\n**Roll your own threading.** Spin up a `RunspacePool`, sync state with `[HashTable]::Synchronized`, marshal updates via `$Window.Dispatcher.Invoke`, debug race conditions when controls get disposed mid-update. The boilerplate-to-actual-code ratio is high.\n\n**WinForms and prayers.** Some people try this. The forms look like they escaped from Windows 2000 and the threading problems don't go away, they just move around.\n\nPsUi is an attempt at another option. The C# backend handles runspace lifecycle and thread marshalling. Errors include stack traces and context for debugging - when something breaks, the error usually tells you why.\n\n---\n\n## Features\n\n### Stream Interception\n\nExisting console scripts work without modification in most cases.\n\nPsUi hooks the PowerShell host and redirects `Write-Host`, `Write-Warning`, `Write-Progress`, and `Write-Error` to UI panels. Progress calls become actual progress bars. Text keeps its colors - DarkYellow stays DarkYellow, not some weird approximation. The script runs the same way it always did, except now there's a window instead of a terminal.\n\n`Read-Host` and `Get-Credential` pop themed dialogs instead of blocking the console. Same with `$host.UI.Prompt()` and `-Confirm` prompts.\n\nBackground thread errors include full stack traces for debugging.\n\n### PowerShell DSL\n\nXAML is verbose. PsUi uses functions instead. Nested scriptblocks define the hierarchy - `New-UiWindow { New-UiCard { New-UiButton } }` - so code structure mirrors visual layout. IntelliSense works. Tab completion works. Around 30 control types: inputs, dropdowns, date pickers, sliders, toggles, tabs, cards, file pickers, and more.\n\nNormally background threads can't see parent scope variables because they run in separate runspaces. PsUi parses your scriptblocks, figures out what you reference, and injects those values into the runspace before execution. Two-way binding comes free with the `-Variable` parameter - write back to the variable in your action and the control updates when the action completes. No event handlers, no `$script:` workarounds, no synchronized hashtable gymnastics.\n\n### Themes\n\nEleven themes: Light, Dark, LightModern, OceanBlue, Bespin, SolarizedDark, Charcoal, DeepRed, Monokai, Azure, Pearl.\n\nSet the theme with `New-UiWindow -Theme Dark` or use the palette button in the titlebar to switch at runtime. Semantic styles handle accent buttons and validation states automatically.\n\n### Form Generation\n\n`New-UiTool` reads parameter metadata from any command and builds a complete form. This is the main reason the module exists - parameterized scripts parse directly into UI elements.\n\nTypes map to controls: `[string]` becomes a text box, `[switch]` a toggle, `[datetime]` a date picker, `[ValidateSet()]` a dropdown, `[ValidateRange()]` a slider. Parameters named `-Path` include file browsers. `-ComputerName` on domain-joined machines hooks the Windows object picker so you can search AD.\n\nMultiple parameter sets produce a selector that rebuilds the form when you switch. Complex dynamic validation may require building the form manually.\n\n### Window Isolation\n\nEach window runs in its own session with its own runspace pool and dispatcher thread. Child windows inherit theme but maintain separate state. You can open as many windows as memory allows and they won't step on each other. The debug tab shows session IDs so you can verify windows are actually isolated.\n\n---\n\n## Quick Start\n\nThree ways to try it, from fastest to most involved:\n\n**Run the demo** (recommended first step)\n```powershell\n# From the repo root\nImport-Module .\\PsUi\\PsUi.psd1\n.\\Start-PSUiDemo.ps1\n```\nThis opens a window with tabs demoing every feature - controls, host interception, async, grids, child windows, and `New-UiTool`. Click around. Break things. Check the Console tab to see what's happening.\n\n**Wrap an existing command**\n```powershell\nImport-Module .\\PsUi\\PsUi.psd1\nNew-UiTool -Command 'Get-ChildItem' -Title 'File Browser' -FolderPickerParameters 'Path'\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Ffile-browser.png\" alt=\"File Browser generated from Get-ChildItem\">\u003C\u002Fp>\n\n**Build something custom**\n```powershell\nImport-Module .\\PsUi\\PsUi.psd1\nNew-UiWindow -Title 'My First Tool' -Content {\n    New-UiInput -Label 'Name' -Variable 'userName'\n    New-UiButton -Text 'Greet' -Action {\n        Write-Host \"Hello, $userName!\"\n    }\n}\n```\nType a name, click the button, check the Console tab.\n\n---\n\n## Use Cases\n\nSysadmin utilities. Onboarding wizards. Data browsers. Log parsers. Internal tools where users need a form instead of a command line.\n\n---\n\n## How It Works\n\n### Async Execution\n\nButton actions run in background runspaces by default. The UI thread stays free to repaint, handle clicks, respond to resizing - all the things a frozen window can't do.\n\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Data Fetcher' -Content {\n    New-UiButtonCard -Header 'Fetch Data' -Icon 'CloudDownload' -Action {\n        # This runs in background - UI stays responsive\n        $data = Invoke-RestMethod 'https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts'\n        Write-Host \"Fetched $($data.Count) posts\"\n        \n        Write-Progress -Activity 'Processing' -PercentComplete 50\n        Start-Sleep -Seconds 1\n        Write-Progress -Activity 'Processing' -Completed\n        \n        $data  # Appears in Results tab\n    }\n}\n```\n\nControl values can be read and written from background threads without any `Dispatcher.Invoke` nonsense. The framework handles it. `AsyncExecutor` manages the runspace pool and routes streams to the right panels.\n\nThe async system has backpressure handling to prevent dispatcher saturation. Without it, rapid output (like a log parser emitting thousands of lines) can peg CPU trying to update the UI.\n\n### Control Hierarchy\n\nPowerShell functions create WPF controls. Nesting builds the visual tree.\n\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'User Form' -Theme Dark -Content {\n    New-UiCard -Header 'Account Details' -Content {\n        New-UiInput -Label 'Username' -Variable 'user' -Placeholder 'jsmith'\n        New-UiInput -Label 'Password' -Variable 'pass' -Password\n        New-UiDropdown -Label 'Role' -Variable 'role' -Items @('Admin', 'User', 'Guest')\n    }\n    \n    New-UiButton -Text 'Submit' -Icon 'Accept' -Accent -Action {\n        # $user, $pass, $role are injected from -Variable names\n        Write-Host \"Creating $user with role $role\"\n    }\n}\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fuser-form-dark.png\" alt=\"User form with dark theme\">\u003C\u002Fp>\n\nThe `-Variable` parameter names the control and creates the binding. Those names become PowerShell variables inside `-Action` blocks. Change the variable, the control updates. Change the control, the variable updates. It's not true reactive binding (no immediate propagation) but it's close enough for form work.\n\n### Auto-Generated Forms\n\n`New-UiTool` inspects command parameters and creates matching controls.\n\n```powershell\nImport-Module PsUi\n\n# Wrap a built-in cmdlet\nNew-UiTool -Command 'Get-Process'\n\n# Works on any command with CmdletBinding\nNew-UiTool -Command 'Get-ChildItem' -Title 'File Browser'\n```\n\n```powershell\nImport-Module PsUi\n\n# Or your own function with proper parameter decorations\nfunction Search-Logs {\n    param(\n        [Parameter(Mandatory)]\n        [string]$Path,\n        \n        [ValidateSet('Error', 'Warning', 'Info')]\n        [string]$Level = 'Error',\n        \n        [datetime]$Since = (Get-Date).AddDays(-7)\n    )\n    Get-Content $Path | Where-Object { $_ -match $Level }\n}\n\nNew-UiTool -Command 'Search-Logs'\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fauto-generated-form.png\" alt=\"Auto-generated form from Search-Logs\">\u003C\u002Fp>\n\nType mappings:\n- `[string]` → text input\n- `[int]`, `[double]` → number input (validates as you type)\n- `[switch]` → toggle\n- `[datetime]` → date picker\n- `[ValidateSet()]` → dropdown\n- `[ValidateRange()]` → slider with min\u002Fmax\n- `[SecureString]` → password field\n- `[PSCredential]` → full credential picker with username\n\nMandatory parameters get validation markers; parameter sets get a selector at the top. Results go to tabs with sorting and filtering. You can add row-level actions with `-ResultActions` - \"click this row, run this code with `$_` set to the row data.\" See the [Result Row Actions](#result-row-actions) pattern below.\n\n### Scope and Threading Model\n\nButton actions run in **separate runspaces** from your console. This is the most important thing to understand about PsUi, and the thing that will bite you if you don't.\n\n**What works:**\n- Control values are injected as variables (`$userName`, `$selectedItem`, etc.) before your action runs\n- Changes to those variables sync back to controls when the action completes\n- User-defined functions from your console are captured and available\n- Variables from your script's scope are captured at window creation time\n\n**What doesn't work:**\n- Globals set in button actions (`$Global:Result = 'done'`) do **not** propagate back to your console session\n- Real-time variable sync - changes happen at action boundaries, not during execution\n- Reference semantics across runspace boundaries - objects get copied, not shared\n\n**Live objects don't cross runspaces.** SQL connections, file streams, COM objects, open sockets - anything holding a native handle gets serialized when crossing the runspace boundary. Properties copy but the underlying connection is gone.\n\nConcrete example: you create a `SqlConnection` in Button A's action and store it in a variable. Button B tries to use that connection. It doesn't work. The connection object got copied, not referenced. The copy has the same connection string but the actual TCP socket is back in Button A's runspace, probably already disposed.\n\nThis applies to:\n- Database connections (`SqlConnection`, `OracleConnection`, etc.)\n- File streams and handles\n- Socket connections\n- Excel COM objects (`$excel = New-Object -ComObject Excel.Application`)\n- Anything that implements `IDisposable` with native resources\n\nIf you need to share heavy state between buttons, either:\n1. Store connection *info* (strings, credentials) and reconnect in each action\n2. Use `$session.Variables` for things that absolutely must persist (but understand the lifecycle)\n3. Accept that each button action is basically its own script invocation\n\nThe hydration layer is designed for form data - strings, numbers, dates, selections.\n\n**Debugging sync issues:** If variables aren't syncing back to controls, the executor fires an `OnFrameworkError` event when internal operations fail. These aren't script errors - they're problems in the hydration system itself. Wire it up to see what the framework is choking on.\n\n**Threading modes:**\n```powershell\n# Default (MTA): ThreadPool for performance\nNew-UiWindow -Title 'Fast' -Content { ... }\n\n# STA mode: dedicated threads for Windows Forms\u002FCOM compatibility\nNew-UiWindow -Title 'Legacy' -AsyncApartment STA -Content { ... }\n```\n\nUse `-AsyncApartment STA` if your scripts use Windows Forms dialogs, legacy COM objects that require STA (like some Office interop), or other STA-dependent APIs. It's slower because we can't use the thread pool, but it's the only way to make certain legacy stuff work.\n\n### Standalone Viewers\n\nWork with or without a parent window. These are for when you just want to show some data:\n\n```powershell\nImport-Module PsUi\n\n# Pipe to a grid with filtering and sorting\nGet-Process | Select-Object Name, Id, CPU, WorkingSet | Out-Datagrid -TitleText 'Processes'\n\n# Select rows and pass through (like Out-GridView -PassThru but better looking)\nGet-Process | Select-Object Name, Id, CPU, WorkingSet | Out-Datagrid -PassThru -IsFilterable | Stop-Process -WhatIf\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fout-datagrid.png\" alt=\"Out-Datagrid with process data\">\u003C\u002Fp>\n\nFilter input is debounced (300ms) so typing doesn't freeze on large datasets. Columns sort by clicking headers. Rows are selectable. Export to CSV works. Faster filtering and better theming than Out-GridView.\n\n`Out-TextEditor` provides find\u002Freplace, line numbers, and optional spell checking for text content:\n\n```powershell\nImport-Module PsUi\n\n# View a file with the text editor\nGet-Content C:\\Windows\\System32\\drivers\\etc\\hosts | Out-TextEditor -TitleText 'Hosts File' -Theme Dark -ReadOnly\n\n# Edit text and get it back\n$notes = 'Meeting notes go here...' | Out-TextEditor -TitleText 'Notes' -SpellCheck\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Ftext-editor.png\" alt=\"Out-TextEditor viewing hosts file\">\u003C\u002Fp>\n\n---\n\n## Requirements\n\n- Windows 10\u002F11 or Server 2016+ (tested most heavily on Windows 10 21H2 and Server 2019)\n- PowerShell 5.1 or 7+ (Windows only - WPF doesn't exist on Linux or Mac, never will)\n- .NET Framework 4.7.2+ (included with Windows 10 1803+, so you probably have it)\n\nPowerShell 7 is faster for script execution but has some quirks with COM objects. PowerShell 5.1 is more compatible with legacy code. The module detects which version you're running and loads the appropriate binaries.\n\n## Installation\n\n```powershell\n# Install from the PowerShell Gallery (recommended)\nInstall-Module -Name PsUi -Scope CurrentUser\n```\n\nOr install using `Install-PSResource` (PSResourceGet):\n\n```powershell\nInstall-PSResource -Name PsUi\n```\n\n### Manual \u002F Development Install\n\n```powershell\n# Clone and import\ngit clone https:\u002F\u002Fgithub.com\u002Fjlabon2\u002FPsUi.git\nImport-Module .\\PsUi\\PsUi.psd1\n\n# Or copy to your modules folder for persistent availability\n$modulePath = \"$env:USERPROFILE\\Documents\\PowerShell\\Modules\"\nCopy-Item .\\PsUi $modulePath -Recurse\nImport-Module PsUi\n```\n\nThe C# backend is pre-compiled. No build step needed unless you're modifying the source. Import is instant.\n\nIf you want to modify the C# code:\n```powershell\n# Build both net472 (PS 5.1) and net6.0-windows (PS 7+)\n.\\Build-PsUi.ps1\n\n# Reload after building\nRemove-Module PsUi -Force -ErrorAction SilentlyContinue\nImport-Module .\\PsUi\\PsUi.psd1 -Force\n```\n\nNote: .NET types are cached per PowerShell session. If you modify C# classes, you need to restart PowerShell entirely for a clean reload. This is a PowerShell thing, not a PsUi thing.\n\n---\n\n## Examples\n\nThese should run if you paste them.\n\n**Basic form**\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Greeting' -Width 400 -Height 200 -Content {\n    New-UiInput -Label 'Name' -Variable 'name' -Placeholder 'Your name here'\n    New-UiButton -Text 'Greet' -Accent -Action {\n        Write-Host \"Hello, $name!\" -ForegroundColor Green\n    }\n}\n```\n\n**Tabbed interface with different control types**\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Settings' -Width 500 -Height 400 -Content {\n    New-UiTab -Header 'General' -Content {\n        New-UiToggle -Label 'Enable dark mode' -Variable 'darkMode'\n        New-UiSlider -Label 'Volume' -Variable 'volume' -Minimum 0 -Maximum 100 -Default 50\n        New-UiDropdown -Label 'Language' -Variable 'language' -Items @('English', 'Spanish', 'French', 'German')\n    }\n    New-UiTab -Header 'Network' -Content {\n        New-UiInput -Label 'Proxy Server' -Variable 'proxy' -Placeholder 'proxy.example.com'\n        New-UiInput -Label 'Port' -Variable 'port' -InputType Int -Default 8080\n        New-UiToggle -Label 'Use authentication' -Variable 'useAuth'\n    }\n    New-UiTab -Header 'Advanced' -Content {\n        New-UiDatePicker -Label 'Start Date' -Variable 'startDate'\n        New-UiTimePicker -Label 'Start Time' -Variable 'startTime' -Default '09:00'\n        New-UiTextArea -Label 'Notes' -Variable 'notes' -Rows 4\n    }\n    \n    New-UiButton -Text 'Save Settings' -Icon 'Save' -Accent -Action {\n        Write-Host \"Dark Mode: $darkMode\"\n        Write-Host \"Volume: $volume\"\n        Write-Host \"Language: $language\"\n        Write-Host \"Proxy: ${proxy}:${port}\"\n        Write-Host \"Use Auth: $useAuth\"\n    }\n}\n```\n\n**Command wrapper (the fast way)**\n```powershell\nImport-Module PsUi\n\n# Three lines to GUI-ify any command\nNew-UiTool -Command 'Get-ChildItem' -Title 'File Browser' -FolderPickerParameters 'Path'\n```\n\n**Real-world pattern: progress and long-running operations**\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Batch Processor' -Width 500 -Height 300 -Content {\n    New-UiInput -Label 'Items to process' -Variable 'itemCount' -InputType Int -Default 10\n    New-UiProgress -Variable 'progress'\n    New-UiInput -Label 'Status' -Variable 'status' -Default 'Ready'\n    \n    New-UiButton -Text 'Start Processing' -Icon 'Play' -Accent -Action {\n        $total = [int]$itemCount\n        for ($i = 1; $i -le $total; $i++) {\n            $status   = \"Processing item $i of $total...\"\n            $progress = ($i \u002F $total) * 100\n            \n            # Simulate work\n            Start-Sleep -Milliseconds 300\n            Write-Host \"Completed item $i\" -ForegroundColor Cyan\n        }\n        \n        $status   = 'Done!'\n        $progress = 100\n        Write-Host 'All items processed!' -ForegroundColor Green\n    }\n}\n```\n\n**Conditional controls (enable\u002Fdisable based on other values)**\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Conditional Demo' -Width 400 -Height 300 -Content {\n    New-UiToggle -Label 'Enable advanced options' -Variable 'advanced'\n    \n    # This input only enables when the toggle is checked\n    New-UiInput -Label 'Server URL' -Variable 'serverUrl' -EnabledWhen 'advanced' -ClearIfDisabled\n    \n    # This button only enables when the input has content\n    New-UiButton -Text 'Connect' -Icon 'Globe' -Accent -EnabledWhen 'serverUrl' -Action {\n        Write-Host \"Connecting to $serverUrl...\"\n    }\n}\n```\n\n---\n\n## Control Reference\n\n### Layout\n\n| Function | Description |\n|----------|-------------|\n| `New-UiWindow` | Main window with theme support, resizing, custom icons |\n| `New-UiPanel` | Horizontal or vertical grouping |\n| `New-UiCard` | Bordered container with optional header |\n| `New-UiGrid` | Row\u002Fcolumn layout, form mode |\n| `New-UiTab` | Tabbed interface |\n| `New-UiExpander` | Collapsible section with header |\n| `New-UiSeparator` | Visual divider |\n\n### Input Controls\n\n| Function | Description |\n|----------|-------------|\n| `New-UiInput` | Text field with password mode, placeholder, filtering |\n| `New-UiTextArea` | Multi-line text |\n| `New-UiDropdown` | Dropdown selector |\n| `New-UiRadioGroup` | Radio buttons |\n| `New-UiToggle` | On\u002Foff switch |\n| `New-UiSlider` | Numeric slider |\n| `New-UiDatePicker` | Date selection |\n| `New-UiTimePicker` | Time selection |\n| `New-UiCredential` | Username and password fields |\n| `New-UiList` | Selectable list with add\u002Fremove |\n| `New-UiTree` | Hierarchical tree view for nested data |\n\n### List Operations\n\n| Function | Description |\n|----------|-------------|\n| `Add-UiListItem` | Add item |\n| `Remove-UiListItem` | Remove item |\n| `Clear-UiList` | Clear all |\n| `Get-UiListItems` | Return all items |\n\n### Display\n\n| Function | Description |\n|----------|-------------|\n| `New-UiLabel` | Text label (Header, Body, Note styles) |\n| `New-UiImage` | Image display |\n| `New-UiGlyph` | Icon from Segoe MDL2 Assets |\n| `New-UiProgress` | Progress bar |\n| `Set-UiProgress` | Update progress |\n| `New-UiLink` | Clickable hyperlink (opens URL or runs action) |\n| `New-UiChart` | Bar, line, or pie chart on a WPF canvas |\n| `Update-UiChart` | Push new data to an existing chart |\n| `New-UiWebView` | Embedded Chromium browser (WebView2) |\n\n### Buttons\n\n| Function | Description |\n|----------|-------------|\n| `New-UiButton` | Button, async by default |\n| `New-UiAction` | Button with no output window |\n| `New-UiDropdownButton` | Button with menu |\n| `New-UiButtonCard` | Card with header, description, and button |\n| `New-UiActionCard` | Card with no output window |\n\n### Theming\n\n| Function | Description |\n|----------|-------------|\n| `Get-UiThemeTemplate` | Theme color definitions |\n| `Register-UiTheme` | Register a custom theme |\n\n### Dialogs\n\n| Function | Description |\n|----------|-------------|\n| `Show-UiMessageDialog` | Message box |\n| `Show-UiConfirmDialog` | Yes\u002FNo |\n| `Show-UiChoiceDialog` | Multiple choice |\n| `Show-UiInputDialog` | Text prompt |\n| `Show-UiPromptDialog` | Multi-field prompt |\n| `Show-UiCredentialDialog` | Credential prompt |\n| `Show-UiFilePicker` | Open file |\n| `Show-UiFolderPicker` | Select folder |\n| `Show-UiSaveDialog` | Save file |\n| `Show-UiDialog` | Custom dialog |\n| `Show-UiGlyphBrowser` | Browse 400+ icons |\n| `Show-WindowsObjectPicker` | AD picker (domain only) |\n\n### Tools\n\n| Function | Description |\n|----------|-------------|\n| `New-UiTool` | Generate form from command |\n| `Out-Datagrid` | Sortable, filterable grid with -PassThru |\n| `Out-TextEditor` | Text viewer\u002Feditor |\n| `Out-CSVDataGrid` | CSV viewer\u002Feditor |\n| `New-UiChildWindow` | Secondary window |\n| `Invoke-UiAsync` | Run scriptblock in background |\n| `Stop-UiAsync` | Cancel the running background operation |\n| `Register-UiHotkey` | Bind a keyboard shortcut to an action |\n\n### Session & Utility\n\n| Function | Description |\n|----------|-------------|\n| `Get-UiValue` | Read a control's current value by variable name |\n| `Set-UiValue` | Set a control's value by variable name |\n| `Get-PsUiIcon` | Get an icon glyph by name |\n| `Get-PsUiIconList` | List all available icon names |\n| `Write-UiHostDirect` | Write to the real console, bypassing the UI proxy |\n\n---\n\n## Theme Usage\n\nThemes affect all controls in the window. There's a palette button in the titlebar for runtime switching, or you can set it in code.\n\n```powershell\n# Set theme at window creation\nNew-UiWindow -Title 'App' -Theme Dark -Content {\n    New-UiLabel -Text 'Dark mode enabled'\n}\n\n# Register a custom theme based on an existing one\nRegister-UiTheme -Name 'MyTheme' -BasedOn 'Dark' -Colors @{\n    Accent     = '#FF6B35'\n    WindowBg   = '#1A1A2E'\n}\n\n# Get available theme definitions\n$themes = Get-UiThemeTemplate\n$themes.Keys\n```\n\nAvailable themes:\n- **Light** - Default light theme with warm neutral tones\n- **Dark** - Dark background with light text\n- **LightModern** - Light theme with blue accents\n- **OceanBlue** - Deep blue tones\n- **Bespin** - Warm browns inspired by the code editor theme\n- **SolarizedDark** - The classic Solarized palette\n- **Charcoal** - Very dark with subtle contrast\n- **DeepRed** - Dark theme with red accents\n- **Monokai** - Colors from the popular syntax theme\n- **Azure** - Blue and white color scheme\n- **Pearl** - Soft pastel tones\n\nSemantic styles work across themes:\n- `New-UiButton -Accent` - Uses the accent color (stands out)\n- `New-UiCard -Accent` - Highlighted card\n- Validation states (errors, warnings) use semantic colors\n\nCustom themes can be defined by adding entries to the theme definitions in `private\u002FThemeDefinitions.ps1`.\n\n---\n\n## Patterns\n\nCommon patterns for building tools.\n\n### Form Data Collection\n\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'New User' -Content {\n    New-UiCard -Header 'User Details' -Content {\n        New-UiInput -Label 'Name' -Variable 'name'\n        New-UiInput -Label 'Email' -Variable 'email'\n        New-UiDropdown -Label 'Role' -Variable 'role' -Items @('Admin', 'User', 'Guest')\n        New-UiDropdown -Label 'Department' -Variable 'dept' -Items @('Engineering', 'Sales', 'Support', 'HR')\n        New-UiToggle -Label 'Active' -Variable 'isActive' -Checked\n    }\n    \n    New-UiButton -Text 'Create User' -Accent -Action {\n        Write-Host \"Creating user...\"\n        Write-Host \"  Name: $name\"\n        Write-Host \"  Email: $email\"\n        Write-Host \"  Role: $role\"\n        Write-Host \"  Department: $dept\"\n        Write-Host \"  Active: $isActive\"\n        \n        # Your actual user creation code here\n        # New-ADUser -Name $name -Email $email ...\n    }\n}\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fnew-user-form.png\" alt=\"New user form\">\u003C\u002Fp>\n\n### Result Row Actions\n\nWhen you want to show data and let users act on individual rows. The `$_` variable in the action contains the row object.\n\n```powershell\nImport-Module PsUi\n\nNew-UiTool -Command 'Get-Service' -ResultActions @(\n    @{\n        Text   = 'Start'\n        Icon   = 'Play'\n        Action = { \n            Write-Host \"Starting $($_.Name)...\"\n            $_ | Start-Service -WhatIf \n        }\n    }\n    @{\n        Text   = 'Stop'\n        Icon   = 'Cancel'\n        Action = { \n            Write-Host \"Stopping $($_.Name)...\"\n            $_ | Stop-Service -WhatIf \n        }\n    }\n    @{\n        Text   = 'Properties'\n        Icon   = 'Info'\n        Action = { \n            $_ | Format-List * | Out-String | Write-Host\n        }\n    }\n)\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fresult-row-actions.png\" alt=\"Result row actions with Get-Service\">\u003C\u002Fp>\n\n### Multi-Step Wizard\n\nUsing tabs as wizard steps. Not a true wizard (no next\u002Fback buttons wired up) but close enough for most internal tools.\n\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Server Provisioning' -Width 600 -Height 500 -Content {\n    New-UiTab -Header '1. Server Info' -Content {\n        New-UiInput -Label 'Server Name' -Variable 'serverName' -Placeholder 'SRV-APP-001'\n        New-UiDropdown -Label 'Environment' -Variable 'environ' -Items @('Dev', 'Test', 'Staging', 'Prod')\n        New-UiDropdown -Label 'OS' -Variable 'osChoice' -Items @('Windows Server 2019', 'Windows Server 2022', 'RHEL 8', 'Ubuntu 22.04')\n    }\n    New-UiTab -Header '2. Resources' -Content {\n        New-UiSlider -Label 'CPU Cores' -Variable 'cpu' -Minimum 1 -Maximum 16 -Default 4\n        New-UiSlider -Label 'RAM (GB)' -Variable 'ram' -Minimum 4 -Maximum 128 -Default 16\n        New-UiSlider -Label 'Disk (GB)' -Variable 'disk' -Minimum 50 -Maximum 2000 -Default 100\n    }\n    New-UiTab -Header '3. Network' -Content {\n        New-UiDropdown -Label 'VLAN' -Variable 'vlan' -Items @('VLAN-10-Servers', 'VLAN-20-DMZ', 'VLAN-30-Internal')\n        New-UiToggle -Label 'Static IP' -Variable 'staticIp'\n        New-UiInput -Label 'IP Address' -Variable 'ipAddr' -EnabledWhen 'staticIp' -Placeholder '10.0.1.x'\n    }\n    New-UiTab -Header '4. Provision' -Content {\n        New-UiLabel -Text 'Review your selections and click Provision to create the VM.' -Style Body\n        New-UiButton -Text 'Provision Server' -Icon 'Play' -Accent -Action {\n            Write-Host \"Provisioning $serverName...\" -ForegroundColor Cyan\n            Write-Host \"Environment: $environ | OS: $osChoice\"\n            Write-Host \"CPU: $cpu cores, RAM: ${ram}GB, Disk: ${disk}GB\"\n            \n            # Your PowerCLI \u002F Azure \u002F AWS provisioning code here\n            Write-Progress -Activity 'Provisioning' -Status 'Creating VM...' -PercentComplete 50\n            Start-Sleep -Seconds 2\n            Write-Progress -Activity 'Provisioning' -Completed\n            \n            Write-Host \"Server $serverName provisioned!\" -ForegroundColor Green\n        }\n    }\n}\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fserver-provisioning.png\" alt=\"Server provisioning wizard\">\u003C\u002Fp>\n\n### Connecting to Remote Systems\n\nPattern for tools that need credentials and connect to external systems.\n\n```powershell\nImport-Module PsUi\n\nNew-UiWindow -Title 'Remote Server Tool' -Width 600 -Height 400 -Content {\n    New-UiCard -Header 'Connection' -Content {\n        New-UiInput -Label 'Server' -Variable 'server' -Placeholder 'server.domain.local'\n        New-UiCredential -Label 'Credentials' -Variable 'creds' -DefaultUser \"$env:USERDOMAIN\\$env:USERNAME\"\n        New-UiToggle -Label 'Use SSL' -Variable 'useSsl' -Checked\n    }\n    \n    New-UiPanel -LayoutStyle Wrap -Content {\n        New-UiButton -Text 'Test Connection' -Icon 'Globe' -Action {\n            if (!$server) { Write-Host 'Enter a server name' -ForegroundColor Yellow; return }\n            if (!$creds) { Write-Host 'Enter credentials' -ForegroundColor Yellow; return }\n            \n            Write-Host \"Testing connection to $server...\"\n            try {\n                # Your connection test here\n                $session = New-PSSession -ComputerName $server -Credential $creds -ErrorAction Stop\n                Write-Host \"Connected successfully!\" -ForegroundColor Green\n                Remove-PSSession $session\n            }\n            catch {\n                Write-Host \"Connection failed: $($_.Exception.Message)\" -ForegroundColor Red\n            }\n        }\n        \n        New-UiButton -Text 'Run Report' -Icon 'Document' -Accent -Action {\n            if (!$server -or !$creds) { \n                Write-Host 'Connect to a server first' -ForegroundColor Yellow\n                return \n            }\n            \n            Write-Host \"Running report on $server...\"\n            # Your report code here\n        }\n    }\n}\n```\n\n\u003Cp align=\"center\">\u003Cimg src=\"docs\u002Fimages\u002Fremote-server.png\" alt=\"Remote server connection tool\">\u003C\u002Fp>\n\n---\n\n## Limitations\n\n- **Windows only.** WPF is a Windows technology. It will never run on Linux or Mac.\n\n- **ISE is not supported.** The PowerShell ISE has threading quirks that make WPF unreliable. Use Windows Terminal, pwsh.exe, or VS Code's terminal.\n\n- **Large datasets degrade performance.** Out-Datagrid handles 10k rows fine. 50k rows gets sluggish. 100k rows will make you wait. Filter before displaying large sets.\n\n- **Not a proper MVVM framework.** No INotifyPropertyChanged, no data binding expressions, no command pattern. PsUi is for internal tools, not production apps.\n\n- **Variable sync has boundaries.** Values sync at action start and end, not continuously. If you need real-time binding, use events manually.\n\n- **Live objects don't cross runspaces.** Database connections, file handles, COM objects - they don't travel. Design around it.\n\n- **No designer.** You write code, you run it, you see what it looks like.\n\n- **Threading bugs may exist.** The core async system has been stress-tested pretty hard, but threading bugs are notoriously good at hiding. If you find one, file an issue.\n\n---\n\n## Architecture\n\nThe module is split into three layers:\n\n**C# Backend** (`src\u002F`, compiled to `PsUi\u002Flib\u002F`) - Runspace pooling, dispatcher marshalling, host interception, thread-safe control proxies. The C# has grown as edge cases surfaced. Dual-targets net472 (PowerShell 5.1) and net6.0-windows (PowerShell 7+).\n\nKey classes:\n- `AsyncExecutor` - Runs scripts on background threads, routes Write-Host\u002FProgress\u002FError to UI events\n- `SessionContext` - Per-window state isolation using `ConcurrentDictionary`\n- `StateHydrationEngine` - Extracts control values into variables, syncs them back after execution\n- `ThreadSafeControlProxy` - Auto-marshals property access to dispatcher thread\n\n**PowerShell Functions** (`PsUi\u002Fpublic\u002F`, `PsUi\u002Fprivate\u002F`) - The DSL layer. `New-UiWindow`, `New-UiButton`, etc. These are thin wrappers that create WPF controls and wire them to the C# backend.\n\n**State Management** - Two APIs depending on your needs:\n- **Variable hydration** (default): Control values become PowerShell variables in your action. Read `$userName`, write `$status = 'Done'`.\n- **Session dictionary** (advanced): Direct access via `$session = Get-UiSession; $session.Variables['controlName']`. For when you need more control.\n\nThe architecture exists because PowerShell's threading model is hostile to GUIs. Scripts expect to block. WPF expects to be responsive. The C# layer bridges that gap by running your scripts in isolated runspaces and marshalling all the I\u002FO back to the UI thread.\n\n---\n\n## Contributing\n\nIf something breaks, file an issue with your PowerShell version, a sanitized copy of the script causing the problem, and the error message.\n\nPull requests welcome.\n\n---\n\n## License\n\nMIT. Use it for whatever. Attribution appreciated but not required.\n\n---\n\n*For when someone asks \"can you make that a GUI?\" and the honest answer is \"I guess.\"*\n","PsUi 是一个用于在 PowerShell 中构建用户界面的库，旨在简化 UI 开发过程。它允许开发者将现有的脚本转换为交互式工具或从零开始创建自定义表单，并通过 PsUi 函数定义布局，在附带的脚本块中编写标准 PowerShell 代码。该框架自动处理多线程问题，包括异步执行、输出路由和保持 UI 响应性，无需手动编写复杂的线程代码。适用于需要快速开发具备图形界面的自动化工具或管理控制台等场景，特别适合那些希望避免传统方法（如 WinForms 或手动管理线程）复杂性的 PowerShell 用户。","2026-06-11 03:30:52","CREATED_QUERY"]