Search code examples
wpfpowershellrunspace

PowerShell WPF Runspace script - $syncHash returns Null in console but works in ISE


Im testing a script I found on https://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/. It is about PowerShell and WPF: Writing Data to a UI From a Different Runspace.

I copied the whole code and saved it to a test.ps1 file:

$syncHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"         
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)          
$psCmd = [PowerShell]::Create().AddScript({   
    Add-Type -AssemblyName presentationframework
    [xml]$xaml = @"
        <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
            Width = "600" Height = "800" ShowInTaskbar = "True">
            <TextBox x:Name = "textbox" Height = "400" Width = "600"/>
        </Window>
    "@
      
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )
    $syncHash.TextBox = $syncHash.window.FindName("textbox")
    $syncHash.Window.ShowDialog() | Out-Null
    $syncHash.Error = $Error
})
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()

The only thing I added was the line "Add-Type -AssemblyName presentationframework" over the xaml. It is working in both the console and in ISE. The Form pops up and the console is responding. However, when I check the $synchhash in the console I get no result, its a Null variable. When I check it in ISE I get the desired response:

PS C:\Windows\System32\WindowsPowerShell\v1.0> $syncHash

Name Value

---- -----

TextBox System.Windows.Controls.TextBox

Window System.Windows.Window

Im running Powershell 5 / 1 / 17763 / 3770, same user on same machine. I checked for admin privileges with this:

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

Both cases false. I have another script witch is way bigger and there it is the opposite way round i cant get it to start up in ISE, but in console it works fine. I need to know whats going on here!

I tried to run the script with forced STA in the console but it wont come up.

powershell.exe -Sta -File MyScript.ps1

Solution

  • The ISE implicitly and invariably dot-sources scripts run in it, so that any variables, function definitions, ... inside the script become available in the caller's scope.

    • This behavior - which is especially treacherous when you run scripts repeatedly, because state lingering from previous runs can affect subsequent ones - is one of the reasons to avoid (bottom section) the obsolescent Windows PowerShell ISE.

    • The actively developed, cross-platform editor that offers the best PowerShell development experience is Visual Studio Code with its PowerShell extension; you can configure the latter to start a new, temporary session for every debugging run, which avoids the lingering-state problem while still giving you access to the script's variables, ... while inside the temporary session.

    Therefore, if you want to inspect the $syncHash variable defined inside your script after running your script in a regular console window or Windows Terminal, you must explicitly invoke it dot-sourced:

    . .\test.ps1
    

    If you invoke your script as just .\test.ps1 or with & .\test.ps1 (using &, the call operator) it runs in a child scope, meaning that its variables, ... go out of scope when the script terminates and are not seen by the caller.