I'm trying to convert my PowerShell project to an executable program (.exe). After some research I found PowerGUI. After converting my .ps1 file into an exe I ran into some problems:
Firstly it takes ages to start the program (about 15 seconds), is this normal or is there something I can do to improve this?
Secondly, if i exit the program I get a windows error message saying the program stopped working unexpectedly. Is there a way to hide this message?
Here is my ps1 code, I got a part of it from a blog and it is my first PowerShell code so don't be to harsh on me ;)
$inputXML = #"
<Window x:Class="BlogPostIII.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BlogPostIII"
mc:Ignorable="d"
Title="Organizer" Height="540" Width="540" FontSize="18.667">
<Grid x:Name="background">
<Button x:Name="OK" Content="OK" HorizontalAlignment="Left" Height="41" Margin="420,458,0,0" VerticalAlignment="Top" Width="100" FontSize="18.667"/>
<Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Left" Height="41" Margin="315,458,0,0" VerticalAlignment="Top" Width="100" FontSize="18.667"/>
<TextBox x:Name="TextBox1" HorizontalAlignment="Left" Height="30" Margin="108,216,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBlock x:Name="TextBlock1" HorizontalAlignment="Left" Height="30" Margin="108,36,0,0" TextWrapping="Wrap" Text="Soort bewerking:" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBox x:Name="TextBox2" HorizontalAlignment="Left" Height="30" Margin="108,291,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBlock x:Name="TextBlock2" HorizontalAlignment="Left" Height="30" Margin="108,111,0,0" TextWrapping="Wrap" Text="Naam Machine:" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBox x:Name="TextBox3" HorizontalAlignment="Left" Height="30" Margin="108,366,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBlock x:Name="TextBlock3" HorizontalAlignment="Left" Height="30" Margin="108,185,0,0" TextWrapping="Wrap" Text="Naam van opdrachtgevend bedrijf:" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBlock x:Name="TextBlock4" HorizontalAlignment="Left" Height="30" Margin="108,261,0,0" TextWrapping="Wrap" Text="Naam product:" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<TextBlock x:Name="TextBlock5" HorizontalAlignment="Left" Height="30" Margin="108,336,0,0" TextWrapping="Wrap" Text="Product ID:" VerticalAlignment="Top" Width="300" FontSize="18.667"/>
<ComboBox x:Name="combobox1" HorizontalAlignment="Left" Margin="108,66,0,0" VerticalAlignment="Top" Width="300"/>
<ComboBox x:Name="combobox2" HorizontalAlignment="Left" Margin="108,140,0,0" VerticalAlignment="Top" Width="300"/>
</Grid>
</Window>
"#
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================
$xaml.SelectNodes("//*[#Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name)}
Function Get-FormVariables{
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true}
write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
Get-FormVariables
#===========================================================================
# List the Comboboxes
#===========================================================================
$WPFcombobox1.AddText('Draaien')
$WPFcombobox1.AddText('Frezen')
$WPFcombobox1.AddText('Slijpen')
$WPFcombobox2.AddText('Doosan 3100LM')
$WPFcombobox2.AddText('Doosan 123')
$WPFcombobox2.AddText('machine 3')
$WPFcombobox2.AddText('machine 4')
$WPFcombobox2.AddText('machine 5')
#===========================================================================
# Actually make the objects work
#===========================================================================
#$WPFMakeUserbutton.Add_Click({(Get-FormFields)})
$WPFOK.Add_Click({
$1 = $WPFcomboBox1.Text
$2 = $WPFcomboBox2.Text
$3 = $WPFtextBox1.Text
$4 = $WPFtextBox2.Text
$5 = $WPFtextBox3.Text + " Werkblad"
New-Item C:\Users\Bjorn\Documents\Powershell\$1\$2\$3\$4\ -Force -type directory
Copy-Item C:\Users\Bjorn\Documents\Powershell\Test_werkblad.docx C:\Users\Bjorn\Documents\Powershell\$1\$2\$3\$4\
Rename-Item C:\Users\Bjorn\Documents\Powershell\$1\$2\$3\$4\Test_werkblad.docx C:\Users\Bjorn\Documents\Powershell\$1\$2\$3\$4\$5.docx
Invoke-Item C:\Users\Bjorn\Documents\Powershell\$1\$2\$3\$4
Invoke-Item C:\Users\Bjorn\Documents\Powershell\$1\$2\$3\$4\$5.docx
$Form.Close()})
$WPFCancel.Add_Click({
$Form.Close()})
#===========================================================================
# Shows the form
#===========================================================================
write-host "To show the form, run the following" -ForegroundColor Cyan
function Show-Form{
$Form.ShowDialog() | out-null
}
Show-Form
About the 15 seconds I am not sure why. Could you give more details? You should add logging and put time stamp in every message... This way you should be able to locate slow part.
About avoiding the error message, you should surround your code with try/catch. For example:
[...]
write-host "To show the form, run the following" -ForegroundColor Cyan
function Show-Form
{
$Form.ShowDialog() | out-null
}
try
{
Show-Form
}
catch
{
$ErrorMessage = $_.Exception.Message
# Show friendly message with error and/or log the error)
}
After reading your code here is some extra advice that could be useful:
Do not hardcode paths. (Like "C:\Users\Bjorn\Documents\Powershell\")
Do you really need PowerShell? Sounds like C# WinForms would be better for this task.
Store XML in a file and read it rather having it in code in $inputXML variable.
The 15 second pause is PowerShell initializing in the background. This is especially noticeable on Windows 7
CAVEAT: "compiling" PowerShell to EXE in most cases (PowerGUI included) is stuffing your original PS1 file inside a Self-extracting EXE. You could do the same with 7-Zip or WinZip.
A PowerGUI "compiled" EXE locks your script execution to whatever version or PowerShell + .Net you had on your DEV box. IOW: If you compile on a PC with PowerShell v4 but only use Write-host in your script (for example), the target PCs will need PowerShell v4+ for it to run!
Related
I am just about finished with program and need some help getting over the finish line. What is supposed to do is search through log files in a predefined directory. Nothing happens with the text I enter in the search box nor when I use the button that I created to execute the search. I have searched all over the Internet which has me as far as I am. I see lot's of examples in Winforms but I don't know the "translation" between Winforms and WPF.
I think that if someone can get me passed this hurdle I can implement the other features I want to add.
PS: I really have no programming skills, just tenacity.
Thanks in advance to all who help.
'''
# Import needed Assemblies
Add-Type -AssemblyName PresentationCore, PresentationFramework, WindowsBase,
System.Drawing, System.Windows.Forms, WindowsFormsIntegration
[void][System.Reflection.Assembly]::LoadWithPartialName('PresentationFramework')
[Windows.Forms.Application]::EnableVisualStyles()
#XAML form designed using Visual Studio
# Build the GUI
[xml]$Form = #"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Log Crawler"
HorizontalAlignment="Center"
FontSize="11" Height="700" Width="1300" FontFamily="Calibri"
WindowStartupLocation="CenterScreen"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled" WindowStyle="ThreeDBorderWindow">
<Grid Margin="0,0,0,0">
<Grid.Background>
<LinearGradientBrush EndPoint="0.8,1" StartPoint="0.8,0">
<GradientStop Color="Black"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<TextBox x:Name="IntegrationsLogCrawler" Text="Integration Log Crawler"
HorizontalAlignment="Center" VerticalAlignment="Top" TextAlignment="Center" TextWrapping="NoWrap"
Margin="0,20,0,0 " Height="37" Width="260"
FontSize="24" FontFamily="Calibri" FontWeight="Bold" FontStyle="Normal" FlowDirection="LeftToRight"/>
<GroupBox x:Name="SearchBox" Header="SearchBox"
HorizontalAlignment="Center" VerticalAlignment="Top"
FontSize="12" FontFamily="Calibri" FontWeight="Bold" Foreground="#FFE4E41A"
Margin="0,100,0,0" Height="40" Width="400">
<TextBox x:Name="InputBox" TextWrapping="NoWrap"/>
</GroupBox>
<Button x:Name="LogCrawl" Content="Log Crawl"
HorizontalAlignment="Center" VerticalAlignment="Top"
Margin="0,150,0,0" Height="40" Width="190"
FontSize="18" FontFamily="Calibri" FontWeight="Bold" Foreground="#FFEE0C3F"/>
<Button x:Name="Exit" Content="Exit"
HorizontalAlignment="Center" VerticalAlignment="Top"
Margin="0,200,0,0" Height="39" Width="110"
FontSize="16" FontFamily="Calibri" FontWeight="Bold" Foreground="#FFE4E41A" Background="#FF040404"/>
<GroupBox x:Name="Results" Header="Results"
HorizontalAlignment="Center" VerticalAlignment="Top"
FontSize="12" FontFamily="Calibri" FontWeight="Bold" Foreground="#FFE4E41A"
Margin="0,250,0,0 " Height="250" Width="1000">
<TextBox x:Name="OutputBox" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"/>
</GroupBox>
</Grid>
</Window>
"#
#Create a form
$XMLReader = (New-Object System.Xml.XmlNodeReader $Form)
$XMLForm = [Windows.Markup.XamlReader]::Load($XMLReader)
#Connect to Controls
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
New-Variable -Name $_.Name -Value $XMLForm.FindName($_.Name) -Force
}
# Load Log Crawl Menu Button Controls
$InputBox = $XMLForm.FindName('InputBox')
$OutputBox = $XMLForm.FindName('OutputBox')
$LogCrawl = $XMLForm.FindName('LogCrawl')
$Exit = $XMLForm.FindName('Exit')
# Logfile Location
$LogFiles = "$($ENV:USERPROFILE)\Documents\_data\logfiles"
# Log Crawl Button Action
$LogCrawl1.Add_Click({
Select-String -Path "$LogFiles\*.*" -Pattern $InputBox -Context 10, 20 | Write-Host
})
# EXIT
$Exit.Add_Click({
$XMLForm.Close()
})
#Show XMLform
$null = $XMLForm.ShowDialog()
'''
I'm not sure about the workings of this part (gave me exception "You cannot call a method on a null-valued expression.") :
#Connect to Controls
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
New-Variable -Name $_.Name -Value $XMLForm.FindName($_.Name) -Force
}
and I think you should remove it.
Then you made a typo in the button variable name $LogCrawl1 --> $LogCrawl, but also change the Add_Click() method to:
# Log Crawl Button Action
$LogCrawl.Add_Click({
# Logfile Location
$LogFiles = "$($ENV:USERPROFILE)\Documents\_data\logfiles"
$OutputBox.Text = Select-String -Path "$LogFiles\*.*" -Pattern $InputBox.Text -SimpleMatch -Context 10, 20
})
P.S. It is unclear if this is also in your real code, but the final closing "# for the XAML here-string must not have whitespace to the left.
I have been building GUI's for a while in powershell with XAML. Everything has worked fine until I attempted to bring in ComboBox. Then powershell no longer accepts it.
I have built the XAML in Visual Studio and it works in Design view. Every website I go to tells me this is the right XAML code. The error occurs with the code
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$DiagForm=[Windows.Markup.XamlReader]::Load( $reader )}
Here is the sample I am working with. If you take out the ComboBox segment, it works perfectly fine.
function Diag {
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Tool" Height="500" Width="300" WindowStartupLocation="CenterScreen" WindowStyle='SingleBorderWindow' ResizeMode='CanMinimize'>
<Grid>
<TextBox HorizontalAlignment="Center" Height="23" TextWrapping="Wrap" Text="Diagnostics" VerticalAlignment="Top" Width="300" Margin="0,-1,-0.2,0" TextAlignment="Center" Foreground="White" Background="#21C500"/>
<Label Content="Report" HorizontalContentAlignment="Center" HorizontalAlignment="Left" Margin="90,27,0,0" VerticalAlignment="Top" Width="120"/>
<ComboBox HorizontalAlignment="Left" Margin="90,60,0,0" VerticalAlignment="Top" Width="120">
<ComboBoxItem MouseMove="OnHover" Name="Period1" IsSelected="True">Last 24hr</ComboBoxItem>
<ComboBoxItem MouseMove="OnHover" Name="Period2" IsSelected="False">Last Week</ComboBoxItem>
<ComboBoxItem MouseMove="OnHover" Name="Period3" IsSelected="False">Last Month</ComboBoxItem>
</ComboBox>
<Label Content="Critical" HorizontalAlignment="Left" Margin="46,115,0,0" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="124,115,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<Label Content="Warnings" HorizontalAlignment="Left" Margin="46,143,0,0" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="124,143,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
'#
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$DiagForm=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."; exit}
$xaml.SelectNodes("//*[#Name]") | %{Set-Variable -Name ($_.Name) -Value $DiagForm.FindName($_.Name)}
$DiagForm.ShowDialog() | out-null
}
Diag
If you examine the exception that is being caught, you'll see the following error message:
Failed to create a 'MouseMove' from the text 'OnHover'.
Indeed, if you remove the MouseMove="OnHover" attributes from your XAML, the problem goes away.
Need advice on adding a text in let's say Text_Status after hitting a button.
How can I also automatically continue the command after a restart to let's say Button_clearTPM, then going to Button_enableTPM, then going to Button_initializeTPM?
Here is my XAML:
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TPM Script" Height="482" Width="479" Background="White">
<Grid Height="375" Width="382">
<Button Content="Clear TPM" Height="55" HorizontalAlignment="Left" Margin="230,30,0,0" Name="Button_clearTPM" VerticalAlignment="Top" Width="140"/>
<Button Content="Enable TPM" Height="55" HorizontalAlignment="Left" Margin="230,0,0,220" Name="Button_enableTPM" VerticalAlignment="Bottom" Width="140"/>
<Button Content="Initialize TPM" Height="55" HorizontalAlignment="Left" Margin="230,169,0,0" Name="Button_initializeTPM" VerticalAlignment="Top" Width="140"/>
<Label Content="Enter Workstation ID: " Height="23" HorizontalAlignment="Left" Margin="31,45,0,0" Name="Label_1" VerticalAlignment="Top" Width="133"/>
<TextBox Height="38" HorizontalAlignment="Left" Margin="31,74,0,0" Name="Text_WSID" VerticalAlignment="Top" Width="171" />
<TextBox Height="114" HorizontalAlignment="Left" Margin="31,241,0,0" Name="Text_Status" VerticalAlignment="Top" Width="329" />
</Grid>
</Window>
'#
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try
{
$Form=[Windows.Markup.XamlReader]::Load( $reader )
}
catch
{
Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."t
}
cls
#First Button
$clearTPM = $Form.FindName('Button_clearTPM')
$clearTPM.Add_Click({ Write-Host "Clear TPM clicked" -ForegroundColor Cyan})
#Second Button
$enableTPM = $Form.FindName('Button_enableTPM')
$enableTPM.Add_Click({ Write-Host "Enable TPM clicked" -ForegroundColor Cyan})
#Third Button
$initializeTPM = $Form.FindName('Button_initializeTPM')
$initializeTPM.Add_Click({ Write-Host "Initialize TPM clicked" -ForegroundColor Cyan})
$Form.ShowDialog() | out-null
##Possible Commands
#Clear-Tpm
#Enable-TpmAutoProvisioning (Export C:Notbackedup)
#Initialize-Tpm
The problem is your script doesn't know what your label is.
So you have to make it accessible by making it into a variable.
Then, you can assign whatever text you want via .Content.
So :
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TPM Script" Height="482" Width="479" Background="White">
<Grid Height="375" Width="382">
<Button Content="Clear TPM" Height="55" HorizontalAlignment="Left" Margin="230,30,0,0" Name="Button_clearTPM" VerticalAlignment="Top" Width="140"/>
<Button Content="Enable TPM" Height="55" HorizontalAlignment="Left" Margin="230,0,0,220" Name="Button_enableTPM" VerticalAlignment="Bottom" Width="140"/>
<Button Content="Initialize TPM" Height="55" HorizontalAlignment="Left" Margin="230,169,0,0" Name="Button_initializeTPM" VerticalAlignment="Top" Width="140"/>
<Label Content="Enter Workstation ID: " Height="23" HorizontalAlignment="Left" Margin="31,45,0,0" Name="Label_1" VerticalAlignment="Top" Width="133"/>
<TextBox Height="38" HorizontalAlignment="Left" Margin="31,74,0,0" Name="Text_WSID" VerticalAlignment="Top" Width="171" />
<TextBox Height="114" HorizontalAlignment="Left" Margin="31,241,0,0" Name="Text_Status" VerticalAlignment="Top" Width="329" />
</Grid>
</Window>
'#
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try
{
$Form=[Windows.Markup.XamlReader]::Load( $reader )
}
catch
{
Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."t
}
## THIS IS WHERE MAGIC HAPPENS
$xaml.SelectNodes("//*[#Name]") | %{Set-Variable -Name "$($_.Name)" -Value $Form.FindName($_.Name)} # find all names and make them accessible via a variable
#First Button
$clearTPM = $Form.FindName('Button_clearTPM')
$clearTPM.Add_Click({
Write-Host "Clear TPM clicked" -ForegroundColor Cyan
$label_1.content = "Clear TPM clicked"
})
#Second Button
$enableTPM = $Form.FindName('Button_enableTPM')
$enableTPM.Add_Click({ Write-Host "Enable TPM clicked" -ForegroundColor Cyan})
#Third Button
$initializeTPM = $Form.FindName('Button_initializeTPM')
$initializeTPM.Add_Click({ Write-Host "Initialize TPM clicked" -ForegroundColor Cyan})
$Form.ShowDialog() | out-null
##Possible Commands
#Clear-Tpm
#Enable-TpmAutoProvisioning (Export C:Notbackedup)
#Initialize-Tpm
Im a beginner and is needing help running an XAML into powershell.
Im trying to follow some format from the net but cant seem to convert my xaml GUI to powershell.
Any help will do. Heres my code:
<Window x:Class="WpfApp3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
Title="Strike Email" Height="137" Width="274" Background="White">
<Grid Background="#FF005D80">
<Button Content="1st Strike" HorizontalAlignment="Left" Height="50" Margin="10,30,0,0" VerticalAlignment="Top" Width="70" Background="White" Click="Button_Click"/>
<Button Content="3rd Strike" HorizontalAlignment="Left" Height="50" Margin="180,30,0,0" VerticalAlignment="Top" Width="70" Background="White"/>
<Button Content="2nd Strike" HorizontalAlignment="Left" Height="50" Margin="95,30,0,0" VerticalAlignment="Top" Width="70" Background="White"/>
</Grid>
For that, you will need to:
Cast your xaml string as a xml
Load presentationframework assembly
Load the XAML into a xmlNodeReader
Load the form
Powershell must be running in STA (Single threaded) mode for this to work
(Powershell ISE launch in STA mode by default.)
[xml]$xaml = 'ValidXAMLStringHere'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Form=[Windows.Markup.XamlReader]::Load( $reader )
$Form.ShowDialog() | out-null
Something like the snippet would be the end result.
I even added a button click event for fun.
[xml]$xaml = #'
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Strike Email" Height="137" Width="274" Background="White">
<Grid Background="#FF005D80">
<Button Content="1st Strike" HorizontalAlignment="Left" Height="50" Margin="10,30,0,0" VerticalAlignment="Top" Width="70" Background="White" x:Name="FirstStrike" />
<Button Content="3rd Strike" HorizontalAlignment="Left" Height="50" Margin="180,30,0,0" VerticalAlignment="Top" Width="70" Background="White"/>
<Button Content="2nd Strike" HorizontalAlignment="Left" Height="50" Margin="95,30,0,0" VerticalAlignment="Top" Width="70" Background="White"/>
</Grid>
</Window>
'#
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try
{
$Form=[Windows.Markup.XamlReader]::Load( $reader )
}
catch
{
Write-Host "Unable to load Windows.Markup.XamlReader. Some possible causes for this problem include: .NET Framework is missing PowerShell must be launched with PowerShell -sta, invalid XAML code was encountered."t
}
cls
$Button = $Form.FindName('FirstStrike')
$Button.Add_Click({ Write-Host "First Strike Button Clicked" -ForegroundColor Cyan})
$Form.ShowDialog() | out-null
References:
Technet - Integrating XAML into PowerShell
Learn Powershell - PowerShell and WPF: Buttons
I am trying to implement background commands that would initiate after the user clicks a button, and so far I have always ended up with my UI locking up while the job is on-going. I am currently using a somewhat time consuming for loop to check if my UI locks up. What can I do to let the job work in the background as the UI is freed up. I am not sure I want to complicate the code by adding runspaces. What am I doing incorrectly?
$inputXML = #"
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Title="Create User" Height="448.05" Width="656.017" ResizeMode="NoResize">
<Grid Margin="0,0,-6.8,-0.8" Background="#FFD7D7D7">
<Grid.RowDefinitions>
<RowDefinition Height="403*"/>
<RowDefinition Height="18*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="Create_User" Content="Create User" HorizontalAlignment="Left" Margin="67,224,0,0" VerticalAlignment="Top" Width="300" Height="26" RenderTransformOrigin="0.551,-0.671" IsEnabled="True">
<Button.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF3F3F3" Offset="0"/>
<GradientStop Color="#FFEBEBEB" Offset="0.5"/>
<GradientStop Color="#FFDDDDDD" Offset="0.5"/>
<GradientStop Color="#FFCDCDCD" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
<Label x:Name="fname" Content="" HorizontalAlignment="Left" Margin="43,11,0,0" VerticalAlignment="Top" Height="26" Width="10"/>
<Label x:Name="fname1" Content="First Name" HorizontalAlignment="Left" Margin="88,54,0,0" VerticalAlignment="Top" Width="72" RenderTransformOrigin="0.513,1.469" Height="26"/>
<Label x:Name="lname" Content="Last Name" HorizontalAlignment="Left" Margin="88,83,0,0" VerticalAlignment="Top" RenderTransformOrigin="-0.167,-0.458" Width="72" Height="25"/>
<TextBox x:Name="fnametxt" Height="23" Margin="167,54,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="0.501,0.452" HorizontalAlignment="Left" Width="136"/>
<Button x:Name="exitbtn" Content="Exit" HorizontalAlignment="Left" VerticalAlignment="Top" Width="135" Margin="447,365,0,0" Height="38" RenderTransformOrigin="0.489,0.462"/>
<Label x:Name="label" Content="Password" HorizontalAlignment="Left" Margin="92,113,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.064,0.601" Height="26" Width="68"/>
<PasswordBox x:Name="passwordBox" Margin="167,113,0,0" VerticalAlignment="Top" Height="24" HorizontalAlignment="Left" Width="136"/>
<TextBox x:Name="stsbox" HorizontalAlignment="Left" Height="62" Margin="67,267,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="300" Background="#FFDADADA" Foreground="Black" Opacity="0.45" SelectionBrush="#FF1D6EBF" RenderTransformOrigin="0.503,-0.59" IsReadOnly="True"/>
<TextBox x:Name="lnametxt" Height="23" Margin="167,85,0,0" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Left" Width="136"/>
</Grid>
</Window>
"#
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window'
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[xml]$xaml = $inputXML
#Read XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."}
$xaml.SelectNodes("//*[#Name]") | %{Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -Scope Global}
Function global:Get-FormVariables{
if ($global:ReadmeDisplay -ne $true) {$global:ReadmeDisplay=$true}
#write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
Get-FormVariables
$WPFCreate_User.Add_Click({
$test = { for($i=1; $i -le 100000; $i++){
$z = $i + $z
$z
}}
$job = Start-Job $test
Wait-Job $job
Receive-Job $job -OutVariable results
Remove-Job $job
$WPFstsbox.text = "$results`n"
})
$WPFexitbtn.add_click({
$form.Close() | out-null
exit})
$form.ShowDialog() | Out-null
Yes, PSJobs are truly "background" jobs.
When you call Start-Job, a separate process is started, executing your job/command.
In your script, the job itself doesn't block the calling thread, but your subsequent command (Wait-Job $job) does.
If you simply fire off Start-Job and return from the Click handler, your UI wouldn't lock up:
$button.add_Click({
$jobCode = { Start-Sleep -Seconds 10 }
$job = Start-Job $jobCode
})
you'll see that the UI becomes responsive again in way lees than 10 seconds.
The problem is that you no longer have access to the $job and no longer control over when it is to be displayed.
To compensate for this, you need background thread or a timed event that can periodically check up on the job(s) for you and output the result.
You could use a Timer and PowerShell's builtin eventing infrastructure for this:
# Create timer
$backgroundTimer = New-Object System.Timers.Timer
# Set interval (ms)
$backgroundTimer.Interval = 500
# Make sure timer stops raising events after each interval
$backgroundTimer.AutoReset = $false
# Have powershell "listen" for the event in the background
Register-ObjectEvent $backgroundTimer -EventName 'Elapsed' -SourceIdentifier 'timerElapsed' -Action {
# Loop through any completed jobs with data to return, from "oldest" to "newest"
while(($finishedJob = Get-Job |Where-Object {$_.State -eq 'Completed' -and $_.HasMoreData}|Select-Object -First 1))
{
# Update the text box on your WPF form with the results
# NOTE: the event is executed in a separate scope, thus the "global:" scope prefix
$global:WPFstsbox.Text = "$(Receive-Job $finishedJob)`n"
# Clean up the job
Remove-Job $finishedJob
}
# Restart timer
$backgroundTimer.Start()
}
# Enable the timer
$backgroundTimer.Enabled = $true