How to create WinUI3 GUI in PowerShell?
See the question and my original answer on StackOverflowStarting a WinUI3 app is quite involved, and supporting full XAML is another difficulty as it also requires compiled XAML files (.xbf), resource files (.pri), etc. These can be built with SDK tools but here I will just demonstrate how to start a WinUI .NET 8 application based on PowerShell 7.4 with or without simple XAML (no automatic binding), without the need for Visual Studio, with zero compilation at deployment time.
First create a directory and put in there:
- The following
.ps1
file, - The content of the latest Microsoft.WindowsAppSDK package nuget, only the
lib\net6.0-windows10.0.18362.0
directory (as of today, it's not targeting .NET 8), - Add to that
WinRT.Runtime.dll
from the Microsoft.Windows.CsWinRT nuget - Add to that
Microsoft.Windows.SDK.NET.dll
from the Microsoft.Windows.SDK.NET.Ref nuget - Add to that
Microsoft.WindowsAppRuntime.Bootstrap.dll
from WinAppSDK Runtime (you can find it in a place likeC:\Program Files\WindowsApps\Microsoft.WindowsAppRuntime.1.5_5001.95.533.0_x64__8wekyb3d8bbwe
as of today). Note: obviously, the Windows App Runtime must be installed on the PC for all this to work. - Optionaly put a .ico file if you want nice icons for your windows
This is how your folder should look (you can delete the .xml files they are for SDK documentation):
Now here is the content of BasicWinUI.ps1
(as you can see it's 95% C#)
Add-Type -Path ".\WinRT.Runtime.dll"
Add-Type -Path ".\Microsoft.Windows.SDK.NET.dll"
Add-Type -Path ".\Microsoft.WindowsAppRuntime.Bootstrap.Net.dll"
Add-Type -Path ".\Microsoft.InteractiveExperiences.Projection.dll"
Add-Type -Path ".\Microsoft.WinUI.dll"
$referencedAssemblies = @(
"System.Threading" # for SynchronizationContext
".\WinRT.Runtime.dll"
".\Microsoft.Windows.SDK.NET.dll"
".\Microsoft.WindowsAppRuntime.Bootstrap.Net.dll"
".\Microsoft.InteractiveExperiences.Projection.dll"
".\Microsoft.WinUI.dll"
)
#Note: we remove warning CS1701: Assuming assembly reference 'System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
# used by 'Microsoft.WindowsAppRuntime.Bootstrap.Net'
# matches identity 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' of 'System.Runtime',
# you may need to supply runtime policy
Add-Type -ReferencedAssemblies $referencedAssemblies -CompilerOptions /nowarn:CS1701 -Language CSharp @"
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.XamlTypeInfo;
using Microsoft.Windows.ApplicationModel.DynamicDependency;
using Windows.Graphics;
using Windows.UI.Popups;
using WinRT.Interop;
namespace BasicWinUI
{
public class App : Application, IXamlMetadataProvider
{
private MyWindow m_window;
private readonly XamlControlsXamlMetaDataProvider _provider = new();
public bool Result => m_window?.Result ?? false;
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
if (m_window != null)
return;
Resources.MergedDictionaries.Add(new XamlControlsResources());
m_window = new MyWindow();
m_window.Activate();
}
public IXamlType GetXamlType(Type type) => _provider.GetXamlType(type);
public IXamlType GetXamlType(string fullName) => _provider.GetXamlType(fullName);
public XmlnsDefinition[] GetXmlnsDefinitions() => _provider.GetXmlnsDefinitions();
public static bool Run()
{
App app = null;
Bootstrap.Initialize(0x0010005); // asks for WinAppSDK version 1.5, or gets "Package dependency criteria could not be resolved" error
XamlCheckProcessRequirements();
Application.Start((p) =>
{
SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread()));
app = new App();
});
Bootstrap.Shutdown();
return app?.Result ?? false;
}
[DllImport("microsoft.ui.xaml")]
private static extern void XamlCheckProcessRequirements();
}
public class MyWindow : Window
{
public MyWindow()
{
Title = "Basic WinUI3";
// set icon by path
AppWindow.SetIcon("BasicWinUI.ico");
// size & center
var area = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest);
var width = 300; var height = 150;
var rc = new RectInt32((area.WorkArea.Width - width) / 2, (area.WorkArea.Height - height) / 2, width, height);
AppWindow.MoveAndResize(rc);
// give a "dialog" look
if (AppWindow.Presenter is OverlappedPresenter p)
{
p.IsMinimizable = false;
p.IsMaximizable = false;
p.IsResizable = false;
}
// create the content as a panel
var panel = new StackPanel { Margin = new Thickness(10) };
Content = panel;
panel.Children.Add(new TextBlock { Text = "Are you sure you want to do this?", HorizontalAlignment = HorizontalAlignment.Center });
// create a panel for buttons
var buttons = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
panel.Children.Add(buttons);
// add yes & no buttons
var yes = new Button { Content = "Yes", Margin = new Thickness(10) };
var no = new Button { Content = "No", Margin = new Thickness(10) };
buttons.Children.Add(yes);
buttons.Children.Add(no);
no.Click += (s, e) => Close();
yes.Click += async (s, e) =>
{
// show some other form
var dlg = new MessageDialog("You did click yes", Title);
InitializeWithWindow.Initialize(dlg, WindowNative.GetWindowHandle(this));
await dlg.ShowAsync();
Result = true;
Close();
};
// focus on first button
panel.Loaded += (s, e) => panel.Focus(FocusState.Keyboard);
}
public bool Result { get; set; }
}
}
"@;
$ret = [BasicWinUI.App]::Run() # get result (in this sample code it's a boolean)
$ret
I start it with a .bat like this in the BasicWinUI
folder:
C:\myPowerShellPath\PowerShell-7.4.2-win-x64\pwsh.exe -File BasicWinUI.ps1
And here is what you should get (XAML free):
Now if you want simple XAML support, you can load a XAML file dynamically and bind to UI objects manually. For example, if you create a .XAML file like this and name it BasicWinUIWindow.xaml
:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button x:Name="okButton">OK</Button>
<Button x:Name="cancelButton">Cancel</Button>
</StackPanel>
</Window>
Place it into the same folder and use a .ps1 like this for example:
Add-Type -Path ".\WinRT.Runtime.dll"
Add-Type -Path ".\Microsoft.Windows.SDK.NET.dll"
Add-Type -Path ".\Microsoft.WindowsAppRuntime.Bootstrap.Net.dll"
Add-Type -Path ".\Microsoft.InteractiveExperiences.Projection.dll"
Add-Type -Path ".\Microsoft.WinUI.dll"
$referencedAssemblies = @(
"System.IO" # for File
"System.Threading" # for SynchronizationContext
".\WinRT.Runtime.dll"
".\Microsoft.Windows.SDK.NET.dll"
".\Microsoft.WindowsAppRuntime.Bootstrap.Net.dll"
".\Microsoft.InteractiveExperiences.Projection.dll"
".\Microsoft.WinUI.dll"
)
Add-Type -ReferencedAssemblies $referencedAssemblies -CompilerOptions /nowarn:CS1701 -Language CSharp @"
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.XamlTypeInfo;
using Microsoft.Windows.ApplicationModel.DynamicDependency;
namespace BasicWinUI
{
public class App : Application, IXamlMetadataProvider
{
private readonly XamlControlsXamlMetaDataProvider _provider = new();
private Window m_window;
private static bool _ok;
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
if (m_window != null)
return;
Resources.MergedDictionaries.Add(new XamlControlsResources());
var dir = Path.GetDirectoryName(typeof(Bootstrap).Assembly.Location);
// load XAML file, should be a Window
m_window = (Window)XamlReader.Load(File.ReadAllText(Path.Combine(dir, "BasicWinUIWindow.xaml")));
var sp = (StackPanel)m_window.Content; // we know root is a stack panel, get buttons
var ok = (Button)sp.FindName("okButton");
var cancel = (Button)sp.FindName("cancelButton");
ok.Click += (s, e) => { _ok = true; m_window.Close(); };
cancel.Click += (s, e) => m_window.Close();
m_window.Activate();
}
public IXamlType GetXamlType(Type type) => _provider.GetXamlType(type);
public IXamlType GetXamlType(string fullName) => _provider.GetXamlType(fullName);
public XmlnsDefinition[] GetXmlnsDefinitions() => _provider.GetXmlnsDefinitions();
public static bool Run()
{
Bootstrap.Initialize(0x0010005); // asks for WinAppSDK version 1.5, or gets "Package dependency criteria could not be resolved" error
XamlCheckProcessRequirements();
Application.Start((p) =>
{
SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread()));
new App();
});
Bootstrap.Shutdown();
return _ok;
}
[DllImport("microsoft.ui.xaml")]
private static extern void XamlCheckProcessRequirements();
}
}
"@;
$ret = [BasicWinUI.App]::Run() # get result (in this sample code it's a boolean)
$ret
Now this is what you'll see:
PS: the .ps1 code will return true
if you press OK and false
if you press Cancel.