Powershell start-sleep freeze/block interaction on my winforms - winforms

I am trying to enter a 10 second wait in order to execute a command in my script.
The problem is that when I run start-sleep my interface flickers or blocks the window.
I tried to do a special function without using start-sleep but the same thing happens.
First solution (Freeze/Block interaction windows)
if ($Timeout -gt 0) {
Start-Sleep -seconds $Timeout
}
Second solution (Freeze/Block interaction windows)
if ($Timeout -gt 0) {
Timeout $Timeout
}
function Timeout($seconds) {
$doneDT = (Get-Date).AddSeconds($seconds)
while($doneDT -gt (Get-Date)) {
$secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds
write-host $secondsLeft
}
}
How could I do to timeout code execution without freezing my winforms? Thank you

I would suggest you to do a Background Job using Start-Job (easiest) or Runspace (a little hard to do but efficient) for your task so u do not have to rely on the sleep timer to make sure ur command completed. But that is your prerogative.
However, you can use this neat little trick although not ideal, to counter your non-responsive form problem. Use the below block to introduce the 10s delay.
for ($i = 0; $i -lt 50; $i++)
{
Start-Sleep -Milliseconds 200
[System.Windows.Forms.Application]::DoEvents()
}
EDIT: Including suggestions.
Sure you can do this inside WinForms. I have done this before. I just moved onto Runspaces later for reasons of efficiency and resources. But for simple purposes, Start-Job is the easiest way to go.
$ScriptBlock = {
#super lengthy code enclosed in paranthesis to create a scriptblock
#say takes a large amount of time to complete
}
$myJob = Start-Job -ScriptBlock $ScriptBlock
Do
{
Start-Sleep -Milliseconds 100
[System.Windows.Forms.Application]::DoEvents()
}
While ($myJob.State -ne "Completed")
$myJob | Receive-Job
Now you can include contingencies for when the job fails and stuff, but I will let u figure that out.

Related

Powershell & WPF - Pass MicrosoftTeams Connection to a ScriptBlock

I have a script, on which I connect to MicrosoftTeams (connect-microsoftteams)
since I have a long running operation, I am trying to run that code in a separate job. however, I do not know how I can pass along the already existing connection to MicrosoftTeams, to that scriptblock.
if (!$Connection) {$Connection = Connect-MicrosoftTeams}
$scriptBlocks = #{
$btns[0] =
{
param ([Parameter(Mandatory=$true)]$Users)
#Connect-MicrosoftTeams
$agents = #()
foreach ($a in $Users) {
$user = get-csonlineuser $a.ObjectID
if ($user) {
$agents += $user
}
}
return $agents
}
}
I launch this using:
foreach ($btn in $btns) {
$btn.Add_Click({
# Temporarily disable this button to prevent re-entry.
$this.IsEnabled = $false
# Show a status message in the associated text box.
$txtBoxes[$this].Visibility = "Visible"
# Asynchronously start a background thread job named for this button.
# Note: Would work with Start-Job too, but that runs the code in *child process*,
# which is much slower and has other implications.
$null = Start-Job -Name $this.Name $scriptBlocks[$this]
})
}
I later do some checks to see if the job completed.
However, this creates a new Powershell instance, that does not have the $Connection to MicrosoftTeams as the 'parent' has..
[edit]
I tried some more, like passing in a Get-Credential, this is almost willing to work.. except MFA is being a problem
$scriptBlocks = #{
$btns[0] = {
param ($Credential)
Connect-MicrosoftTeams -Credential $Credential
$global:AllCallQueues = Get-CsCallQueue -first 10000
}
}
this Produces:
Loaded Module 'Microsoft.Teams.ConfigAPI.Cmdlets'
One or more errors occurred.
AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access 'xxx'.
Trace ID: xxx
Correlation ID: xx
Timestamp: 2021-10-26 18:58:45Z
One or more errors occurred.
Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "GetRemoteNewCsOnlineSession" with "1" argument(s): "Run either Connect-MicrosoftTeams or new-csonlinesession before runnin
g cmdlets.""
all credits for this code go to https://stackoverflow.com/a/65531439/3014542

Press hotkey to rerun script during a loop but don't wait for it

Basically I have a script that runs and it Start-Sleep for 30 minutes then loops around again and re-runs. I was wondering if there was a way where you could click on the window, press a hotkey, and have it just refresh manually?
while($true){
$data = #("device1", "device2")
ForEach ($server in $data) {
ping $server
start-sleep 1800
clear
}
}
I found through other methods that the below has allowed me to check the screen for any key press and break if there is one, but still be in a start-sleep status.
$timeout = New-TimeSpan -Minutes 30
$sw = [diagnostics.stopwatch]::StartNew()
while ($sw.elapsed -lt $timeout){
if ($host.UI.RawUI.KeyAvailable) {
$key = $host.UI.RawUI.ReadKey()
break
}
start-sleep -seconds 5
}
My guess, your best bet is to implement a for loop inside your main while loop that does check for key press once every 5-15 seconds and if it detects that, it breaks itself, so your while loop can start over, here are a few links that seem to be relevant:
Waiting for user input with a timeout
https://blogs.msdn.microsoft.com/timid/2014/01/29/read-host-with-a-timeout-kind-of/
How to stop while($true) that contains start-sleep in powershell
Is there a way to catch ctrl-c and ask the user to confirm?

Powershell find silverlight time elasped

