Start an Async PowerShell Host to Prevent UI Freeze

  • (2 Pages)
  • +
  • 1
  • 2

18 Replies - 804 Views - Last Post: Yesterday, 02:27 PM Rate Topic: -----

#1 hexagod   User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 400
  • Joined: 29-October 16

Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 09:19 AM

I have an application which hosts the powershell engine and when I run the following code:

        private void GetPrinterButton_Click(object sender, RoutedEventArgs e)
        {
            using (PowerShell PowerShellInstance = PowerShell.Create())
            {
                PowerShellInstance.AddScript(dollarSignSource + @"get-printer -computername $ource |
                       select -property Name, Drivername, Portname, Shared |
                        export-csv printer.csv -delimiter " + "\"" + "^" + "\"" + @" -notypeinformation -Encoding UTF8");

                PSDataCollection<PSObject> outputCollection = new PSDataCollection<PSObject>();
                //outputCollection.DataAdded += OutputCollection_DataAdded;

                //ICollection<T> yo = outputCollection;

                IAsyncResult result = PowerShellInstance.BeginInvoke<PSObject, PSObject>(null, outputCollection);

                statusTextBox.Text = "Execution has stopped. The pipeline state: " + PowerShellInstance.InvocationStateInfo.State;

                foreach (PSObject outputItem in outputCollection)
                {

                    printerObjListBox.Items.Add(outputItem.Properties["Name"].Value.ToString());
                    //TODO: handle/process the output items if required
                    //  textBox3.Text += (outputItem.BaseObject.ToString());

                }


            }


        }



the problem is that when I click my button the program completely stops until it gets a return from the PowerShell pipeline. Does anyone know how I could run that code and have it not freeze the UI?

It's not that big a deal for this but I'm creating another feature that will allow user to add IP address and my program will change each IP into the NETBIOS hostname as the user clicks a button. Basically it will create a list of computers and I want the user to be able to type without stopping as the HOSTNAMES populate.

This post has been edited by hexagod: 02 January 2019 - 02:32 PM


Is This A Good Question/Topic? 0
  • +

Replies To: Start an Async PowerShell Host to Prevent UI Freeze

#2 Skydiver   User is offline

  • Code herder
  • member icon

Reputation: 6664
  • View blog
  • Posts: 22,744
  • Joined: 05-May 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 09:27 AM

In the current version of C#, you should be using async/await.

Stephen Cleary has advise on how to wrap the old style IAsyncResult to something compatible with async[il]/[il]await:
Async Interop with IAsyncResult
Was This Post Helpful? 1
  • +
  • -

#3 Skydiver   User is offline

  • Code herder
  • member icon

Reputation: 6664
  • View blog
  • Posts: 22,744
  • Joined: 05-May 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 09:38 AM

Or alternatively, you can just do a Task.Run() to run everything on another thread, and simply await the results from the output collection. It'll probably be simpler since I don't there is a requirement that the PowerShell hosting thread has to be the primary thread.

