Powershell + XAML + Search Box + capture and display - powershell

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.

Related

Populate a ListView with a custom file list (FilePath and Size) in Powershell

I'm trying to make a remote copy tool, which works fine in the console but I'm having trouble creating the GUI for it.
When I populate my Listbox with the list of files to copy they just don't display as intended...
My problem is in the ChooseFiles function
#xaml Code
$inputXML = #"
<Window x:Class="RemoteCopy.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:RemoteCopy"
mc:Ignorable="d"
Title="Remote Copy" Height="556.841" Width="800">
<Grid>
<Label Content="Remote Computer's IP:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="RemoteIPTextbox" HorizontalAlignment="Left" Height="20" Margin="238,16,0,0" TextWrapping="Wrap" Text="IP address" VerticalAlignment="Top" Width="128"/>
<Label Content="Username: " HorizontalAlignment="Left" Height="26" Margin="10,40,0,0" VerticalAlignment="Top" Width="131"/>
<Label Content="Password: " HorizontalAlignment="Left" Height="26" Margin="10,70,0,0" VerticalAlignment="Top" Width="131"/>
<TextBox x:Name="UsernameTextbox" HorizontalAlignment="Left" Height="20" Margin="238,46,0,0" TextWrapping="Wrap" Text="Username" VerticalAlignment="Top" Width="128"/>
<Button x:Name="CheckConnectionButton" Content="Check Connection" HorizontalAlignment="Left" Margin="389,46,0,0" VerticalAlignment="Top" Width="107" Height="20"/>
<Button x:Name="ChooseFilesButton" Content="Choose Files" HorizontalAlignment="Left" Height="80" Margin="535,16,0,0" VerticalAlignment="Top" Width="122"/>
<ListView x:Name="FilesListView" HorizontalAlignment="Left" Height="234" Margin="33,128,0,0" VerticalAlignment="Top" Width="720">
<ListView.View>
<GridView>
<GridViewColumn Header="File"/>
<GridViewColumn Header="Size"/>
</GridView>
</ListView.View>
</ListView>
<Button x:Name="StartCopyButton" Content="Start Copy" HorizontalAlignment="Left" Height="24" Margin="33,374,0,0" VerticalAlignment="Top" Width="720"/>
<ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="20" Margin="113,418,0,0" VerticalAlignment="Top" Width="572"/>
<Label Content="Progress:" HorizontalAlignment="Left" Height="28" Margin="33,418,0,0" VerticalAlignment="Top" Width="67"/>
<Label Content="100%" HorizontalAlignment="Left" Height="28" Margin="714,418,0,0" VerticalAlignment="Top" Width="39"/>
<PasswordBox x:Name="Passwordbox" HorizontalAlignment="Left" Margin="238,78,0,0" VerticalAlignment="Top" Width="128"/>
<Button x:Name="ExitButton" Content="Exit" HorizontalAlignment="Left" Height="21" Margin="283,455,0,0" VerticalAlignment="Top" Width="213"/>
</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-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged or TextChanged properties in your textboxes (PowerShell cannot process them)"
throw
}
#===========================================================================
# Load XAML Objects In PowerShell
#===========================================================================
$xaml.SelectNodes("//*[#Name]") | %{"trying item $($_.Name)";
try {Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop}
catch{throw}
}
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
#===========================================================================
# Use this space to add code to the various form elements in your GUI
#===========================================================================
#my test folder
c:
Set-Location c:\temp
function DisplayInBytes($num)
#reference: https://stackoverflow.com/questions/24616806/powershell-display-files-size-as-kb-mb-or-gb
{
$suffix = "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"
$index = 0
while ($num -gt 1kb)
{
$num = $num / 1kb
$index++
}
"{0:N1} {1}" -f $num, $suffix[$index]
}
function ChooseFiles
{
$WPFFilesListView.Clear()
$filesToCopy = Get-Files
$filesToCopy.FileNames | ForEach-Object {
$currentfilesize = (Get-Item $_).Length
$currentfilesize = DisplayInBytes -num $currentfilesize
$row = New-Object System.Windows.Forms.ListViewItem($_)
[void]$row.SubItems.Add("$currentfilesize")
[void]$WPFFilesListView.Items.Add($row)
}
}
Function Get-Files($initialDirectory="")
#Reference to https://stackoverflow.com/questions/15885132/file-folder-chooser-dialog-from-a-windows-batch-script
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null
$myFile = new-object Windows.Forms.OpenFileDialog
$myFile.InitialDirectory = Get-Location
$myFile.Filter = "Text Files (*.txt)|*.txt|Powershell Script Files (*.ps1)|*.ps1|Batch Files (*.bat)|*.bat|All Files (*.*)|*.*"
$myFile.ShowHelp = $true
$myFile.Multiselect = $true
[void]$myfile.ShowDialog()
if ($myFile.Multiselect) { $myFile.FileNames } else { $myFile.FileName }
return $myFile
}
$WPFExitButton.Add_Click({$form.close()})
$WPFChooseFilesButton.Add_Click({ChooseFiles})
$Form.ShowDialog() | out-null
The Function to Choose my Files and Populate my Listbox is this one:
function ChooseFiles
{
$WPFFilesListView.Clear()
$filesToCopy = Get-Files
$filesToCopy.FileNames | ForEach-Object {
$currentfilesize = (Get-Item $_).Length
$currentfilesize = DisplayInBytes -num $currentfilesize
$row = New-Object System.Windows.Forms.ListViewItem($_)
[void]$row.SubItems.Add("$currentfilesize")
[void]$WPFFilesListView.Items.Add($row)
}
}
But my Result looks like this:
I intended to have the Filename and Filesize displayed,
If I test the Variables they work
Write-host $_
write-host $currentfilesize
One in each column
You need to change two things to fix the display:
Add a DisplayMemberBinding to the ListView
<ListView x:Name="FilesListView" HorizontalAlignment="Left" Height="234" Margin="33,128,0,0" VerticalAlignment="Top" Width="720">
<ListView.View>
<GridView>
<GridViewColumn Header="File" DisplayMemberBinding="{Binding File}"/>
<GridViewColumn Header="Size" DisplayMemberBinding="{Binding Size}"/>
</GridView>
</ListView.View>
</ListView>
Pass a pscustomobject with the specified binding properties to the ListView
function ChooseFiles
{
$WPFFilesListView.Clear()
$filesToCopy = Get-Files
$filesToCopy.FileNames | ForEach-Object {
$currentfilesize = (Get-Item $_).Length
$currentfilesize = DisplayInBytes -num $currentfilesize
$WPFFilesListView.Items.Add([pscustomobject]#{File=$_;Size=$currentfilesize})
}
}

Update Textbox if var changes

I want to update my WPF PowerShell GUI. I set up a small GUI with one button and one textbox to test my script. It looks like:
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
Add-Type -AssemblyName System.Windows.Forms
[xml]$XAML = #"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="test" Height="630" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<StackPanel>
<Button Name="test" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<TextBox Name="write" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</StackPanel>
</Grid>
</Window>
"#
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
try {
$Form = [Windows.Markup.XamlReader]::Load($reader)
} catch {
Write-Host "Something is wrong"; exit
}
$xaml.SelectNodes("//*[#Name]") | %{
Set-Variable -Name ($_.Name) -Value $Form.FindName($_.Name)
}
$test.Add_Click({
Write-Host $varvar
Start-Process powershell -Argument ".\varchecker.ps1"
})
$Form.ShowDialog() | Out-Null
By clicking the button I start another script where I read a variable. This looks like:
while ($true) {
$global:varvar = Get-Content var.txt
Write-Host $varvar
}
Now I want to print the var from the started script to the textbox in the other script. Is this possible? Or the better question: is that a good way to do that?
I don't think you can do it this way. You are trying to start a new PowerShell process, getting some data and passing it to the parent process. I think you can sort of do it in C with pipe() and fork() but havent found a way to do it in PowerShell yet.
You can however, use Start-Job, which creates a process in the background. Alternatively, you can look at Runspaces too for multithreading.
You can also use Invoke-Expression to call the script.
All you have to do from the script is return $Varvar.
If you can tell us what problem you are trying to solve with this method, maybe folks can be more helpful.
Getting values from a new PowerShell process inside your GUI is much too complicated if all you want is to read the contents of a file.
Why not do it all from the GUI code you have?
# [System.Reflection.Assembly]::LoadWithPartialName is deprecated.
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"
Title="test" Height="630" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<StackPanel>
<Button Name="test" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<TextBox Name="write" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</StackPanel>
</Grid>
</Window>
"#
$reader = New-Object System.Xml.XmlNodeReader $xaml
try {
$Form = [Windows.Markup.XamlReader]::Load($reader)
}
catch {
Write-Host "Something is wrong"; exit
}
# get references for the button and textbox objects into PowerShell variables.
$button = $Form.FindName("test")
$textBox = $Form.FindName("write")
$button.Add_Click({
# don't start a new PowerShell process, but simply read the file here
$textBox.Text = $var = Get-Content -Path 'PATH TO var.txt' -Raw
Write-Host $var
})
$Form.ShowDialog() | Out-Null
# clean-up
$reader.Dispose()

XAML to powershell

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

Do Jobs really work in background in powershell?

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

Problems converting ps1 to exe using PowerGUI

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!