SkillAgentSearch skills...

GliderUI

Cross-platform Desktop GUI framework for PowerShell powered by Avalonia

Install / Use

/learn @mdgrs-mei/GliderUI
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<div align="center">

GliderUI

GitHub license PowerShell Gallery PowerShell Gallery

Cross-platform Desktop GUI framework for PowerShell powered by Avalonia.

https://github.com/user-attachments/assets/aa53a0b8-d478-4897-bc82-d10c804c98b1

</div>

[!NOTE] This module is in its prototyping phase. Frequent breaking changes are expected until this notice is removed.

Requirements

  • PowerShell 7.4 or newer
  • Windows(win-x64/win-arm64), macOS(osx-arm64) or Linux(linux-x64)

Installation

Install-PSResource -Name GliderUI

On macOS or Linux, call Enable-GLIExecution once after the installation or update using the same user account that installed the module:

# Adds execute permission to the server executable file.
Enable-GLIExecution

Quick Start

This code creates a Window that has a clickable button:

using namespace GliderUI.Avalonia.Controls
Import-Module GliderUI -Force

$win = [Window]::new()
$win.Title = 'Hello from PowerShell!'
$win.Width = 400
$win.Height = 200

$button = [Button]::new()
$button.Content = 'Click Me'
$button.HorizontalAlignment = 'Center'
$button.VerticalAlignment = 'Center'
$button.AddClick({
    $button.Content = 'Clicked!'
})

$win.Content = $button
# Show() shows the window but does not block the script.
$win.Show()
$win.WaitForClosed()

QuickStart

If you dot-source the script and comment out $win.WaitForClosed(), you can inspect UI objects or even modify them on the terminal:

PS> $button

ClickMode                  : Release
HotKey                     :
CommandParameter           :
IsDefault                  : False
IsCancel                   : False
IsPressed                  : False
Flyout                     :
Content                    : Click Me
ContentTemplate            :
Presenter                  : GliderUI.Avalonia.Controls.Presenters.ContentPresenter
:

Since the API of GliderUI follows the Avalonia's API, you can read the Avalonia documentation to see what should be available. The documentation of the Button class is here for example.

You can also refer to the examples folder for script samples.

How It Works

GliderUI launches a server process GliderUI.Server that provides all the UI functionalities. The GliderUI module communicates with the server through IPC (Inter-Process Communication) to create UI elements and handle events. No Avalonia's dlls are loaded in PowerShell.

<img width="1269" height="779" alt="image" src="https://github.com/user-attachments/assets/2324899a-6331-4fb7-93e9-6716079b04a5" />

This model simplifies the script structure. You can write long-running code in event handlers without blocking GUI. It's also allowed to access properties of UI elements directly on any thread without using Dispatchers.

This works:

$status = [TextBlock]::new()
$progressBar = [ProgressBar]::new()
$button.AddClick({
    $button.IsEnabled = $false
    $status.Text = 'Downloading...'
    1..50 | ForEach-Object {
        $progressBar.Value = $_
        Start-Sleep -Milliseconds 50
    }

    $status.Text = 'Installing...'
    51..100 | ForEach-Object {
        $progressBar.Value = $_
        Start-Sleep -Milliseconds 50
    }

    $status.Text = '🎉Done!'
    $button.IsEnabled = $true
})

HowItWorks

Supported APIs

All Avalonia controls and types become accessible by adding "GliderUI" as a prefix to their namespaces.

$button = [GliderUI.Avalonia.Controls.Button]::new()

Most methods and properties are automatically generated by the source generator, but those that use the following types are not supported:

  • Arrays
  • Delegates
  • Pointers
  • Types with ref or out modifiers

Avalonia version

The current supported Avalonia version is 11.3.12.

Event Callback

Event callbacks are script blocks that are invoked when UI events are fired. You can register them with Add{EventName} methods of UI elements. The typical example is the Click event of a button:

$button = [Button]::new()
$argumentList = 1, 2
$button.AddClick({
    param ($argumentList, $s, $e)
    Write-Host "ArgumentList: $argumentList"
    Write-Host "Sender: $s"
    Write-Host "EventArgs: $e"
}, $argumentList)

The same code can also be written using the EventCallback class. It allows you to customize the callback behavior:

$button = [Button]::new()
$clickCallback = [EventCallback]::new()
$clickCallback.RunspaceMode = 'RunspacePoolAsyncUI'
$clickCallback.DisabledControlsWhileProcessing = $button
$clickCallback.ArgumentList = 1, 2
$clickCallback.ScriptBlock = {
    param ($argumentList, $s, $e)
    Write-Host "ArgumentList: $argumentList"
    Write-Host "Sender: $s"
    Write-Host "EventArgs: $e"
}
$button.AddClick($clickCallback)

The RunspaceMode property controls where and how the script block runs.

$clickCallback.RunspaceMode = 'RunspacePoolAsyncUI'

There are three runspace modes, MainRunspaceAsyncUI, MainRunspaceSyncUI, and RunspacePoolAsyncUI.

MainRunspaceAsyncUI (Default)

The default value of RunspaceMode is MainRunspaceAsyncUI which means that the script block runs in the runspace where the script block is created (Main runspace). The script block can be a bound script block and sees the global or script scope variables in the runspace.

AsyncUI means that the callbacks do not block the UI thread on the server side. Even if a callback takes long time to finish, the UI stays responsive. If a button is pressed while the previous callback is running, the new callback is queued and processed after the previous one completes. If this behavior is not desirable, you can specify controls that are disabled on the server side while the event callback is running:

$clickCallback.DisabledControlsWhileProcessing = $button

It is a good practice to set the DisabledControlsWhileProcessing for long-running callbacks to avoid unintuitive queuing.

There is one callback queue per runspace where GliderUI module is loaded, and callbacks in the queue are typically processed inside Window.WaitForClosed method or {AwaitableType}.WaitForCompleted method. Please see MultipleRunspaces.ps1 for an example of multi-runspace scenario.

MainRunspaceSyncUI

Callbacks in MainRunspaceSyncUI mode run in the main runspace just like those with MainRunspaceAsyncUI, but they block the UI thread on the server side until they complete. Because they block the UI thread, it is guaranteed that no other events are triggered while the callback is running (No need to set DisabledControlsWhileProcessing).

RunspacePoolAsyncUI

Callbacks in RunspacePoolAsyncUI mode are handled in parallel by multiple runspaces in the runspace pool. This mode is ideal for long-running callbacks that must keep other callbacks responsive during execution. You can specify the number of runspaces in the pool and the script block that defines the global variables and functions in the runspaces:

Set-GLIRunspacePoolOption -RunspaceCount 5 -InitializationScript {
    param ($ScriptRoot)
    $globalVar = 'Global variable in the runspace.'
    function GlobalFunction() {
        'Global function in the runspace.'
    }
} -InitializationScriptArgumentList $PSScriptRoot

Note that the GliderUI module is automatically loaded in each runspace.

Since the callbacks are executed in parallel, you should pass variables via ArgumentList and handle thread safety just as you would with Start-ThreadJob. See CancelLongRunningEventCallback.ps1 as a basic example, and MultipleProgressBars.ps1 as an example of multiple concurrent tasks.

AvaloniaRuntimeXamlLoader

Instead of creating UI elements by code, you can also create them by loading XAML similar to WPF in PowerShell. You can search for an UI element by the FindControl method of Control and add event handlers from PowerShell. Note that you can't use x:Class attributes or code-behind binding in XAML.

using namespace GliderUI.Avalonia.Markup.Xaml

$xamlString = @'
<Window
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Button x:Name="button" Content="Click Me" HorizontalAlignment="Center" />
</Window>
'@

$win = [AvaloniaRuntimeXamlLoader]::Parse($xamlString, $null)
$win.Width = 400
$win.Height = 200
$button = $win.FindControl('button')
$button.AddClick({
    $button.Content = 'Clicked!'
})

$win.Show()
$win.WaitForClosed()

Contributing

Code of Conduct

Please read our Code of Conduct to foster a welcoming environment. By participating in this project, you are expected to uphold this code.

Have a question or want to showcase something?

Please come to our Discussions page and avoid filing an issue to ask a question.

Want to file an issue or make a PR?

Please see our Contribution Guidelines.

Credits

GliderUI uses:

  • Avalonia<br>https://github.com/AvaloniaUI/Avalonia
  • vs-StreamJsonRpc<br>https://github.com/microsoft/vs-streamjsonrpc

Related Skills

View on GitHub
GitHub Stars101
CategoryDevelopment
Updated1d ago
Forks4

Languages

C#

Security Score

95/100

Audited on Apr 7, 2026

No findings