Search code examples
c#wpfpowershellbusyindicator

How to use BusyIndicator with Powershell script execution in WPF application


I had not originally planned to implement the BusyIndicator, but I realized that my Powershell script takes some time to execute, which may cause confusion with a user. There do not seem to be any tutorials out there showing how to utilize the BusyIndicator with Powershell scripts in C#. This is a great tutorial since it was written by the author of the WPF Extended Toolkit, but it's not for what I need.

Here is what I have. Code is truncated for the sake of brevity:

public partial class MainWindow : Window
{

    public MainWindow()
    {
         InitializeComponent();

    }

    private void imageBtn_Click(object sender, RoutedEventArgs e)
    {
        Button imgBtn = sender as Button;
        string appToCheck = GetSubString(imgBtn.Name);            

        switch(appToCheck)
        {
            case "weather":
               InvokePowerShell(appToCheck);
                break;
            case "news":
                //DO THE SAME FOR ALL CASES
                break;
                //17 more cases follow
        }
    }


  private string GetSubString(string buttonName)
  {
      int index = buttonName.IndexOf('B');
      return buttonName.Substring(0, index);
  }



  private void InvokePowerShell(string str)
    {
        str = char.ToUpper(str[0]) + str.Substring(1);

        PowerShell ps = PowerShell.Create();
        ps.AddScript("Get-AppxPackage | Select Name | Where-Object ($_.Name -like '*" + str + "*'}");

        _busyIndicator.IsBusy = true;
        ps.BeginInvoke<PSObject>(null, new PSInvocationSettings(), ar =>
        {
            try
            {
                var psOutput = ps.EndInvoke(ar);

                this.Dispatcher.Invoke(() => _busyIndicator.IsBusy = false);
                foreach (PSObject item in psOutput)
                {
                    if (item.Equals(String.Empty))
                    {
                        MessageBox.Show(str + " is not installed so cannot be removed.");
                    }
                    else
                    {
                        if (MessageBox.Show("This cannot be undone.\nContinue?", "Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.No)
                        {
                            //DO NOTHING
                        }
                        else
                        {
                            //TODO Remove the app
                            MessageBox.Show(str + " successfully removed.");
                        }
                    }
                }
            }
            finally
            {
                //dispose of it
                ps.Dispose();
            }
        }, null);
      }
   }
}   

I already have the BusyIndicator set up in my XAML:

<toolkit:BusyIndicator x:Name="_busyIndicator" IsBusy="False" BusyContent="One moment....">
<!-- Insert the rest of my markup here -->
</toolkit:BusyIndicator>

I will also have an even longer method to remove everything that's listed in the app, so I will definitely want the indicator. I tried to follow the tutorial given in the link above, but I ran into a problem with my foreach loop going out of scope.

I tried to use the asynchronous BeginInvoke() method to no avail. The busy indicator just kept going, like so:

PSDataCollection<PSObject> outputCollection = new PSDataCollection<PSObject>();
IAsyncResult result = PowerShellInstance.BeginInvoke<PSObject>(outputCollection);
while (result.IsCompleted == false)
        {
            _busyIndicator.IsBusy = true;
        }
//Then my foreach loop shown above with the if statements and MessageBox notifications

I'm very new to this. Any help would be greatly appreciated.


Solution

  • You can either run your code on serapate thread (with Task.Run or similar construct), or use BeginInvoke like this:

    private void InvokePowerShell(string str) {
        str = char.ToUpper(str[0]) + str.Substring(1);
        // remove using, or move to a field
        PowerShell ps = PowerShell.Create();
        ps.AddScript("Get-AppxPackage | Select Name | Where-Object {$_.Name -like '*" + str + "*'}");
        //This is where the app pauses slightly and I need a busyindicator                
        _busyIndicator.IsBusy = true;
        ps.BeginInvoke<PSObject>(null, new PSInvocationSettings(), ar => {
            try {                    
                var psOutput = ps.EndInvoke(ar);
                // note, you are not on UI thread here
                this.Dispatcher.Invoke(() => _busyIndicator.IsBusy = false);
                // the rest of your code here
            }
            finally {
                // if did not move to a field - dispose here
                ps.Dispose();
            }
        }, null);
    }