I am trying to write data from a text file to a in an HTA.
I'm running a powershell script inside of the HTA, using VBscript for the input buttons
Get-TSSession -computername ismeta | where { $_.username -eq 'amis5235'} | format-table windowstationname,username,state,sessionid | out-file C:\windows\temp\PSTerminalServices.txt
I'm going to be using a for each loop for about 60 servers
Then I was hoping to write the output to a within the HTA, kind of like a streamer in VB or stacking a string the VBscript, something like:
strHTML = strHTML & "Running Process = " & objProcess.Name & " PID = " & objProcess.ProcessID & " Description = " & objProcess.Description & "<br>"
but it seems there should be a simpler way to do this.
I think this minimal HTA will solve your problem. It runs a command line and reads the output stream, one line every 1/10 second, then pushes the results into a textarea. You may want to alter your Powershell script to return the process details to STDOUT, but it will probably work.
<script language="Javascript">
var E, LineWriteTimerID
function execWithStatus(cmdLine){//Can't run minimized with Exec. Can't capture StdOut/StdErr with Run.
E = new ActiveXObject("WScript.Shell").Exec(cmdLine);
LineWriteTimerID = window.setInterval("writeOutLine()",100);//pause for 100ms
E.StdIn.Close();//must close input to complete a ps command
}
function writeOutLine(){
if(E.StdOut.AtEndOfStream) window.clearTimeout(LineWriteTimerID);
if(!E.StdErr.AtEndOfStream) txtResults.value += "ERROR: " + E.StdErr.ReadAll() + "\n";
if(!E.StdOut.AtEndOfStream) txtResults.value += E.StdOut.ReadLine() + "\n";
}
</script>
<textarea id=txtCmd style="width:90%" rows=1>
powershell.exe -noninteractive -command ls c:\windows\system32\drivers\etc\</textarea>
<button onclick="execWithStatus(txtCmd.value)">Run</button>
<br><textarea id=txtResults style="width:100%" rows=20></textarea>
Save this code as an .HTA file, change the contents of the txtCmd textarea to be your command line, and give it a try. Good Luck!
Ok Here is the way I use.
On the theorical point of view it consist in building an interface with Windows Forms and then put PowerSell code behind event.
On technical point of view two solutions :
1) Use visual studio free edition to build interface in C# and then a conversion tool to create the associate PowerShell source (french article here)
2) you can download freely (you just need to register) Sapiens PrimalFormsCE.exe (Community Edition)
This tool allow you create a form and then to generate Powershell associete code.
You can also build forms from crash here is a peace of sample code :
Add-Type -AssemblyName system.Windows.Forms
# Create the form
$form = New-Object Windows.Forms.Form
$form.Text = "Test Saisie"
$form.Size = New-Object System.Drawing.Size(250,154)
# Create EntryFiel
$TB_Saisie = New-Object System.Windows.Forms.TextBox
$TB_Saisie.Location = New-Object System.Drawing.Point(50,31)
$TB_Saisie.Size = New-Object System.Drawing.Size(150,32)
# Create "Ok" Button
$PB_Ok = New-Object System.Windows.Forms.Button
$PB_Ok.Text = "Ok"
$PB_Ok.Location = New-Object System.Drawing.Point(50,62)
$PB_Ok.Size = New-Object System.Drawing.Size(50,32)
$PB_Ok.DialogResult = [System.Windows.Forms.DialogResult]::OK
# Create "Cancel" Button
$PB_Cancel = New-Object System.Windows.Forms.Button
$PB_Cancel.Text = "Cancel"
$PB_Cancel.Location = New-Object System.Drawing.Point(150,62)
$PB_Cancel.Size = New-Object System.Drawing.Size(50,32)
$PB_Cancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
# Add controls to the form
$form.Controls.Add($PB_Ok)
$form.Controls.Add($PB_Cancel)
$form.Controls.Add($TB_Saisie)
# Message loop
$Res = $form.ShowDialog()
If ($Res -eq [System.Windows.Forms.DialogResult]::OK)
{
Write-Host ("Accepted : {0}" -f $TB_Saisie.Text)
}
else
{
Write-Host "Cancel"
}
Related
I have been trying to create a Powershell form that uses the input to do other tasks. But the data needs to be validated (can't be blank, etc...) But when I run it and have to correct some of the inputs I get Multiple values from the return with the blank and my final. Also the variable doesn't leave the function. I have tried a number of things like Clear-Variable and Out-Null and I know it has to do with the way Powershell handles return data but I'm stuck.
I have a simplified version here:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName Microsoft.VisualBasic
$BuildName = ""
function Enter-BuildInfo {
# Create a new form
$CoreForm = New-Object system.Windows.Forms.Form
$CoreForm.ClientSize = ‘220,100’
$CoreForm.text = “Enter Name”
$CoreForm.BackColor = “#03335a”
$Coreform.StartPosition = 'CenterScreen'
$Coreform.Topmost = $true
$OKButton = New-Object System.Windows.Forms.Button -Property #{
Location = New-Object System.Drawing.Size(160,60)
Size = New-Object System.Drawing.Size(50,25)
BackColor = "#ffffff"
Text = 'OK'
DialogResult = [System.Windows.Forms.DialogResult]::OK
}
$CoreForm.AcceptButton = $OKButton
$CoreForm.Controls.Add($OKButton)
### Inserting the text box that will accept input
$textbox1 = New-Object System.Windows.Forms.TextBox
$textbox1.Location = New-Object System.Drawing.Point(10,25) ### Location of the text box
$textbox1.Size = New-Object System.Drawing.Size(175,25) ### Size of the text box
$textbox1.Multiline = $false ### Allows multiple lines of data
$textbox1.AcceptsReturn = $false ### By hitting enter it creates a new line
#$textbox1.ScrollBars = "Vertical" ### Allows for a vertical scroll bar if the list of text is too big for the window
$Coreform.Controls.Add($textbox1)
$BuildName = $textbox1.text
$Coreresult = $CoreForm.ShowDialog()
if ($Coreresult -eq [Windows.Forms.DialogResult]::OK)
{
$BuildName = $textbox1.text
}
if ($BuildName -ne "")
{
Write-Host "Click OK Name: $BuildName "
}
else
{
[Microsoft.VisualBasic.Interaction]::MsgBox("BuildName can not be blank",'OKOnly,SystemModal,Information', 'Retry Name')
Enter-BuildInfo
}
Write-Host "Inside Function Name: $BuildName "
}
Enter-BuildInfo
Write-Host "Out of Function Name: $BuildName "
My results:
Ok
Click OK Name: Test Name
Inside Function Name: Test Name
Inside Function Name:
Out of Function Name:
Variables defined in a function are never visible to the function's caller.
Make your function output data (do not use Write-Host for that) that you want the caller to see, which the caller has to capture ($output = Enter-BuildInfo)
Perform input validation in WinForm forms via event handlers that are called before the form closes (returns from the .ShowDialog() call).
That way there's no need for recursive calls to your function, which complicate matters.
Specifically:
Initialize your $OKButton's .Enabled property to $false
Pass an event handler (script block) to $textbox1.add_TextChanged(), in which you enable $OKButton only if $textbox1.Text.Trim() -ne ''
Note: To handle the enabling logic for the OK button based on multiple other controls:
Define a function that implements the validation logic based on the state of all relevant controls.
Call that function from the script blocks passed to the .add_{eventName}() methods of all relevant controls.
I start building up a small winform with Copy button and a label under this button.
When I click on Copy button it starts to copy files from source to destination.
I would like to run this asynchroniously so I don't want form to be freezed while copy operation runs. That's why I use Job. After a successful copy I need feedback of copy and show an "OK" text with green color but it is not working.
Here is my code:
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.Application]::EnableVisualStyles()
Function Copy-Action{
$Computername = "testclient"
$Source_Path = "C:\temp\"
$Destination_Path = "\\$Computername\c$\temp"
$job = Start-Job -Name "Copy" -ArgumentList $Source_Path,$Destination_Path –ScriptBlock {
param($Source_Path,$Destination_Path)
Copy-Item $Source_Path -Destination $Destination_Path -Recurse -Force
}
Register-ObjectEvent $job StateChanged -MessageData $Status_Label -Action {
[Console]::Beep(1000,500)
$Status_Label.Text = "OK"
$Status_Label.ForeColor = "#009900"
$eventSubscriber | Unregister-Event
$eventSubscriber.Action | Remove-Job
} | Out-Null
}
# DRAW FORM
$form_MainForm = New-Object System.Windows.Forms.Form
$form_MainForm.Text = "Test Copy"
$form_MainForm.Size = New-Object System.Drawing.Size(200,200)
$form_MainForm.FormBorderStyle = "FixedDialog"
$form_MainForm.StartPosition = "CenterScreen"
$form_MainForm.MaximizeBox = $false
$form_MainForm.MinimizeBox = $true
$form_MainForm.ControlBox = $true
# Copy Button
$Copy_Button = New-Object System.Windows.Forms.Button
$Copy_Button.Location = "50,50"
$Copy_Button.Size = "75,30"
$Copy_Button.Text = "Copy"
$Copy_Button.Add_Click({Copy-Action})
$form_MainForm.Controls.Add($Copy_Button)
# Status Label
$Status_Label = New-Object System.Windows.Forms.Label
$Status_Label.Text = ""
$Status_Label.AutoSize = $true
$Status_Label.Location = "75,110"
$Status_Label.ForeColor = "black"
$form_MainForm.Controls.Add($Status_Label)
#show form
$form_MainForm.Add_Shown({$form_MainForm.Activate()})
[void] $form_MainForm.ShowDialog()
Copy is successful but showing an "OK" label won't. I have placed a Beep but it doesn't work too.
What am I doing wrong ? Any solution to this?
Thank you.
Let me offer alternatives to balrundel's helpful solution - which is effective, but complex.
The core problem is that while a form is being shown modally, with .ShowDialog(), WinForms is in control of the foreground thread, not PowerShell.
That is, PowerShell code - in the form's event handlers - only executes in response to user actions, which is why your job-state-change event handler passed to Register-ObjectEvent's -Action parameter does not fire (it would eventually fire, after closing the form).
There are two fundamental solutions:
Stick with .ShowDialog() and perform operations in parallel, in a different PowerShell runspace (thread).
balrundel's solution uses the PowerShell SDK to achieve this, whose use is far from trivial, unfortunately.
See below for a simpler alternative based on Start-ThreadJob
Show the form non-modally, via the .Show() method, and enter a loop in which you can perform other operations while periodically calling [System.Windows.Forms.Application]::DoEvents() in order to keep the form responsive.
See this answer for an example of this technique.
A hybrid approach is to stick with .ShowDialog() and enter a [System.Windows.Forms.Application]::DoEvents() loop inside the form event handler.
This is best limited to a single event handler applying this technique, as using additional simultaneous [System.Windows.Forms.Application]::DoEvents() loops invites trouble.
See this answer for an example of this technique.
Simpler, Start-ThreadJob-based solution:
Start-ThreadJob is part of the the ThreadJob module that offers a lightweight, thread-based alternative to the child-process-based regular background jobs and is also a more convenient alternative to creating runspaces via the PowerShell SDK.
It comes with PowerShell (Core) 7+ and can be installed on demand in Windows PowerShell with, e.g., Install-Module ThreadJob -Scope CurrentUser.
In most cases, thread jobs are the better choice, both for performance and type fidelity - see the bottom section of this answer for why.
In addition to syntactic convenience, Start-ThreadJob, due to being thread-based (rather than using a child process, which is what Start-Job does), allows manipulating the calling thread's live objects.
Note that the sample code below, in the interest of brevity, performs no explicit thread synchronization, which may situationally be required.
The following simplified, self-contained sample code demonstrates the technique:
The sample shows a simple form with a button that starts a thread job, and updates the form from inside that thread job after the operation (simulated by a 3-second sleep) completes, as shown in the following screen shots:
Initial state:
After pressing Start Job (the form remains responsive):
After the job has ended:
The .add_Click() event handler contains the meat of the solution; the source-code comments hopefully provide enough documentation.
# PSv5+
using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type -AssemblyName System.Windows.Forms
# Create a sample form.
$form = [Form] #{
Text = 'Form with Thread Job'
ClientSize = [Point]::new(200, 80)
FormBorderStyle = 'FixedToolWindow'
}
# Create the controls and add them to the form.
$form.Controls.AddRange(#(
($btnStartJob = [Button] #{
Text = "Start Job"
Location = [Point]::new(10, 10)
})
[Label] #{
Text = "Status:"
AutoSize = $true
Location = [Point]::new(10, 40)
Font = [Font]::new('Microsoft Sans Serif', 10)
}
($lblStatus = [Label] #{
Text = "(Not started)"
AutoSize = $true
Location = [Point]::new(80, 40)
Font = [Font]::new('Microsoft Sans Serif', 10)
})
))
# The script-level helper variable that maintains a collection of
# thread-job objects created in event-handler script blocks,
# which must be cleaned up after the form closes.
$script:jobs = #()
# Add an event handler to the button that starts
# the background job.
$btnStartJob.add_Click( {
$this.Enabled = $false # To prevent re-entry while the job is still running.
# Signal the status.
$lblStatus.Text = 'Running...'
$form.Refresh() # Update the UI.
# Start the thread job, and add the job-info object to
# the *script-level* $jobs collection.
# The sample job simply sleeps for 3 seconds to simulate a long-running operation.
# Note:
# * The $using: scope is required to access objects in the caller's thread.
# * In this simple case you don't need to maintain a *collection* of jobs -
# you could simply discard the previous job, if any, and start a new one,
# so that only one job object is ever maintained.
$script:jobs += Start-ThreadJob {
# Perform the long-running operation.
Start-Sleep -Seconds 3
# Update the status label and re-enable the button.
($using:lblStatus).Text = 'Done'
($using:btnStartJob).Enabled = $true
}
})
$form.ShowDialog()
# Clean up the collection of jobs.
$script:jobs | Remove-Job -Force
Start-Job creates a separate process, and when your form is ready to receive events, it can't listen to job events. You need to create a new runspace, which is able to synchronize thread and form control.
I adapted code from this answer. You can read much better explanation there.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.Application]::EnableVisualStyles()
Function Copy-Action{
$SyncHash = [hashtable]::Synchronized(#{TextBox = $Status_Label})
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ThreadOptions = "UseNewThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("SyncHash", $SyncHash)
$Worker = [PowerShell]::Create().AddScript({
$SyncHash.TextBox.Text = "Copying..."
# Copy-Item
$Computername = "testclient"
$Source_Path = "C:\temp\"
$Destination_Path = "\\$Computername\c$\temp"
Copy-Item $Source_Path -Destination $Destination_Path -Recurse -Force
$SyncHash.TextBox.ForeColor = "#009900"
$SyncHash.TextBox.Text = "OK"
})
$Worker.Runspace = $Runspace
$Worker.BeginInvoke()
}
# DRAW FORM
$form_MainForm = New-Object System.Windows.Forms.Form
$form_MainForm.Text = "Test Copy"
$form_MainForm.Size = New-Object System.Drawing.Size(200,200)
$form_MainForm.FormBorderStyle = "FixedDialog"
$form_MainForm.StartPosition = "CenterScreen"
$form_MainForm.MaximizeBox = $false
$form_MainForm.MinimizeBox = $true
$form_MainForm.ControlBox = $true
# Copy Button
$Copy_Button = New-Object System.Windows.Forms.Button
$Copy_Button.Location = "50,50"
$Copy_Button.Size = "75,30"
$Copy_Button.Text = "Copy"
$Copy_Button.Add_Click({Copy-Action})
$form_MainForm.Controls.Add($Copy_Button)
# Status Label
$Status_Label = New-Object System.Windows.Forms.Label
$Status_Label.Text = ""
$Status_Label.AutoSize = $true
$Status_Label.Location = "75,110"
$Status_Label.ForeColor = "black"
$form_MainForm.Controls.Add($Status_Label)
#show form
$form_MainForm.Add_Shown({$form_MainForm.Activate()})
[void] $form_MainForm.ShowDialog()
I have cmd script. I want to use timer for 10 second to give a decision to continue cmd script process or to pause it.
I want to put this script at the first line of my cmd script
powershell.exe -ExecutionPolicy Bypass -File %~dp0\Pause_GUI.ps1
It will pop up 10s countdown, after 10s, it will continue the cmd script process by return errorlevel, but if we click button pause, the cmd script will pause, also by return errorlevel.
Anyone can help please
UPDATED
#------------------------------------------- Add in Forms Controls -------------------------------------------#
Add-Type -AssemblyName System.Windows.Forms
#-------------------------------------------------------------------------------------------------------------#
#---------------------------------------- Begins creation of the form ----------------------------------------#
$MainForm = New-Object System.Windows.Forms.Form
$MainForm.Text = "Message"
$MainForm.Width = 500
$MainForm.Height = 200
$MainForm.StartPosition = "CenterScreen"
$MainForm.BackColor = "#e2e2e2"
#-------------------------------------------------------------------------------------------------------------#
#----------------------------------------------- Button Clicks -----------------------------------------------#
$Auto_Button = ({ $global:result=1
$MainForm.Close() })
$Manual_Button = ({ $global:result=0
$MainForm.Close() })
#-------------------------------------------------------------------------------------------------------------#
#-------------------------------------------------- Buttons --------------------------------------------------#
$Automatic = New-Object System.Windows.Forms.Button
$Automatic.Location = New-Object System.Drawing.Size(110,80)
$Automatic.Size = New-Object System.Drawing.Size(120,30)
$Automatic.Text = "Continue After 10s"
$Automatic.BackColor = "#e47104"
$Automatic.Add_Click($Auto_Button)
$MainForm.Controls.Add($Automatic)
$Manual = New-Object System.Windows.Forms.Button
$Manual.Location = New-Object System.Drawing.Size(270,80)
$Manual.Size = New-Object System.Drawing.Size(100,30)
$Manual.Text = "Pause"
$Manual.BackColor = "#e47104"
$Manual.Add_Click($Manual_Button)
$MainForm.Controls.Add($Manual)
#-------------------------------------------------------------------------------------------------------------#
#--------------------------------------------- Displays the Form ---------------------------------------------#
$result=0
$MainForm.ShowDialog()
exit $result
#-------------------------------------------------------------------------------------------------------------#
How to handle the button "Continue after 10s" as a timer? And the GUI will close automatically after 10s
You need a System.Windows.Forms.Timer-object that counts your time and a .tick-event that will trigger when the time has come. However you need to stop (and dispose) the timer or it will keep on triggering the event even when the window is closed. (In Powershell ISE that could cause windows to close as soon as you load them). To grab the timer from within it's own event you need to adress it in the right scope. I used the global-scope for that.
$Auto_Button = ({
$global:Counter = 0
$global:timer = New-Object -type System.Windows.Forms.Timer
$global:timer.Interval = 1000
$global:timer.add_Tick({
if ($Counter -eq 10){
write-host $global:counter
$global:timer.Stop()
$global:timer.Dispose()
$result=1
$MainForm.Close()
$global:Counter++
}else{
write-host $global:counter
$global:Counter++
}
})
$global:timer.Start()
})
It used to work but now it suddently terminates less than a second after being opened. I set the execution policy to unrestricted & re-installed Windows yet it still does not work...
The .ps1 shows up for 1 second in task manager before windows security health service when it's run using .vbs & then disappears: https://i.imgur.com/VNX7NKx.png
Here's the script (its purpose is to show notification messages):
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$notifyobj = New-Object System.Windows.Forms.NotifyIcon
$notifyobj.icon = "c:/users/work/Pictures/icon.ico"
$notifyobj.BalloonTipTitle = "New Message"
$notifyobj.BalloonTipText = "C"
$notifyobj.Visible = $True
$notifyobj.ShowBalloonTip(1000)
$notifyobj.Dispose()
More info on this thread.
Odd for sure, and there should have been really no reason you should have had to reinstall Windows for your effort. So, something else is impacting this. Hard to say what though.
Try this version to see if you have any / more success. It takes a different approach, but works on my systems.
Function Show-Notification
{
Param
(
[string]$MessageType,
[string]$MessageText,
[string]$MessageTitle
)
#load Windows Forms and drawing assemblies
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
#define an icon image pulled from PowerShell.exe
$Icon=[system.drawing.icon]::ExtractAssociatedIcon((join-path $pshome powershell.exe))
$Notify = New-Object System.Windows.Forms.NotifyIcon
$Notify.icon = $Icon
$Notify.visible = $True
#define the tool tip icon based on the message type
switch ($messagetype)
{
"Error" {$MessageIcon = [System.Windows.Forms.ToolTipIcon]::Error}
"Info" {$MessageIcon = [System.Windows.Forms.ToolTipIcon]::Info}
"Warning" {$MessageIcon = [System.Windows.Forms.ToolTipIcon]::Warning}
Default {$MessageIcon = [System.Windows.Forms.ToolTipIcon]::None}
}
#display the balloon tipe
$Notify.showballoontip($Notification_timeout,$MessageTitle,$MessageText,$MessageType)
}
Show-Notification -MessageType Info -MessageText 'some message' -MessageTitle 'New Alert'
Update
Modifying your posted code to match a few items in mine, allows your code to work in the ISE, console as expected.
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
$notifyobj = New-Object System.Windows.Forms.NotifyIcon
# I don't have your icon, so, using what I know I can reach
$notifyobj.Icon = [system.drawing.icon]::ExtractAssociatedIcon((join-path $pshome powershell.exe))
$notifyobj.BalloonTipTitle = "New Message"
$notifyobj.BalloonTipText = "C"
$notifyobj.Visible = $True
$notifyobj.ShowBalloonTip(1000)
$notifyobj.Dispose()
Im using windows Windows.Forms.RichTextBox to redirect my powershell script output "$var".
Detect.Urls is already enabled and working, but unable to open them by clicking.
Can any one help me with the code for link click event handler in powershell script........
$outputBox = New-Object System.Windows.Forms.RichTextBox
$outputBox.Location = New-Object System.Drawing.Size(10,150)
$outputBox.Size = New-Object System.Drawing.Size(700,300)
$outputBox.MultiLine = $True
$outputBox.SelectionIndent = 8
$outputBox.SelectionHangingIndent = 3
$outputBox.SelectionRightIndent = 12
$outputBox.ScrollBars = "ForcedBoth"
$Form.Controls.Add($outputBox)
$outputBox.Text = $var
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()
You have to handle click events by yourself
$outputBox.add_LinkClicked({
Start-Process -FilePath $_.LinkText
})
Will open link in the default browser when clicked.
This is how I do it with Powershell Studio...right click the control and add a new event...add the linkclicked event and then go to the script and add the following.
depending on what you want to be opened you may need to used something other than explorer, but the $_.linktext should have the link you want from the url. note if there are spaces you will have to something to replace them as the url will be broke at the first space it encounters.
$Linkclicked = $_.LinkText
explorer.exe $Linkclicked