(There maybe requirements that the thread has to be STA initialized -- in which case, you'll need to start your own thread, instead of using Task.Run(), and have to wait on a thread handle.)

Update: I'm not seeing any requirement that the PowerShell host thread needs to be an STA thread. So Task.Run() maybe your most direct way of doing things.
Was This Post Helpful? 1
  • +
  • -

#4 hexagod   User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 400
  • Joined: 29-October 16

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 01:23 PM

This doesn't throw any exceptions, doesn't lock the UI but also nothing populates in textBox

       private async void AddComputerButton_Click(object sender, RoutedEventArgs e)
        {
                string strOutput = "";
                 var _t = Task.Run(() =>
                 {
                    using (PowerShell PowerShellInstance = PowerShell.Create())
                    {
                        string getHostByAddress = @"[System.Net.dns]::GetHostbyAddress(" + "\"" + @"66.66.66.66" + "\"" + ").Hostname";

                        PowerShellInstance.AddScript(getHostByAddress);

                        Collection<PSObject> results = PowerShellInstance.Invoke();

                        foreach (PSObject obj in results)
                        {
                            strOutput = obj.ToString();
                             //retStr.Append(obj.ToString());
                             statusTextBox.Text = strOutput;
                         }

                     }
                 });
                 //Task.WaitAll(_t);
                 await Task.Yield();
                // await _t;
                Dispatcher.Invoke(() =>
                {
                    statusTextBox.Text = strOutput;

                });

                //statusTextBox.Text = strOutput;

            
        }



If I get rid of the
                Dispatcher.Invoke(() =>
                {
                    statusTextBox.Text = strOutput;

                });


I get an exception "The calling thread cannot access this object because a different thread owns it"

async is new to me, what I do wrong?
Was This Post Helpful? 0
  • +
  • -

#5 Skydiver   User is offline

  • Code herder
  • member icon

Reputation: 6664
  • View blog
  • Posts: 22,744
  • Joined: 05-May 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 01:32 PM

Task.Run() starts a new thread. WPF (and WinForms) don't let you update the UI from another thread. That is why I was saying that you need to await the results on the UI thread. When you have the results, update the UI with the results.
Was This Post Helpful? 2
  • +
  • -

#6 hexagod   User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 400
  • Joined: 29-October 16

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 02:29 PM

Skydiver

yooo thank you so much fam. :yes: Appreciate you holding my hand there... my first async process... this is pretty jazzy with the powershell engine

you will remember that I used to WriteAllText to a .ps1 file and then ShellExecute or Process.Start... I actually still like doing it that way for certain operations because I can run a ton of scripts at once but for stuff like this where I'm just grabbing a string from a PSObject this works gr8!

here's how I got it working:

private async void AddComputerButton_Click(object sender, RoutedEventArgs e)
 {
         string strOutput = "";
          var _t = Task.Run(() =>
          {
             using (PowerShell PowerShellInstance = PowerShell.Create())
             {
                 string getHostByAddress = @"[System.Net.dns]::GetHostbyAddress(" + "\"" + @"66.66.66.66" + "\"" + ").Hostname";

                 PowerShellInstance.AddScript(getHostByAddress);

                 Collection<PSObject> results = PowerShellInstance.Invoke();

                 foreach (PSObject obj in results)
                 {
                     strOutput = obj.ToString();
                      
                      statusTextBox.Text = strOutput;
                  }

              }
          });
                 await _t;
                 statusTextBox.Text = strOutput;   
 }


Was This Post Helpful? 0
  • +
  • -

#7 Sheepings   User is offline

  • Senior Programmer
  • member icon

Reputation: 171
  • View blog
  • Posts: 1,019
  • Joined: 05-December 13

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 02:47 PM

Please do not edit your original post and remove source. From what i read; Its a sure way to lose your editing privileges, and I'm sure this isn't the first time this was said to you.

Also, if you had used a delegate when invoking and passed that method on to the dispatcher, you'd find your textbox would have worked and updated your UI. See an example here. Always delegate when you want to talk to your UI from other threads.
Was This Post Helpful? 1
  • +
  • -

#8 Skydiver   User is offline

  • Code herder
  • member icon

Reputation: 6664
  • View blog
  • Posts: 22,744
  • Joined: 05-May 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 02:56 PM

I would have changed things to something more like this:
var powerShellTask = Task.Run(() => {
    using (var shell = new PowerShell.Create())
    {
        // :
        // : initialize the shell here
        // :

        // Invoke the cmdlet/script
        var results = shell.Invoke():

        // Collect output into a string builder
        var sb = new StringBuilder();
        foreach(var resultObj in results)
            sb.Append(resultObj.ToString());

        // Return all big string
        return sb.ToString();
    }
});

statusTextBox.Text = await powerShellTask;


Was This Post Helpful? 1
  • +
  • -

#9 hexagod   User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 400
  • Joined: 29-October 16

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 02 January 2019 - 03:25 PM

Sheepings I didn't remove source from my original post I edited it to fix the Asyc versus Async so that it will be properly indexed for future devs
Was This Post Helpful? 0
  • +
  • -

#10 andrewsw   User is online

  • head thrashing
  • member icon

Reputation: 6667
  • View blog
  • Posts: 27,302
  • Joined: 12-December 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 03 January 2019 - 02:17 AM

Presumably you have made the decision to go via PowerShell, but it is worth mentioning that a lot of what you are scripting could be more directly achieved via WMI.
Was This Post Helpful? 1
  • +
  • -

#11 baavgai   User is offline

  • Dreaming Coder
  • member icon


Reputation: 7361
  • View blog
  • Posts: 15,284
  • Joined: 16-October 07

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 03 January 2019 - 08:50 AM

So, full stop. Before I went any farther, I'd make a wrapper for those shell calls:
private IEnumerable<string> ShellCall(string script) {
    using (var shell = PowerShell.Create()) {
        shell.AddScript(script);
        return shell.Invoke().Cast<PSObject>().Select(x => x.ToString());
    }
}



Then, before getting into tasks, define the job you want to do:
private string Host(string addr) =>
    string.Join("", ShellCall($"[System.Net.Dns]::GetHostByAddress('{addr}').HostName"));



Now, your GUI becomes much easier:
private async void AddComputerButton_Click(object sender, RoutedEventArgs e) {
    statusTextBox.Text = await Task.Run(() => Host("66.66.66.66"));
}



At this point, it would be extremely remiss not to point out that anything you can do in PowerShell you can do in C#. Indeed, your last example is trivial and calling PowerShell for it seems kind of silly. e.g.
private string Host(string addr) =>
    System.Net.Dns.GetHostByAddress(addr).HostName;


Was This Post Helpful? 2
  • +
  • -

#12 Skydiver   User is offline

  • Code herder
  • member icon

Reputation: 6664
  • View blog
  • Posts: 22,744
  • Joined: 05-May 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 03 January 2019 - 08:58 AM

For post #4 and #6, why not just call System.Dns.GetHostByAddress() directly instead of even playing with WMI or PowerShell?

For retrieving the installed printers and its details, WMI is the easiest way to go in C#. With C++, using the Win32 API EnumPrinters() is going to be less painful that dealing with WMI.
Was This Post Helpful? 0
  • +
  • -

#13 Skydiver   User is offline

  • Code herder
  • member icon

Reputation: 6664
  • View blog
  • Posts: 22,744
  • Joined: 05-May 12

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted 03 January 2019 - 09:06 AM

And I just remembered: Since this code lives within WPF, we shouldn't really setting controls' Text property directly. Instead the found values should be exposed in a view model. The View should be bound to the exposed property and will be updated by the UI once the property in the view model is updated. There's not going to be any freezing as long as the actual work to get the values is done asynchronously (either by as an asynchronous thread, or by using async/await within the same thread.
Was This Post Helpful? 1
  • +
  • -

#14 hexagod   User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 400
  • Joined: 29-October 16

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted Yesterday, 11:42 AM

^^^ yes that's good advice skydiver

..and
I think you guys are getting hung up on that gethostbyaddress ... I just used that as an example of what I'm trying to do. That's not what I'm truly using the PowerShell engine for in practical application.

I am always adding new features to my administration software. I assume down the line there are things that can be done through PS that can't be done through WMI but I could be wrong about that. Again, I don't have a lot of time to research this stuff and I'm not one to post GIVE ME TEH CODEZ. I usually just throw something together that works with excellent performance because I don't have the luxury of time.

When I get home I work on other programming projects so it's not like I sit trying to figure this stuff out.

thank you for all the advice! I will be combing through this information and absorbing it for future reference.
Was This Post Helpful? 0
  • +
  • -

#15 hexagod   User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 400
  • Joined: 29-October 16

Re: Start an Async PowerShell Host to Prevent UI Freeze

Posted Yesterday, 11:48 AM

View Postbaavgai, on 03 January 2019 - 08:50 AM, said:

So, full stop. Before I went any farther, I'd make a wrapper for those shell calls:
private IEnumerable<string> ShellCall(string script) {
    using (var shell = PowerShell.Create()) {
        shell.AddScript(script);
        return shell.Invoke().Cast<PSObject>().Select(x => x.ToString());
    }
}



Then, before getting into tasks, define the job you want to do:
private string Host(string addr) =>
    string.Join("", ShellCall($"[System.Net.Dns]::GetHostByAddress('{addr}').HostName"));



Now, your GUI becomes much easier:
private async void AddComputerButton_Click(object sender, RoutedEventArgs e) {
    statusTextBox.Text = await Task.Run(() => Host("66.66.66.66"));
}



At this point, it would be extremely remiss not to point out that anything you can do in PowerShell you can do in C#. Indeed, your last example is trivial and calling PowerShell for it seems kind of silly. e.g.
private string Host(string addr) =>
    System.Net.Dns.GetHostByAddress(addr).HostName;



Nice!!! thank you sir!!
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2