Creating and rendering a simple WinUI3 GUI in PowerShell 7.5 which is based on .NET 9. Nothing complicated, just a window and a button in it, such as this XAML
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="App1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
</StackPanel>
</Window>
.cs
CSharp files uncompiled in PowerShell if there is a need for CSharp code.I've created a fully working WinUI3 app in Visual Studio 2022 using the latest WindowsApps SDK. Then inside of the Winui3 project\App1\bin\x64\Debug\net8.0-windows10.0.22621.0\win-x64
folder I've tried loading all of the DLLs
in there in PowerShell. Some 200 dlls loaded and a few failed to load.
In PowerShell now I have access to the type [Microsoft.UI.Xaml.Window]
but when I try to create an instance of it
New-Object -TypeName Microsoft.UI.Xaml.Window
# Or
[Microsoft.UI.Xaml.Window]::new()
I get the following error
MethodInvocationException: Exception calling ".ctor" with "0" argument(s): "The type initializer for '_IWindowFactory' threw an exception."
It looks like there is a dependency missing for _IWindowFactory
.
This is the full error message
Exception :
Type : System.Management.Automation.MethodInvocationException
ErrorRecord :
Exception :
Type : System.Management.Automation.ParentContainsErrorRecordException
Message : Exception calling ".ctor" with "0" argument(s): "The type initializer for '_IWindowFactory' threw an exception."
HResult : -2146233087
CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
FullyQualifiedErrorId : TypeInitializationException
InvocationInfo :
ScriptLineNumber : 1
OffsetInLine : 1
HistoryId : 4
Line : [Microsoft.UI.Xaml.Window]::new()
Statement : [Microsoft.UI.Xaml.Window]::new()
PositionMessage : At line:1 char:1
+ [Microsoft.UI.Xaml.Window]::new()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
TargetSite :
Name : ConvertToMethodInvocationException
DeclaringType : [System.Management.Automation.ExceptionHandlingOps]
MemberType : Method
Module : System.Management.Automation.dll
Message : Exception calling ".ctor" with "0" argument(s): "The type initializer for '_IWindowFactory' threw an exception."
Data : System.Collections.ListDictionaryInternal
InnerException :
Type : System.TypeInitializationException
TypeName : _IWindowFactory
TargetSite :
Name : get_Instance
DeclaringType : [Microsoft.UI.Xaml.Window+_IWindowFactory]
MemberType : Method
Module : Microsoft.WinUI.dll
Message : The type initializer for '_IWindowFactory' threw an exception.
InnerException :
Type : System.TypeInitializationException
TypeName : WinRT.ActivationFactory`1
TargetSite :
Name : As
DeclaringType : [WinRT.ActivationFactory`1[T]]
MemberType : Method
Module : Microsoft.WinUI.dll
Message : The type initializer for 'WinRT.ActivationFactory`1' threw an exception.
InnerException :
Type : System.Runtime.InteropServices.COMException
ErrorCode : -2147221164
TargetSite :
Name : ThrowExceptionForHR
DeclaringType : [System.Runtime.InteropServices.Marshal]
MemberType : Method
Module : System.Private.CoreLib.dll
Message : Class not registered (0x80040154 (REGDB_E_CLASSNOTREG))
Source : System.Private.CoreLib
HResult : -2147221164
StackTrace :
at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)
at WinRT.BaseActivationFactory..ctor(String typeNamespace, String typeFullName)
at WinRT.ActivationFactory`1..ctor()
at WinRT.ActivationFactory`1..cctor()
Source : Microsoft.WinUI
HResult : -2146233036
StackTrace :
at WinRT.ActivationFactory`1.As(Guid iid)
at Microsoft.UI.Xaml.Window._IWindowFactory..ctor()
at Microsoft.UI.Xaml.Window._IWindowFactory..cctor()
Source : Microsoft.WinUI
HResult : -2146233036
StackTrace :
at Microsoft.UI.Xaml.Window._IWindowFactory.get_Instance()
at Microsoft.UI.Xaml.Window..ctor()
at CallSite.Target(Closure, CallSite, Type)
Source : System.Management.Automation
HResult : -2146233087
StackTrace :
at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception exception, Type typeToThrow, String methodName, Int32 numArgs, MemberInfo memberInfo)
at CallSite.Target(Closure, CallSite, Type)
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at System.Management.Automation.Interpreter.DynamicInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
CategoryInfo : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : TypeInitializationException
InvocationInfo :
ScriptLineNumber : 1
OffsetInLine : 1
HistoryId : 4
Line : [Microsoft.UI.Xaml.Window]::new()
Statement : [Microsoft.UI.Xaml.Window]::new()
PositionMessage : At line:1 char:1
+ [Microsoft.UI.Xaml.Window]::new()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CommandOrigin : Internal
ScriptStackTrace : at <ScriptBlock>, <No file>: line 1
Other people have tried this too and had similar results. Another issue related to this problem asking for some guidance from Microsoft.
I don't know how Visual Studio does this that makes it all so easy and automated, but I believe I need to do the same tasks manually in PowerShell.
Simon provided a fantastic answer. I tried to convert his answer to an all powershell solution. The only part I couldn't figure out was why this line can't be translated one for one. $this.Resources.MergedDictionaries.Add([Microsoft.UI.Xaml.Controls.XamlControlsResources]::new())
This builds the application in another runspace while leaving room to add to the DataContext and to call Window.Activate() when needed by dispatcherqueue.
# cd 'C:\change\this'
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"
# //Setup runspacepool and shared variable
$ConcurrentDict = [System.Collections.Concurrent.ConcurrentDictionary[string,object]]::new()
$State = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$RunspaceVariable = [System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('ConcurrentDict', $ConcurrentDict, $null)
$State.Variables.Add($RunspaceVariable)
$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $([int]$env:NUMBER_OF_PROCESSORS + 1), $State, (Get-Host))
$RunspacePool.Open()
$Powershell = [PowerShell]::Create()
$Powershell.RunspacePool = $RunspacePool
$AppSetup = @'
# cd 'C:\change\this'
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"
class PwshWinUIApp : Microsoft.UI.Xaml.Application, Microsoft.UI.Xaml.Markup.IXamlMetadataProvider {
# //App is able to load without Microsoft.UI.Xaml.Markup.IXamlMetadataProvider but interaction such as clicking a button will crash the terminal without it.
$MainWindow
$provider = [Microsoft.UI.Xaml.XamlTypeInfo.XamlControlsXamlMetaDataProvider]::new()
static [bool]$OkWasClicked
$SharedConcurrentDictionary
[Microsoft.UI.Xaml.Markup.IXamlType]GetXamlType([type]$type) {
return $this.provider.GetXamlType($type)
}
[Microsoft.UI.Xaml.Markup.IXamlType]GetXamlType([string]$fullname) {
return $this.provider.GetXamlType($fullname)
}
[Microsoft.UI.Xaml.Markup.XmlnsDefinition[]]GetXmlnsDefinitions() {
return $this.provider.GetXmlnsDefinitions()
}
PwshWinUIApp() {}
PwshWinUIApp($SharedConcurrentDictionary) {
$this.SharedConcurrentDictionary = $SharedConcurrentDictionary
}
OnLaunched([Microsoft.UI.Xaml.LaunchActivatedEventArgs]$a) {
if ($null -ne $this.MainWindow) { return }
# //Don't know why this line is problematic or how to get it to work in powershell. But the app works without it.
# $this.Resources.MergedDictionaries.Add([Microsoft.UI.Xaml.Controls.XamlControlsResources]::new())
$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">
<TextBlock Text="{Binding tbContent, Mode=TwoWay}" Margin="10" />
<Button x:Name="okButton" Margin="10">OK</Button>
<Button x:Name="cancelButton" Margin="10">Cancel</Button>
</StackPanel>
</Window>'
$this.MainWindow = [Microsoft.UI.Xaml.Markup.XamlReader]::Load($xaml)
$ClassScope = $this
$WindowScope = $this.MainWindow
$this.SharedConcurrentDictionary.App = $ClassScope # Terminal will crash on most properties and the object itself when printing to terminal.
$this.SharedConcurrentDictionary.Window = $WindowScope
$this.SharedConcurrentDictionary.Dispatcher = $WindowScope.DispatcherQueue
$this.SharedConcurrentDictionary.OnloadFinished = $true
# $this.MainWindow.Activate()
}
static [bool] Run($SharedConcurrentDictionary) {
[Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap]::Initialize(0x0010005)
[Microsoft.UI.Xaml.Application]::Start({
[PwshWinUIApp]::new($SharedConcurrentDictionary)
})
[Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap]::Shutdown()
return [PwshWinUIApp]::OkWasClicked
}
}
[PwshWinUIApp]::Run($ConcurrentDict)
'@
# //Start app without window
$AppSetupScriptBlock = [scriptblock]::Create($AppSetup)
$null = $Powershell.AddScript($AppSetupScriptBlock)
$Handle = $Powershell.BeginInvoke()
# //Optional binding to class
[NoRunspaceAffinity()]
class binder {
# //Should inherit IPropertyNotifyChanged
# //or a dependency object
binder(){}
$tbContent = 'Without IPropertyNotifyChanged, this will not update'
}
$ConcurrentDict.binder = [binder]::new()
# //Wait for app to finish loading
while ($ConcurrentDict.OnloadFinished -ne $true) {
Start-Sleep -Milliseconds 50
}
# //Send actions to dispatcher such as setting up buttons (Could also bind buttons through a class like above)
$null = $ConcurrentDict.Dispatcher.TryEnqueue([scriptblock]::create({
# //This is inside the Window thread/runspace
# //Because ConcurrentDict is a shared variable, the Window thread can also access it
# //We have less access compared to wpf, where you could traverse the wpf object on any thread.
# //If you call $ConcurrentDict.Window.Content outside of this thread, it will be empty.
$sp = $ConcurrentDict.Window.Content
$ConcurrentDict.Window.Content.DataContext = $ConcurrentDict.binder
$ok = $sp.FindName("okButton")
$cancel = $sp.FindName("cancelButton")
$ok.add_Click([scriptblock]::create({
param($s, $e)
Write-Verbose "sender is: $($s.Name)" -Verbose
[PwshWinUIApp]::OkWasClicked = $true
$ConcurrentDict.ThreadId = "Set from Thread Id: $([System.Threading.Thread]::CurrentThread.ManagedThreadId)"
$ConcurrentDict.Window.Close()
}.ToString()))
$cancel.add_Click([scriptblock]::create({
param($s, $e)
Write-Verbose "sender is: $($s.Name)" -Verbose
$ConcurrentDict.ThreadId = "Set from Thread Id: $([System.Threading.Thread]::CurrentThread.ManagedThreadId)"
$ConcurrentDict.Window.Close()
}.ToString()))
}.ToString()))
# //Finally show the window via dispatcher
$Action = {$ConcurrentDict.Window.Activate()}.ToString()
$NoContextAction = [scriptblock]::create($Action)
$null = $ConcurrentDict.Window.DispatcherQueue.TryEnqueue($NoContextAction)
"Current Thread Id: $([System.Threading.Thread]::CurrentThread.ManagedThreadId)"
$ConcurrentDict