I'm trying to use PowerShell to find the total time elapsed for a Silverlight web application (IE10) to load. Since this is a Silverlight app, I wished to find the time elapsed AFTER Silverlight loads, not just when the web page loads. I've tried the following but Silverlight always crashes.
$ie = New-Object -ComObject internetexplorer.application
$ie.visible = $true
while($ie.busy){}#wait for browser to load
$time = measure-command -expression{
$ie.navigate('http://www.mysilverlightapp.com')
while($ie.readyState -ne 4){}
}
write-host "$($time.totalseconds)"
Please help me on what I'm doing wrong.

winform displaying in classic view outside of ISE

Not sure what is causing this issue but I have a PowerShell script that uses Windows Forms as the UI. When developing it inside of the PowerShell ISE everything looked nice with modern style buttons. When I run it from PowerShell it displays in a Windows Classic style view and crashes when I make a call to New-Object System.Windows.Forms.SaveFileDialog. Is there a fix for this that I can add to my code so my script not only looks better but actually functions outside of the ISE?
Edit:
This is my function where I call SaveFileDialog. This works inside of the ISE but when I run the script from PowerShell itself it crashes when I call this.
function exportToCSV([System.Object[]] $expArray) {
$save = New-Object System.Windows.Forms.SaveFileDialog
$save.CreatePrompt = $False
$save.SupportMultiDottedExtensions = $True
$save.DefaultExt = "csv"
$save.Filter = "CSV (*.csv) | *.csv*"
$save.Title = "Export to CSV"
if ($save.ShowDialog() -eq "OK") {
$expArray | export-csv $save.FileName
}
}
Include the following command in your script before showing the form to get modern style appearance.
[System.Windows.Forms.Application]::EnableVisualStyles()
This should however not affect the savefiledialog. I'm gonna need more to help you with that since you're saying it works in ISE, but not in normal console.
A workaround for your SaveFileDialog is adding:
$save.ShowHelp = $true
In PS3.0 everything works fine, but in PS2.0 the dialog doesn't show. The ShowHelp fixes that, but also gives you an old-style dialog. However, functionality is more imporant then appearance. :)

In PowerShell Form.Show() does not work right, but Form.ShowDialog() does

I am trying to display an image via powershell. I made a script based on this forum post.
If I use ShowDialog() it works fine, except the powershell execution stops while the dialog is up. However, that is by design for a modal dialog. If I call Form.Show() in PowershellISE the form shows up, but freezes and cannot be moved or dismissed. Behavior is similar if I copy and past the code to a powershell console.
How do I make the dialog non-modal, and not freeze.
First Answer Why it appends.
In a Windows graphic program the thread which create a Window must loop in a message pump in order to redistribute (translate) messages coming from the user action to events in his Windows.
In a modal window, the modal code that handles the window display runs its own message pump loop and doesn't return until the window is closed. That's why the code after ShowDialog() won't execute until the window is closed.
Show(), just ask to show the Window, but if there is no pump loop to manage the messages coming from user action, it just freezes.
Second a simple way to have two threads
The CmdLet start-job use another thread from the pool allocated by Powershell so it makes the dialog non-modal, and it does not freeze.
function goForm
{
[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
$file = (get-item 'C:\temp\jpb.png')
#$file = (get-item "c:\image.jpg")
$img = [System.Drawing.Image]::Fromfile($file);
# This tip from http://stackoverflow.com/questions/3358372/windows-forms-look-different-in-powershell-and-powershell-ise-why/3359274#3359274
[System.Windows.Forms.Application]::EnableVisualStyles();
$form = new-object Windows.Forms.Form
$form.Text = "Image Viewer"
$form.Width = $img.Size.Width;
$form.Height = $img.Size.Height;
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $img.Size.Width;
$pictureBox.Height = $img.Size.Height;
$pictureBox.Image = $img;
$form.controls.add($pictureBox)
$form.Add_Shown( { $form.Activate() } )
$form.ShowDialog()
}
Clear-Host
start-job $function:goForm
$name = Read-Host "What is you name"
Write-Host "your name is $name"
There are ways to make this work, but nothing is worth spending five hours explaining on an open forum. There are other free, shrink-wrapped ways to do this on powershell. Most notably with the free WPF powershell toolkit: Show-UI at http://showui.codeplex.com/ (previously known as WPK and/or PowerBoots - they are merged now.)
If your goal is actually to not block the interactive console when an image is shown then you still can use the script as it is with ShowDialog but you should start it using, for example, Start-Job. Thus, the dialog is still modal but it blocks execution in another runspace. The main runspace still can be used for invoking other commands.
Caveats: 1) You should close all opened dialogs before closing the interactive console. 2) If you care, you should remove completed jobs yourself (when a dialog is closed a job that started it still exists).
I use a similar approach in my custom host and it works fine. I also tested it with the script from your link. I changed it slightly so that it is called show-image.ps1 and accepts a file path as a parameter.
This command shows an image and blocks the calling runspace:
show-image.ps1 C:\TEMP\_110513_055058\test.png
This command shows an image and does not block the calling runspace:
Start-Job { show-image.ps1 $args[0] } -ArgumentList C:\TEMP\_110513_055058\test.png
Building on #JPBlanc's anwer, this would also be possible (and faster) using a runspace.
Here's a basic example (the rest would basically remain the same)
$ps = [PowerShell]::Create()
[void]$ps.AddScript({
Add-Type -AssemblyName System.Windows.Forms
$form = [Windows.Forms.Form]::new()
$form.ShowDialog()
})
[void]$ps.BeginInvoke()

Resources