
Powershell UI Architect Skill
- 110 installs
- 404kidwiz/claude-supercode-skills
Design and build terminal UIs and command-line interfaces using PowerShell.
About
Skill for architecting and building rich terminal UIs in PowerShell. Developers building CLI tools use this to create polished, interactive command-line interfaces.
- CLI design
- PowerShell UI
- Terminal interfaces
Powershell Ui Architect by the numbers
- 110 all-time installs (skills.sh)
- Ranked #253 of 566 CLI & Terminal skills by installs in the Skillselion catalog
- Data as of Aug 11, 2026 (Skillselion catalog sync)
npx skills add https://github.com/404kidwiz/claude-supercode-skills --skill powershell-ui-architectAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 110 |
|---|---|
| Repository | 404kidwiz/claude-supercode-skills ↗ |
What it does
Design and build terminal UIs and command-line interfaces using PowerShell.
Files
PowerShell UI Architect
Purpose
Provides expertise in building graphical user interfaces (GUI) and terminal user interfaces (TUI) with PowerShell. Specializes in WinForms, WPF, and console-based TUI frameworks for creating user-friendly PowerShell tools.
When to Use
- Building PowerShell tools with GUI
- Creating WinForms applications
- Developing WPF interfaces for scripts
- Building terminal user interfaces (TUI)
- Adding dialogs to automation scripts
- Creating interactive admin tools
- Building configuration wizards
- Implementing progress displays
Quick Start
Invoke this skill when:
- Creating GUIs for PowerShell scripts
- Building WinForms or WPF interfaces
- Developing terminal-based UIs
- Adding interactive dialogs to tools
- Creating admin tool interfaces
Do NOT invoke when:
- Cross-platform CLI tools → use
/cli-developer - PowerShell module design → use
/powershell-module-architect - Web interfaces → use
/frontend-design - Windows app development (non-PS) → use
/windows-app-developer
Decision Framework
UI Type Needed?
├── Simple Dialog
│ └── WinForms MessageBox / InputBox
├── Full Windows App
│ ├── Simple layout → WinForms
│ └── Rich UI → WPF with XAML
├── Console/Terminal
│ ├── Simple menu → Write-Host + Read-Host
│ └── Rich TUI → Terminal.Gui / PSReadLine
└── Cross-Platform
└── Terminal-based onlyCore Workflows
1. WinForms Application
1. Add System.Windows.Forms assembly 2. Create Form object 3. Add controls (buttons, text boxes) 4. Wire up event handlers 5. Configure layout 6. Show form with ShowDialog()
2. WPF Interface
1. Define XAML layout 2. Load XAML in PowerShell 3. Get control references 4. Add event handlers 5. Implement logic 6. Display window
3. TUI with Terminal.Gui
1. Install Terminal.Gui module 2. Initialize application 3. Create window and views 4. Add controls (buttons, lists, text) 5. Handle events 6. Run main loop
Best Practices
- Keep UI code separate from logic
- Use XAML for complex WPF layouts
- Handle errors gracefully with user feedback
- Provide progress indication for long operations
- Test on target Windows versions
- Use appropriate UI for audience (GUI vs TUI)
Anti-Patterns
| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| UI logic mixed with business logic | Hard to maintain | Separate concerns |
| Blocking UI thread | Frozen interface | Use runspaces/jobs |
| No input validation | Crashes, bad data | Validate before use |
| Hardcoded sizes | Scaling issues | Use anchoring/docking |
| No error messages | Confused users | Friendly error dialogs |
PowerShell GUI Patterns
Overview
This guide covers GUI development patterns for PowerShell, including WinForms, WPF, and Terminal User Interfaces (TUI).
WinForms Patterns
Basic Form Structure
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Show-WinFormsDialog {
$form = New-Object System.Windows.Forms.Form
$form.Text = "PowerShell WinForms"
$form.Width = 400
$form.Height = 300
$form.StartPosition = "CenterScreen"
# Add controls
$button = New-Object System.Windows.Forms.Button
$button.Text = "Click Me"
$button.Location = New-Object System.Drawing.Point(150, 200)
$button.Size = New-Object System.Drawing.Size(100, 30)
$button.Add_Click({
[System.Windows.Forms.MessageBox]::Show("Button clicked!")
})
$form.Controls.Add($button)
$form.ShowDialog()
}Data Binding
function Show-BoundData {
# Create data source
$data = @(
[PSCustomObject]@{ Name = "Item 1"; Value = 100 },
[PSCustomObject]@{ Name = "Item 2"; Value = 200 },
[PSCustomObject]@{ Name = "Item 3"; Value = 300 }
)
$form = New-Object System.Windows.Forms.Form
$form.Text = "Data Binding Example"
# Create DataGridView
$dataGridView = New-Object System.Windows.Forms.DataGridView
$dataGridView.Location = New-Object System.Drawing.Point(20, 20)
$dataGridView.Size = New-Object System.Drawing.Size(340, 200)
$dataGridView.AutoGenerateColumns = $true
$dataGridView.DataSource = $data
$form.Controls.Add($dataGridView)
$form.ShowDialog()
}Event Handling
function Show-EventHandling {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Event Handling"
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(20, 20)
$textBox.Size = New-Object System.Drawing.Size(340, 20)
# Text changed event
$textBox.Add_TextChanged({
param($sender, $e)
Write-Host "Text changed: $($sender.Text)"
})
# Key press event
$textBox.Add_KeyPress({
param($sender, $e)
if ($e.KeyChar -eq [char]13) {
[System.Windows.Forms.MessageBox]::Show("Enter pressed")
}
})
$form.Controls.Add($textBox)
$form.ShowDialog()
}WPF Patterns
XAML-Based WPF
$xaml = @"
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PowerShell WPF" Height="300" Width="400">
<Grid>
<Button Name="btnClick" Content="Click Me"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="100" Height="30"/>
</Grid>
</Window>
"@
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
$window = [System.Windows.Markup.XamlReader]::Load($reader)
# Add event handler
$btnClick = $window.FindName("btnClick")
$btnClick.Add_Click({
[System.Windows.MessageBox]::Show("Button clicked!")
})
$window.ShowDialog()MVVM Pattern
# ViewModel
class MyViewModel : System.ComponentModel.INotifyPropertyChanged {
[string]$_name
[string]$Name {
get { return $this._name }
set {
if ($this._name -ne $value) {
$this._name = $value
$this.OnPropertyChanged("Name")
}
}
}
[System.Collections.ObjectModel.ObservableCollection[string]]$Items
MyViewModel() {
$this.Items = [System.Collections.ObjectModel.ObservableCollection[string]]::new()
$this.Items.Add("Item 1")
$this.Items.Add("Item 2")
}
[void]$OnPropertyChanged($propertyName) {
if ($this.PropertyChanged -ne $null) {
$this.PropertyChanged.Invoke($this, [System.ComponentModel.PropertyChangedEventArgs]::new($propertyName))
}
}
event PropertyChanged($sender, $e)
hidden [System.ComponentModel.PropertyChangedEventHandler]$PropertyChanged
}Data Binding in WPF
$xaml = @"
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Text="Enter Name:"/>
<TextBox Name="txtName" Height="25"/>
<TextBlock Name="lblName" Height="25" Text="{Binding Name}"/>
</StackPanel>
</Window>
"@
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
$window = [System.Windows.Markup.XamlReader]::Load($reader)
# Create ViewModel
$viewModel = [MyViewModel]::new()
$window.DataContext = $viewModel
# Bind TextBox
$txtName = $window.FindName("txtName")
$txtName.SetBinding([System.Windows.Controls.TextBox]::TextProperty, "Name")
$window.ShowDialog()TUI (Terminal User Interface) Patterns
Basic TUI Menu
function Show-TuiMenu {
$menuItems = @(
@{ Label = "Option 1"; Action = { Write-Host "Selected Option 1" } },
@{ Label = "Option 2"; Action = { Write-Host "Selected Option 2" } },
@{ Label = "Option 3"; Action = { Write-Host "Selected Option 3" } }
)
while ($true) {
Clear-Host
Write-Host "=== Main Menu ===" -ForegroundColor Cyan
Write-Host ""
for ($i = 0; $i -lt $menuItems.Count; $i++) {
Write-Host " [$($i + 1)] $($menuItems[$i].Label)" -ForegroundColor White
}
Write-Host " [Q] Quit" -ForegroundColor Red
Write-Host ""
$selection = Read-Host "Select option"
if ($selection -eq 'q' -or $selection -eq 'Q') {
break
}
$selectedIndex = 0
if ([int]::TryParse($selection, [ref]$selectedIndex)) {
$selectedIndex--
if ($selectedIndex -ge 0 -and $selectedIndex -lt $menuItems.Count) {
Clear-Host
& $menuItems[$selectedIndex].Action
Read-Host "Press Enter to continue"
}
}
}
}TUI Table Display
function Show-TuiTable {
$data = @(
@{ Name = "Item 1"; Status = "Active"; Value = 100 },
@{ Name = "Item 2"; Status = "Inactive"; Value = 200 },
@{ Name = "Item 3"; Status = "Active"; Value = 300 }
)
Clear-Host
Write-Host "=== Data Table ===" -ForegroundColor Cyan
Write-Host ""
$data | Format-Table -AutoSize
Write-Host ""
Read-Host "Press Enter to continue"
}TUI Progress Bar
function Show-TuiProgress {
$totalItems = 100
for ($i = 0; $i -le $totalItems; $i++) {
$progress = ($i / $totalItems) * 100
$filled = [Math]::Floor(50 * $progress / 100)
$empty = 50 - $filled
$bar = "█" * $filled + "░" * $empty
Write-Host "`r[$bar] $progress%" -NoNewline -ForegroundColor Green
Start-Sleep -Milliseconds 50
}
Write-Host "`nComplete!" -ForegroundColor Green
}Framework Selection
When to Use WinForms
Pros:
- Simple to implement
- Lightweight
- Good for simple dialogs
Cons:
- Limited styling options
- Not modern looking
- Limited data binding
Use Cases:
- Simple input forms
- Utility dialogs
- Quick prototypes
When to Use WPF
Pros:
- Modern appearance
- Rich styling options
- Advanced data binding
- MVVM pattern support
Cons:
- Steeper learning curve
- More complex to implement
- Heavier than WinForms
Use Cases:
- Complex applications
- Data-heavy interfaces
- Professional-looking GUIs
- MVVM pattern required
When to Use TUI
Pros:
- Cross-platform compatible
- Lightweight
- No GUI dependencies
- Works over SSH
Cons:
- Limited interaction options
- No graphics
- Terminal-based only
Use Cases:
- Server administration
- SSH/remote sessions
- Command-line tools
- Cross-platform compatibility needed
Common Patterns
Modal Dialogs
function Show-ModalDialog {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Modal Dialog"
$form.ShowDialog() | Out-Null
}Asynchronous Operations
function Show-Progress {
$form = New-Object System.Windows.Forms.Form
$form.Text = "Processing..."
$progressBar = New-Object System.Windows.Forms.ProgressBar
$progressBar.Location = New-Object System.Drawing.Point(20, 50)
$progressBar.Size = New-Object System.Drawing.Size(340, 20)
$form.Controls.Add($progressBar)
# Start operation in background
$job = Start-Job -ScriptBlock {
Start-Sleep -Seconds 5
}
# Update progress
while ($job.State -eq 'Running') {
$progressBar.Value += 10
$form.Refresh()
Start-Sleep -Milliseconds 500
}
Remove-Job $job
$form.ShowDialog()
}Best Practices
1. Framework Selection: Choose the right framework for your needs 2. Event Handling: Implement proper event handlers 3. Error Handling: Add try-catch blocks for user interactions 4. Responsiveness: Keep UI responsive during operations 5. Accessibility: Consider accessibility features 6. Cross-Platform: Use TUI for cross-platform needs 7. Testing: Test GUI applications thoroughly 8. Performance: Optimize for performance with large datasets
Resources
<#
.SYNOPSIS
Builds WPF-based GUI applications in PowerShell
.DESCRIPTION
Creates WPF applications with XAML, MVVM patterns, and data binding
.PARAMETER XamlPath
Path to XAML file for WPF UI definition
.PARAMETER ViewModel
Hashtable containing ViewModel properties
.PARAMETER Show
Display the WPF window
.EXAMPLE
.\build_wpf.ps1 -XamlPath "./MainWindow.xaml" -Show
#>
#Requires -Version 5.1
#Requires -Assembly PresentationFramework, PresentationCore, WindowsBase
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[ValidateScript({
if (-not (Test-Path $_)) {
throw "XAML file does not exist: $_"
}
$true
})]
[string]$XamlPath,
[Parameter(Mandatory=$false)]
[hashtable]$ViewModel,
[Parameter(Mandatory=$false)]
[switch]$Show,
[Parameter(Mandatory=$false)]
[switch]$GenerateXaml,
[Parameter(Mandatory=$false)]
[string]$OutputXamlPath,
[Parameter(Mandatory=$false)]
[string]$WindowTitle = "WPF Application",
[Parameter(Mandatory=$false)]
[int]$Width = 400,
[Parameter(Mandatory=$false)]
[int]$Height = 300
)
function Initialize-WpfAssemblies {
Write-Verbose "Loading WPF assemblies"
try {
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
Write-Verbose "WPF assemblies loaded successfully"
}
catch {
Write-Error "Failed to load WPF assemblies: $_"
throw
}
}
function New-WpfWindow {
param(
[string]$Title,
[int]$WindowWidth,
[int]$WindowHeight
)
Write-Verbose "Creating WPF window"
$window = New-Object System.Windows.Window
$window.Title = $Title
$window.Width = $WindowWidth
$window.Height = $WindowHeight
$window.WindowStartupLocation = 'CenterScreen'
$window.ResizeMode = 'CanResize'
return $window
}
function Read-XamlFile {
param(
[string]$Path
)
Write-Verbose "Reading XAML from: $Path"
try {
$xamlContent = Get-Content -Path $Path -Raw -ErrorAction Stop
# Remove BOM if present
if ($xamlContent[0] -eq 0xEF -and $xamlContent[1] -eq 0xBB -and $xamlContent[2] -eq 0xBF) {
$xamlContent = $xamlContent.Substring(3)
}
return $xamlContent
}
catch {
Write-Error "Failed to read XAML file: $_"
throw
}
}
function Convert-XamlToWindow {
param(
[string]$Xaml
)
Write-Verbose "Converting XAML to WPF window"
try {
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($Xaml))
$window = [System.Windows.Markup.XamlReader]::Load($reader)
return $window
}
catch {
Write-Error "Failed to parse XAML: $_"
throw
}
}
function Set-WpfDataContext {
param(
[System.Windows.Window]$Window,
[hashtable]$Data
)
Write-Verbose "Setting data context"
if ($Data) {
$Window.DataContext = $Data
Write-Verbose "Data context set with $($Data.Count) properties"
}
}
function Find-WpfElement {
param(
[System.Windows.Window]$Window,
[string]$Name
)
Write-Verbose "Finding element: $Name"
try {
$element = $Window.FindName($Name)
return $element
}
catch {
Write-Warning "Could not find element: $Name"
return $null
}
}
function Add-WpfEventHandler {
param(
[System.Windows.Window]$Window,
[string]$ElementName,
[string]$EventName,
[scriptblock]$Handler
)
Write-Verbose "Adding event handler: $ElementName.$EventName"
$element = Find-WpfElement -Window $Window -Name $ElementName
if (-not $element) {
Write-Warning "Element not found: $ElementName"
return
}
try {
$eventInfo = $element.GetType().GetEvent($EventName)
$eventInfo.AddEventHandler($element, $Handler)
Write-Verbose "Event handler added successfully"
}
catch {
Write-Warning "Failed to add event handler: $_"
}
}
function Update-WpfBinding {
param(
[System.Windows.Window]$Window,
[string]$ElementName,
[string]$PropertyName,
[object]$Value
)
Write-Verbose "Updating binding: $ElementName.$PropertyName"
$element = Find-WpfElement -Window $Window -Name $ElementName
if ($element) {
$element.GetType().GetProperty($PropertyName).SetValue($element, $Value)
}
}
function New-WpfXamlTemplate {
param(
[string]$Title,
[int]$WindowWidth,
[int]$WindowHeight
)
Write-Verbose "Generating WPF XAML template"
$xaml = @"
<Window x:Class="$Title.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="$Title"
Width="$WindowWidth"
Height="$WindowHeight"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Header -->
<StackPanel Grid.Row="0" Background="#FF2D2D30" Padding="10">
<TextBlock Text="$Title" FontSize="18" Foreground="White" FontWeight="Bold"/>
</StackPanel>
<!-- Main Content -->
<StackPanel Grid.Row="1" Margin="20">
<TextBlock Text="Content Area" FontSize="14" Margin="0,0,0,10"/>
<TextBox Name="txtContent" Height="100" TextWrapping="Wrap" AcceptsReturn="True"/>
</StackPanel>
<!-- Footer -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="20">
<Button Name="btnOk" Content="OK" Width="80" Margin="0,0,10,0"/>
<Button Name="btnCancel" Content="Cancel" Width="80"/>
</StackPanel>
</Grid>
</Window>
"@
return $xaml
}
function New-MvvmViewModel {
param(
[hashtable]$Properties,
[hashtable]$Commands
)
Write-Verbose "Creating MVVM ViewModel"
$viewModel = [PSCustomObject]@{
Properties = $Properties
Commands = $Commands
}
# Implement INotifyPropertyChanged
$viewModel | Add-Member -Name PropertyChanged -MemberType ScriptProperty -Value {
param($property)
Write-Verbose "Property changed: $property"
}
return $viewModel
}
function Show-WpfDialog {
param(
[System.Windows.Window]$Window
)
Write-Verbose "Showing WPF dialog"
try {
$app = New-Object System.Windows.Application
$app.Run($Window) | Out-Null
Write-Verbose "WPF dialog closed"
}
catch {
Write-Error "Failed to show WPF dialog: $_"
throw
}
}
try {
Write-Verbose "Starting WPF build process"
Initialize-WpfAssemblies
$window = $null
if ($XamlPath) {
$xamlContent = Read-XamlFile -Path $XamlPath
$window = Convert-XamlToWindow -Xaml $xamlContent
Set-WpfDataContext -Window $window -Data $ViewModel
Write-Host "WPF window loaded from XAML"
}
elseif ($GenerateXaml) {
$xamlTemplate = New-WpfXamlTemplate -Title $WindowTitle -WindowWidth $Width -WindowHeight $Height
if ($OutputXamlPath) {
Set-Content -Path $OutputXamlPath -Value $xamlTemplate -Encoding UTF8
Write-Host "XAML template generated: $OutputXamlPath"
}
$window = Convert-XamlToWindow -Xaml $xamlTemplate
Write-Host "WPF window created from template"
}
else {
$window = New-WpfWindow -Title $WindowTitle -WindowWidth $Width -WindowHeight $Height
# Add basic content
$stackPanel = New-Object System.Windows.Controls.StackPanel
$window.Content = $stackPanel
$label = New-Object System.Windows.Controls.Label
$label.Content = "WPF Application"
$label.FontSize = 16
$stackPanel.Children.Add($label) | Out-Null
Write-Host "Basic WPF window created"
}
if ($ViewModel) {
Set-WpfDataContext -Window $window -Data $ViewModel
}
if ($Show) {
Show-WpfDialog -Window $window
}
else {
Write-Host "WPF application built successfully"
Write-Host "Title: $WindowTitle"
Write-Host "Size: ${Width}x$Height"
}
Write-Verbose "WPF build completed"
}
catch {
Write-Error "WPF build failed: $_"
exit 1
}
finally {
Write-Verbose "Build WPF script completed"
}
Export-ModuleMember -Function New-WpfWindow, Read-XamlFile, Convert-XamlToWindow, Set-WpfDataContext
<#
.SYNOPSIS
Creates WinForms-based GUI applications in PowerShell
.DESCRIPTION
Generates WinForms GUI templates with controls, event handlers, and data binding
.PARAMETER FormTitle
Title of the form
.PARAMETER Width
Form width in pixels
.PARAMETER Height
Form height in pixels
.PARAMETER Controls
Array of control definitions
.EXAMPLE
.\create_winforms.ps1 -FormTitle "My App" -Width 400 -Height 300
#>
#Requires -Version 5.1
#Requires -Assembly System.Windows.Forms, System.Drawing
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$FormTitle,
[Parameter(Mandatory=$false)]
[int]$Width = 400,
[Parameter(Mandatory=$false)]
[int]$Height = 300,
[Parameter(Mandatory=$false)]
[hashtable[]]$Controls = @(),
[Parameter(Mandatory=$false)]
[ValidateSet('FixedSingle', 'Fixed3D', 'FixedDialog', 'Sizable', 'FixedToolWindow', 'SizableToolWindow')]
[string]$FormBorderStyle = 'Sizable',
[Parameter(Mandatory=$false)]
[ValidateSet('Normal', 'Minimized', 'Maximized', 'CenterScreen', 'WindowsDefaultLocation', 'WindowsDefaultBounds', 'CenterParent')]
[string]$StartPosition = 'CenterScreen',
[Parameter(Mandatory=$false)]
[switch]$Show,
[Parameter(Mandatory=$false)]
[switch]$GenerateScript,
[Parameter(Mandatory=$false)]
[string]$OutputPath
)
function New-WinFormsForm {
param(
[string]$Title,
[int]$FormWidth,
[int]$FormHeight,
[string]$BorderStyle,
[string]$StartPos
)
Write-Verbose "Creating WinForms form"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = $Title
$form.Width = $FormWidth
$form.Height = $FormHeight
$form.FormBorderStyle = $BorderStyle
$form.StartPosition = $StartPos
return $form
}
function New-WinFormsButton {
param(
[string]$Text,
[int]$X,
[int]$Y,
[int]$Width = 100,
[int]$Height = 30,
[scriptblock]$OnClick
)
Write-Verbose "Creating button: $Text"
$button = New-Object System.Windows.Forms.Button
$button.Text = $Text
$button.Location = New-Object System.Drawing.Point($X, $Y)
$button.Size = New-Object System.Drawing.Size($Width, $Height)
if ($OnClick) {
$button.Add_Click($OnClick)
}
return $button
}
function New-WinFormsTextBox {
param(
[string]$Text = '',
[int]$X,
[int]$Y,
[int]$Width = 200,
[int]$Height = 20,
[switch]$Multiline,
[switch]$ReadOnly
)
Write-Verbose "Creating textbox"
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Text = $Text
$textBox.Location = New-Object System.Drawing.Point($X, $Y)
$textBox.Size = New-Object System.Drawing.Size($Width, $Height)
$textBox.Multiline = $Multiline
$textBox.ReadOnly = $ReadOnly
return $textBox
}
function New-WinFormsLabel {
param(
[string]$Text,
[int]$X,
[int]$Y,
[int]$Width = 100,
[int]$Height = 20
)
Write-Verbose "Creating label: $Text"
$label = New-Object System.Windows.Forms.Label
$label.Text = $Text
$label.Location = New-Object System.Drawing.Point($X, $Y)
$label.Size = New-Object System.Drawing.Size($Width, $Height)
return $label
}
function New-WinFormsListBox {
param(
[int]$X,
[int]$Y,
[int]$Width = 200,
[int]$Height = 100
)
Write-Verbose "Creating listbox"
$listBox = New-Object System.Windows.Forms.ListBox
$listBox.Location = New-Object System.Drawing.Point($X, $Y)
$listBox.Size = New-Object System.Drawing.Size($Width, $Height)
return $listBox
}
function New-WinFormsComboBox {
param(
[int]$X,
[int]$Y,
[int]$Width = 150,
[int]$Height = 20,
[switch]$DropDownStyle
)
Write-Verbose "Creating combobox"
$comboBox = New-Object System.Windows.Forms.ComboBox
$comboBox.Location = New-Object System.Drawing.Point($X, $Y)
$comboBox.Size = New-Object System.Drawing.Size($Width, $Height)
if ($DropDownStyle) {
$comboBox.DropDownStyle = 'DropDownList'
}
return $comboBox
}
function Add-ControlToForm {
param(
[System.Windows.Forms.Form]$Form,
[hashtable]$ControlDef
)
Write-Verbose "Adding control: $($ControlDef.Type)"
$type = $ControlDef.Type
$x = $ControlDef.X
$y = $ControlDef.Y
switch ($type) {
'Button' {
$control = New-WinFormsButton -Text $ControlDef.Text -X $x -Y $y `
-Width $ControlDef.Width -Height $ControlDef.Height
}
'TextBox' {
$control = New-WinFormsTextBox -Text $ControlDef.Text -X $x -Y $y `
-Width $ControlDef.Width -Height $ControlDef.Height `
-Multiline:$ControlDef.Multiline -ReadOnly:$ControlDef.ReadOnly
}
'Label' {
$control = New-WinFormsLabel -Text $ControlDef.Text -X $x -Y $y `
-Width $ControlDef.Width -Height $ControlDef.Height
}
'ListBox' {
$control = New-WinFormsListBox -X $x -Y $y -Width $ControlDef.Width -Height $ControlDef.Height
}
'ComboBox' {
$control = New-WinFormsComboBox -X $x -Y $y -Width $ControlDef.Width -Height $ControlDef.Height
}
default {
Write-Warning "Unknown control type: $type"
return
}
}
if ($ControlDef.Name) {
$control.Name = $ControlDef.Name
$Form.Controls.Add($control)
# Add to form's tag for easy access
if (-not $Form.Tag) {
$Form.Tag = @{}
}
$Form.Tag[$ControlDef.Name] = $control
}
else {
$Form.Controls.Add($control)
}
}
function Show-WinFormsDialog {
param(
[System.Windows.Forms.Form]$Form
)
Write-Verbose "Showing form dialog"
try {
$result = $Form.ShowDialog()
Write-Verbose "Form closed with result: $result"
return $result
}
catch {
Write-Error "Form display failed: $_"
throw
}
}
function Export-WinFormsScript {
param(
[string]$Title,
[int]$FormWidth,
[int]$FormHeight,
[hashtable[]]$ControlList,
[string]$OutputFile
)
Write-Verbose "Exporting WinForms script"
$scriptContent = @"
<#
.SYNOPSIS
Auto-generated WinForms application
.DESCRIPTION
Title: $Title
Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
#>
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
`$form = New-Object System.Windows.Forms.Form
`$form.Text = "$Title"
`$form.Width = $FormWidth
`$form.Height = $FormHeight
`$form.StartPosition = "CenterScreen"
"@
foreach ($control in $ControlList) {
$type = $control.Type
$x = $control.X
$y = $control.Y
$width = $control.Width
$height = $control.Height
switch ($type) {
'Button' {
$scriptContent += @"
`$$($control.Name) = New-Object System.Windows.Forms.Button
`$$($control.Name).Text = "$($control.Text)"
`$$($control.Name).Location = New-Object System.Drawing.Point($x, $y)
`$$($control.Name).Size = New-Object System.Drawing.Size($width, $height)
`$$($control.Name).Add_Click({
# Add click handler logic here
Write-Host "Button clicked"
})
`$form.Controls.Add(`$$($control.Name))
"@
}
'TextBox' {
$scriptContent += @"
`$$($control.Name) = New-Object System.Windows.Forms.TextBox
`$$($control.Name).Text = "$($control.Text)"
`$$($control.Name).Location = New-Object System.Drawing.Point($x, $y)
`$$($control.Name).Size = New-Object System.Drawing.Size($width, $height)
$($control.Multiline ? "`$$($control.Name).Multiline = `$true`n" : "")
$($control.ReadOnly ? "`$$($control.Name).ReadOnly = `$true`n" : "")
`$form.Controls.Add(`$$($control.Name))
"@
}
'Label' {
$scriptContent += @"
`$$($control.Name) = New-Object System.Windows.Forms.Label
`$$($control.Name).Text = "$($control.Text)"
`$$($control.Name).Location = New-Object System.Drawing.Point($x, $y)
`$$($control.Name).Size = New-Object System.Drawing.Size($width, $height)
`$form.Controls.Add(`$$($control.Name))
"@
}
}
}
$scriptContent += @"
`$form.ShowDialog()
"@
if ($OutputFile) {
Set-Content -Path $OutputFile -Value $scriptContent -Encoding UTF8
Write-Host "Script exported to: $OutputFile"
}
return $scriptContent
}
try {
Write-Verbose "Starting WinForms creation: $FormTitle"
$form = New-WinFormsForm -Title $FormTitle -FormWidth $Width -FormHeight $Height -BorderStyle $FormBorderStyle -StartPos $StartPosition
foreach ($control in $Controls) {
Add-ControlToForm -Form $form -ControlDef $control
}
if ($GenerateScript) {
$scriptPath = if ($OutputPath) { $OutputPath } else { "$FormTitle.ps1" }
Export-WinFormsScript -Title $FormTitle -FormWidth $Width -FormHeight $Height -ControlList $Controls -OutputFile $scriptPath
}
if ($Show) {
Show-WinFormsDialog -Form $form
}
else {
Write-Host "WinForms form created successfully"
Write-Host "Title: $FormTitle"
Write-Host "Size: ${Width}x$Height"
Write-Host "Controls: $($Controls.Count)"
}
Write-Verbose "WinForms creation completed"
}
catch {
Write-Error "WinForms creation failed: $_"
exit 1
}
finally {
Write-Verbose "Create WinForms script completed"
}
Export-ModuleMember -Function New-WinFormsForm, New-WinFormsButton, New-WinFormsTextBox, New-WinFormsLabel
<#
.SYNOPSIS
Designs Terminal User Interface (TUI) applications in PowerShell
.DESCRIPTION
Creates console-based UI with menus, forms, tables, and interactive controls
.PARAMETER Title
Application title
.PARAMETER MenuItems
Array of menu item definitions
.PARAMETER TableData
Data to display in table format
.EXAMPLE
.\design_tui.ps1 -Title "My App" -MenuItems @(@{Label="Option 1";Action={}})
#>
#Requires -Version 5.1
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[string]$Title = "TUI Application",
[Parameter(Mandatory=$false)]
[hashtable[]]$MenuItems,
[Parameter(Mandatory=$false)]
[object[]]$TableData,
[Parameter(Mandatory=$false)]
[hashtable]$FormFields,
[Parameter(Mandatory=$false)]
[ValidateSet('Menu', 'Table', 'Form', 'Progress', 'Wizard')]
[string]$Mode = 'Menu',
[Parameter(Mandatory=$false)]
[switch]$ClearScreen,
[Parameter(Mandatory=$false)]
[string]$ForegroundColor = 'White',
[Parameter(Mandatory=$false)]
[string]$BackgroundColor = 'Black'
)
function Initialize-TuiColors {
param(
[string]$Fore,
[string]$Back
)
$host.UI.RawUI.ForegroundColor = $Fore
$host.UI.RawUI.BackgroundColor = $Back
Clear-Host
}
function Write-TuiHeader {
param(
[string]$Title,
[int]$Width = 80
)
$border = "=" * $Width
$padding = " " * [Math]::Floor(($Width - $Title.Length - 2) / 2)
$titleLine = "$padding $Title $padding"
Write-Host $border -ForegroundColor Cyan
Write-Host $titleLine -ForegroundColor White
Write-Host $border -ForegroundColor Cyan
Write-Host ""
}
function Write-TuiMenu {
param(
[hashtable[]]$Items
)
Write-Host "Main Menu" -ForegroundColor Yellow
Write-Host "-" * 20 -ForegroundColor Yellow
Write-Host ""
for ($i = 0; $i -lt $Items.Count; $i++) {
$label = $Items[$i].Label
$shortcut = $Items[$i].Shortcut
if ($shortcut) {
Write-Host " [$($i + 1)]" -NoNewline -ForegroundColor Cyan
Write-Host " $label " -NoNewline
Write-Host "($shortcut)" -ForegroundColor DarkGray
}
else {
Write-Host " [$($i + 1)] $label" -ForegroundColor White
}
}
Write-Host ""
Write-Host " [Q] Quit" -ForegroundColor Red
Write-Host ""
}
function Show-TuiMenu {
param(
[hashtable[]]$Items,
[string]$Prompt = "Select an option: "
)
while ($true) {
if ($ClearScreen) {
Clear-Host
}
Write-TuiHeader -Title $Title
Write-TuiMenu -Items $Items
Write-Host $Prompt -NoNewline -ForegroundColor Green
$input = Read-Host
if ($input -eq 'q' -or $input -eq 'Q') {
return 'Quit'
}
$selectedIndex = 0
if ([int]::TryParse($input, [ref]$selectedIndex)) {
$selectedIndex--
if ($selectedIndex -ge 0 -and $selectedIndex -lt $Items.Count) {
$selectedItem = $Items[$selectedIndex]
if ($selectedItem.Action) {
& $selectedItem.Action
}
if ($selectedItem.SubMenu) {
Show-TuiMenu -Items $selectedItem.SubMenu -Prompt $Prompt
}
if (-not $selectedItem.KeepOpen) {
return $selectedItem
}
}
else {
Write-Host "Invalid selection. Please try again." -ForegroundColor Red
Start-Sleep -Seconds 1
}
}
else {
# Check for shortcuts
foreach ($item in $Items) {
if ($item.Shortcut -and $input -eq $item.Shortcut) {
if ($item.Action) {
& $item.Action
}
if (-not $item.KeepOpen) {
return $item
}
break
}
}
Write-Host "Invalid selection. Please try again." -ForegroundColor Red
Start-Sleep -Seconds 1
}
}
}
function Write-TuiTable {
param(
[object[]]$Data,
[string[]]$Properties
)
if (-not $Data) {
Write-Host "No data to display" -ForegroundColor Yellow
return
}
if (-not $Properties) {
$Properties = $Data[0].PSObject.Properties.Name
}
# Calculate column widths
$colWidths = @{}
foreach ($prop in $Properties) {
$maxWidth = $prop.Length
foreach ($item in $Data) {
$value = $item.$prop ?? ''
$maxWidth = [Math]::Max($maxWidth, $value.ToString().Length)
}
$colWidths[$prop] = $maxWidth + 2
}
# Write header
$header = ""
foreach ($prop in $Properties) {
$header += ("{0,-$($colWidths[$prop])}" -f $prop)
}
Write-Host $header -ForegroundColor Cyan
Write-Host ("-" * $header.Length) -ForegroundColor Cyan
# Write data rows
foreach ($item in $Data) {
$row = ""
foreach ($prop in $Properties) {
$value = $item.$prop ?? ''
$row += ("{0,-$($colWidths[$prop])}" -f $value)
}
Write-Host $row -ForegroundColor White
}
Write-Host ""
Write-Host "Total: $($Data.Count) items" -ForegroundColor Gray
}
function Show-TuiForm {
param(
[hashtable]$Fields
)
$results = @{}
Write-Host "Form Entry" -ForegroundColor Yellow
Write-Host "-" * 20 -ForegroundColor Yellow
Write-Host ""
foreach ($field in $Fields.GetEnumerator()) {
$fieldName = $field.Key
$fieldInfo = $field.Value
$label = $fieldInfo.Label ?? $fieldName
$default = $fieldInfo.Default
$required = $fieldInfo.Required
$prompt = "$label"
if ($required) {
$prompt += "*"
}
$prompt += ": "
Write-Host $prompt -NoNewline -ForegroundColor Green
if ($default) {
Write-Host "[$default] " -NoNewline -ForegroundColor DarkGray
}
$input = Read-Host
if ([string]::IsNullOrEmpty($input)) {
if ($required) {
Write-Host "This field is required." -ForegroundColor Red
# Try again
# Simplified: continue for now
}
elseif ($default) {
$results[$fieldName] = $default
}
else {
$results[$fieldName] = $null
}
}
else {
$results[$fieldName] = $input
}
}
Write-Host ""
Write-Host "Form completed. Press any key to continue..." -ForegroundColor Gray
$null = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
return $results
}
function Show-TuiProgress {
param(
[int]$PercentComplete,
[string]$Activity,
[string]$Status = "Processing..."
)
$width = 50
$filled = [Math]::Floor($width * $PercentComplete / 100)
$empty = $width - $filled
$bar = ("█" * $filled) + ("░" * $empty)
Write-Host "`r$Activity" -ForegroundColor Yellow -NoNewline
Write-Host " " -NoNewline
Write-Host "[$bar] $PercentComplete%" -ForegroundColor Green -NoNewline
Write-Host " $Status" -ForegroundColor Gray
}
function Show-TuiWizard {
param(
[hashtable[]]$Steps
)
$currentStep = 0
$wizardData = @{}
while ($currentStep -lt $Steps.Count) {
if ($ClearScreen) {
Clear-Host
}
Write-TuiHeader -Title "$Title - Wizard"
$step = $Steps[$currentStep]
Write-Host "Step $($currentStep + 1) of $($Steps.Count)" -ForegroundColor Cyan
Write-Host ""
Write-Host $step.Title -ForegroundColor White
Write-Host $step.Description -ForegroundColor DarkGray
Write-Host ""
if ($step.Type -eq 'Form') {
$formData = Show-TuiForm -Fields $step.Fields
$wizardData[$step.Name] = $formData
}
elseif ($step.Type -eq 'Info') {
Write-Host $step.Content -ForegroundColor White
Write-Host ""
Write-Host "Press any key to continue..." -ForegroundColor Gray
$null = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
Write-Host ""
Write-Host "[N] Next [P] Previous [Q] Quit" -ForegroundColor Green
$choice = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").Character
switch ($choice) {
'n' {
if ($currentStep -lt $Steps.Count - 1) {
$currentStep++
}
}
'p' {
if ($currentStep -gt 0) {
$currentStep--
}
}
'q' {
return $null
}
}
}
return $wizardData
}
try {
Write-Verbose "Starting TUI design: $Title"
Initialize-TuiColors -Fore $ForegroundColor -Back $BackgroundColor
switch ($Mode) {
'Menu' {
$result = Show-TuiMenu -Items $MenuItems
Write-Host "Selected: $($result.Label)" -ForegroundColor Green
}
'Table' {
Write-TuiHeader -Title $Title
Write-TuiTable -Data $TableData
}
'Form' {
Write-TuiHeader -Title $Title
$result = Show-TuiForm -Fields $FormFields
Write-Host "`nForm data:" -ForegroundColor Yellow
$result.GetEnumerator() | ForEach-Object {
Write-Host " $($_.Key): $($_.Value)" -ForegroundColor White
}
}
'Progress' {
Write-TuiHeader -Title $Title
for ($i = 0; $i -le 100; $i += 10) {
Show-TuiProgress -PercentComplete $i -Activity "Processing" -Status "Step $i/100"
Start-Sleep -Milliseconds 200
}
Write-Host "`nComplete!" -ForegroundColor Green
}
'Wizard' {
$result = Show-TuiWizard -Steps $MenuItems
if ($result) {
Write-Host "`nWizard completed!" -ForegroundColor Green
}
}
}
Write-Verbose "TUI design completed"
}
catch {
Write-Error "TUI design failed: $_"
exit 1
}
finally {
Write-Verbose "Design TUI script completed"
}
Export-ModuleMember -Function Write-TuiMenu, Show-TuiMenu, Write-TuiTable, Show-TuiForm, Show-TuiProgress
import { spawn, ChildProcess } from 'child_process';
import * as path from 'path';
interface PowerShellOptions {
executionPolicy?: string;
noProfile?: boolean;
nonInteractive?: boolean;
}
interface WinFormsParams {
formTitle: string;
width?: number;
height?: number;
controls?: WinFormsControl[];
formBorderStyle?: 'FixedSingle' | 'Fixed3D' | 'FixedDialog' | 'Sizable' | 'FixedToolWindow' | 'SizableToolWindow';
startPosition?: 'Normal' | 'Minimized' | 'Maximized' | 'CenterScreen' | 'WindowsDefaultLocation' | 'WindowsDefaultBounds' | 'CenterParent';
show?: boolean;
generateScript?: boolean;
outputPath?: string;
}
interface WinFormsControl {
type: 'Button' | 'TextBox' | 'Label' | 'ListBox' | 'ComboBox';
name: string;
text?: string;
x: number;
y: number;
width: number;
height: number;
multiline?: boolean;
readOnly?: boolean;
}
interface WpfParams {
xamlPath?: string;
viewModel?: Record<string, any>;
show?: boolean;
generateXaml?: boolean;
outputXamlPath?: string;
windowTitle?: string;
width?: number;
height?: number;
}
interface TuiParams {
title?: string;
menuItems?: TuiMenuItem[];
tableData?: any[];
formFields?: Record<string, any>;
mode?: 'Menu' | 'Table' | 'Form' | 'Progress' | 'Wizard';
clearScreen?: boolean;
foregroundColor?: string;
backgroundColor?: string;
}
interface TuiMenuItem {
label: string;
shortcut?: string;
action?: string;
subMenu?: TuiMenuItem[];
keepOpen?: boolean;
}
export class PowerShellUIArchitect {
private scriptPath: string;
constructor(scriptPath: string = './scripts') {
this.scriptPath = scriptPath;
}
private async executePowerShell(script: string, params: Record<string, any>, options?: PowerShellOptions): Promise<string> {
return new Promise((resolve, reject) => {
const args: string[] = [];
if (options?.executionPolicy) {
args.push('-ExecutionPolicy', options.executionPolicy);
}
if (options?.noProfile) {
args.push('-NoProfile');
}
if (options?.nonInteractive) {
args.push('-NonInteractive');
}
args.push('-File', path.join(this.scriptPath, script));
Object.entries(params).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => {
args.push(`-${key}`, `'${JSON.stringify(v).replace(/'/g, "''")}'`);
});
} else if (typeof value === 'boolean') {
if (value) {
args.push(`-${key}`);
}
} else if (typeof value === 'object') {
args.push(`-${key}`, `"${JSON.stringify(value).replace(/"/g, '\\"')}"`);
} else if (value !== undefined && value !== null) {
args.push(`-${key}`, value.toString());
}
});
const ps: ChildProcess = spawn('powershell.exe', args);
let stdout = '';
let stderr = '';
ps.stdout?.on('data', (data: Buffer) => {
stdout += data.toString();
});
ps.stderr?.on('data', (data: Buffer) => {
stderr += data.toString();
});
ps.on('close', (code: number) => {
if (code === 0) {
resolve(stdout);
} else {
reject(new Error(`PowerShell failed with code ${code}: ${stderr}`));
}
});
ps.on('error', (err: Error) => {
reject(err);
});
});
}
async createWinForms(params: WinFormsParams, options?: PowerShellOptions): Promise<string> {
const scriptParams: Record<string, any> = {
FormTitle: params.formTitle,
};
if (params.width) scriptParams.Width = params.width;
if (params.height) scriptParams.Height = params.height;
if (params.controls) scriptParams.Controls = params.controls;
if (params.formBorderStyle) scriptParams.FormBorderStyle = params.formBorderStyle;
if (params.startPosition) scriptParams.StartPosition = params.startPosition;
if (params.show) scriptParams.Show = params.show;
if (params.generateScript) scriptParams.GenerateScript = params.generateScript;
if (params.outputPath) scriptParams.OutputPath = params.outputPath;
return this.executePowerShell('create_winforms.ps1', scriptParams, {
executionPolicy: 'RemoteSigned',
...options
});
}
async buildWpf(params: WpfParams, options?: PowerShellOptions): Promise<string> {
const scriptParams: Record<string, any> = {};
if (params.xamlPath) scriptParams.XamlPath = params.xamlPath;
if (params.viewModel) scriptParams.ViewModel = params.viewModel;
if (params.show) scriptParams.Show = params.show;
if (params.generateXaml) scriptParams.GenerateXaml = params.generateXaml;
if (params.outputXamlPath) scriptParams.OutputXamlPath = params.outputXamlPath;
if (params.windowTitle) scriptParams.WindowTitle = params.windowTitle;
if (params.width) scriptParams.Width = params.width;
if (params.height) scriptParams.Height = params.height;
return this.executePowerShell('build_wpf.ps1', scriptParams, {
executionPolicy: 'RemoteSigned',
...options
});
}
async designTui(params: TuiParams, options?: PowerShellOptions): Promise<string> {
const scriptParams: Record<string, any> = {};
if (params.title) scriptParams.Title = params.title;
if (params.menuItems) scriptParams.MenuItems = params.menuItems;
if (params.tableData) scriptParams.TableData = params.tableData;
if (params.formFields) scriptParams.FormFields = params.formFields;
if (params.mode) scriptParams.Mode = params.mode;
if (params.clearScreen) scriptParams.ClearScreen = params.clearScreen;
if (params.foregroundColor) scriptParams.ForegroundColor = params.foregroundColor;
if (params.backgroundColor) scriptParams.BackgroundColor = params.backgroundColor;
return this.executePowerShell('design_tui.ps1', scriptParams, {
executionPolicy: 'RemoteSigned',
...options
});
}
async checkWindowsFormsSupport(): Promise<boolean> {
try {
const result = await this.executePowerShell(
'Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop; $true',
{}
);
return result.trim().toLowerCase() === 'true';
} catch {
return false;
}
}
async checkWpfSupport(): Promise<boolean> {
try {
const result = await this.executePowerShell(
'Add-Type -AssemblyName PresentationFramework -ErrorAction Stop; $true',
{}
);
return result.trim().toLowerCase() === 'true';
} catch {
return false;
}
}
createWinFormsButton(name: string, text: string, x: number, y: number, width: number = 100, height: number = 30): WinFormsControl {
return {
type: 'Button',
name,
text,
x,
y,
width,
height
};
}
createWinFormsTextBox(name: string, text: string, x: number, y: number, width: number = 200, height: number = 20, multiline: boolean = false, readOnly: boolean = false): WinFormsControl {
return {
type: 'TextBox',
name,
text,
x,
y,
width,
height,
multiline,
readOnly
};
}
createWinFormsLabel(name: string, text: string, x: number, y: number, width: number = 100, height: number = 20): WinFormsControl {
return {
type: 'Label',
name,
text,
x,
y,
width,
height
};
}
createTuiMenuItem(label: string, action?: string, shortcut?: string): TuiMenuItem {
return {
label,
action,
shortcut
};
}
}
export default PowerShellUIArchitect;