Best way to use a form in an existing script

Ask questions about creating Graphical User Interfaces (GUI) in PowerShell and using WinForms controls.
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
This topic is 10 years and 8 months old and has exceeded the time allowed for comments. Please begin a new topic or use the search feature to find a similar but newer topic.
Locked
User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 2 times

Re: Best way to use a form in an existing script

Post by davidc »

Just to be clear, many people who reply to posts are not SAPIEN employees. Instead they are volunteers who choose to assist others (thank you!). SAPIEN employees will be denoted with the SAPIEN logo. I know sometimes there are confusions as to who works for SAPIEN and who doesn't :)

Thank you

David
David
SAPIEN Technologies, Inc.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Best way to use a form in an existing script

Post by jvierra »

I completely agree with David. What you are trying to do can very easily be done with PowerShell Studio (PSS). To help you understand what you ae up against eith using formsin POwerSHell I have a little demo. Follow it thrugh to the end and you will begin to see how forms work in WIndows and in the Net Framework. Once that is understaood you will be able to see ways to use PSS to accomplish very tricky scenarios.

Start by pasting the following code into the PowerShell CLI. DO not use ISE because it does not behave well with minimalist forms.
PowerShell Code
Double-click the code block to select all.
function New-DemoForm{

	#----------------------------------------------
	#----------------------------------------------
	[void][reflection.assembly]::Load("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

	#----------------------------------------------
	$theForm = New-Object System.Windows.Forms.Form
	$textbox1 = New-Object System.Windows.Forms.TextBox
	$buttonCancel = New-Object System.Windows.Forms.Button
	$buttonOK = New-Object System.Windows.Forms.Button
	$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState


	$theForm.Controls.Add($textbox1)
	$theForm.Controls.Add($buttonOK)
	$theForm.Controls.Add($buttonCancel)
	
	#$theForm.CancelButton = $buttonCancel
	#$theForm.AcceptButton = $buttonOK
	$theForm.ClientSize = '284, 262'
	$theForm.FormBorderStyle = 'FixedDialog'
	$theForm.MaximizeBox = $False
	$theForm.MinimizeBox = $False
	$theForm.Name = "formTheBigForm"
	$theForm.StartPosition = 'CenterScreen'
	$theForm.Text = "The Big Form"

	# textbox1
	#
	$textbox1.Location = '46, 33'
	$textbox1.Name = "textbox1"
	$textbox1.Size = '167, 20'
	$textbox1.TabIndex = 1
	$textbox1.Text = 'The Big Form'
	
	# buttonCancel
	#
	$buttonCancel.DialogResult = 'Cancel'
	$buttonCancel.Location = '197, 198'
	$buttonCancel.Name = "buttonCancel"
	$buttonCancel.Size = '75, 23'
	$buttonCancel.TabIndex = 2
	$buttonCancel.Text = "Cancel"
	$buttonCancel.UseVisualStyleBackColor = $true

	# buttonOK
	#
	$buttonOK.Anchor = 'Bottom, Right'
	$buttonOK.DialogResult = 'OK'
	$buttonOK.Location = '197, 227'
	$buttonOK.Name = "buttonOK"
	$buttonOK.Size = '75, 23'
	$buttonOK.TabIndex = 0
	$buttonOK.Text = "OK"
	$buttonOK.UseVisualStyleBackColor = $True
	
	$theForm

} #End Function
This is a simple functio0n that returns a simple form. Try to launch the form like this.

$myform=New-DemoForm
$myform.ShowDialog()

Play with the buttons to see what is returned. I will give you a bit of time to get this working and then I will give you a couple of two or three line tests to run to demonstrate some drawbacks and features of Windows.Forms.

Most of the 'programmers' I have worked with and trained in Windows Forms from classic API forms to MFC to Net Forms have had a very hard time understanding how Windows sees...well...'Windows'.

In Windows everything is a Window. A console is a system generated generic window that hosts a console program. PowerShell is a console program. PowerShell ISE is a console program hosted in a custom frame. PowerShell is not what is called a "Windows" program. It is a "Windows Console" program.

A console program can host a window in the Net Framework but it is limited to only one window. I will show you why after you become familiar with the script I posted above.

I will also show you a simple way to collect your information from a job or scriptblock without generating global or temporary variables. I will show you how to modify PSS forms to be usable in a scriptblock without copying and pasting.

All of this will require some tricks and some advanced programming knowledge of PowerShell and the Net Framework but once set up it should be pretty much transparent.
User avatar
jayterry
Posts: 29
Last visit: Wed Jul 05, 2023 10:55 pm

Re: Best way to use a form in an existing script

Post by jayterry »

David, thanks for the input and suggestions. And I do understand about Sapien employees vs others who post, but I realize you also wanted to mention that for the benefit of others who may be following this thread. As I said before, I do appreciate the feedback I've gotten from all.
User avatar
jayterry
Posts: 29
Last visit: Wed Jul 05, 2023 10:55 pm

Re: Best way to use a form in an existing script

Post by jayterry »

Mr. Vierra (I'm assuming that's your name) you have been exceedingly generous with your time and expertise and it is very much appreciated. However, my questions are about specific ways to build a clock and you keep trying to explain the philosophy of time.

As I said, I'm a developer with long experience in several languages. I'm certainly no guru but I know how to code win apps, the main msg message loop, using SendMessage and PostMessage, synchronous vs asynchronous calls, using Win APIs and .Net, how everything is a window, the diff between true win apps vs console apps, how Win forms processing works, etc. And I know Powershell well. If not for the constraints given to me for this particular project I could write it in VB, C++, or C#. Or I could build the form in C# and use Add-Type to compile it on the fly inside the PS script.

I have no doubt you are more knowledgeable than I in many of those areas and I could learn a great deal from you. But the reason for this thread was that I was not so familiar with PSS. I realized that because of my requirements in this particular case, I would not be using PSS as it was intended. But I also knew I should be able to use it to easily create the code for my forms and then take the generated output to use in my existing scripts. I needed input from others more knowledgeable about the different ways PSS generated code so that I could determine the most effective way to use it to get what I needed for my one-off usage.

In other words, I was looking for more information about the tool (PSS) and the different parts it can make available so I could figure out how best to assemble a custom clock, instead of needing information about how to manage time (Windows forms methodology). You have been very helpful to me in figuring out how to assemble my clock. :D

Thanks
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Best way to use a form in an existing script

Post by jvierra »

jayterry -

I think you are misunderstanding what I am trying to show you. I am trying to help you see how PSS can do what you want but you seem to think that you already know what I am going for.

I can tell by your question that you either do not understand the relationship between PowerShell and Net Forms or you are forgetting or ignoring some very specific issues.

If you are not interested in my showing you how to obtain your required result easily using PSS then I will leave the thread and let you work it out.

I am sorry if I appeared to be downplaying your expertise. It is just that you question shows that you do not understand the relationship between Net Forms, PowerShell and how PSS generate a form. Perhaps David can show you what I was going to show you. It is actually very easy. I use it frequently.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Best way to use a form in an existing script

Post by jvierra »

I am going to try one more thing to try and get you to see where I am going with this:

Here is how we do this in MFC:

Code: Select all

CDialog MyDlg(IDD_MYDIALOG);

   INT_PTR nRet = MyDlg.DoModal();
   switch ( nRet )
    ....

My question is; "How do you get the values back from the dialog?"

We can do exactly the same thing in PSS and it can be done with only two minor edits.
User avatar
jayterry
Posts: 29
Last visit: Wed Jul 05, 2023 10:55 pm

Re: Best way to use a form in an existing script

Post by jayterry »

You may be right, I certainly could be missing something or not understanding something. So please make your points, I would honestly like to hear them.

What is it about the relationship between PowerShell and Net Forms that you think I don't understand, have forgotten, or are ignoring?

Actually, please just start with the 2 minor edits you mentioned. Maybe that will clear it up.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Best way to use a form in an existing script

Post by jvierra »

jayterry wrote:You may be right, I certainly could be missing something or not understanding something. So please make your points, I would honestly like to hear them.

What is it about the relationship between PowerShell and Net Forms that you think I don't understand, have forgotten, or are ignoring?

Actually, please just start with the 2 minor edits you mentioned. Maybe that will clear it up.
Ok - do you remember how we return data from an MFC dialog?
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Best way to use a form in an existing script

Post by jvierra »

OK - We return the data from a PSS for in the same way. Just lie it is returned in any Net Form that is a dialog. You need to rad the data out of the dialog.

NO why is this? You said you needed to wrpa a form in a 'scriptblock'. By that I am assuming you mena you want to run the form as a 'Job' ining Invoke-Command. As you know, variables in a job are not available outside of the job. Thisis one weekness of PSS. It generates a lot of very convenient variables that we can address and access using 'sapien-sense' inside of PSS. The problem is none of these variables is accessible in the 'Job' object. Everyting in a job that is returned must be explicitly returned via either 'Write-Output' or 'Out-Default'.

We can use the old dialog methods. Just alter the end of the form script to return the form instead of the silly 'Ok' and 'Cancel'.

Return the form on Ok and return null (or return nothing) on 'Cancel'

You now have all of the variables in your form to access as needed. When you are done with the form just use Release-Variable and get rid of it.

Altering the templates for PSS is the easiest way to do this.

If you do not really need to use 'Invoke-command', and I believe you don't, then just use David's method as it does not require any special steps.
User avatar
jayterry
Posts: 29
Last visit: Wed Jul 05, 2023 10:55 pm

Re: Best way to use a form in an existing script

Post by jayterry »

Ah, think I may see why you think I don't understand!
It might be because you made an invalid assumption, or simply did not read one of my earlier posts carefully enough. In one of my very early posts I wrote
Using the "form as a scriptblock" method is pretty easy. I can just export a form with the runspace option. Then take the exported file and comment out the 5 or 6 statements at the top that sets up the runspace, give the scriptblock a name, and replace the "$psCmd.BeginInvoke()" with "invoke-command -scriptblock $formscriptblockname". The only other thing I might change would be within the scriptblock to change "Call-MyForm_pff | Out-Null" to "$dialogResult = Call-MyForm_pff" and add "return $dialogResult", so that the form scriptblock actually returns the dialogResult value.
If I had written "invoke-command -asjob $scriptblock" then you would be correct. The scriptblock would be as isolated from the calling script as when running it in a separate runspace.

But what I wrote was "invoke-command -scriptblock $scriptblock". It would have been less susceptible to confusion if I had instead used "$scriptblock.invoke()", which is basically the same thing. As you know, invoking a scriptblock in this way is entirely different. It runs just like a called function in PowerShell, with its own scope but as a child scope of the caller. Everything in the calling script is available in the invoked scriptblock, and the scriptblock can easily expose any variable it wants to the calling script by simply making it a script level variable (i.e. $script:exposedvar". And note that I even mentioned making a minor change inside the scriptblock so that it would return the dialogresult value, making it act even more like a standard win form. So it could be called as "$dialogresult = $scriptblock.invoke()".

Now I don't see anything wrong with the method you suggested, but I do see the following advantages to how I was thinking of doing it.

You did not consider getting data into the form. The defaults will not always be static but will depend on the conditions when the form is called. In my way I don't have to do anything at all to pass in parameters since all variables in the calling script are already available inside the form.

Passing back the entire form is certainly a valid approach, but why pass back everything when all that's needed are a few variables. I know it's not really THAT big a deal resource-wise, but my assembler training just ingrained it too deep into me to watch the resources. And instead of having to dispose of that form variable, with the scriptblock invoke as I described the entire scriptblock will simply go out of scope automatically after the invocation without me having to do anything (everything except those few variables that I choose to define as script scope).

And this is just a personal preference, but simply setting script scope in the form on any vars I need back in the calling script just seems easier and more efficient then adding write-outputs or gathering them up for the return (assuming you don't just pass back the whole form). Plus it leaves the scriptblock's return value free for the dialogresult.

As to the question you asked: Yes, I understand your MFC snippet. And the answer to "how do you get your data back" is via the return value from a call to the form object's dialogmodal method. And since you were assuming I was running the scriptblock as a job then the only way my calling script could have access to any form data was via the scriptblock's return value. But as you can see, it's an entirely different situation when invoking the scriptblock as I actually suggested. And while you may only need to make 2 minor edits for what you suggested, using my idea I can lift the form's scriptblock directly from the generated script with no edits at all (unless I want to make the minor changes to 2 lines so that it returns the dialogresult).

Considering those clarifications, is your opinion any different or do you still see that I'm missing some significant concept?

Thanks
This topic is 10 years and 8 months old and has exceeded the time allowed for comments. Please begin a new topic or use the search feature to find a similar but newer topic.
Locked