Execute Powershell cmdlet that have user prompts within C# WPF application

Viewed 36

I am attempting to create a WPF application that will execute some powershell commands using a 3rd party module (ShareGate). After extensive research and banging my head on the keyboard, I have gotten the application to at least execute the cmdlets I have asked for. The cmdlet in question, if run in powershell, will prompt the user to log into a web service using edge I believe. When running the cmdlet from the application, it throws an error which is misleading "during the last update edge was not able to be installed...."

I think that this error is coming up because this implementation isn't allowing powershell to pop open the browser like it does within a powershell window.

My question is this: "How can I redirect the user prompt to come up within the wpf application? or can I?"

here is my method:

        public Task StartSGMigrations(IProgress<string> progress)
        {
            var sharegatePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Apps\\ShareGate\\Sharegate.Automation.dll";
            if (client != null)
            {
                try
                {
                    InitialSessionState iss = InitialSessionState.CreateDefault();
                    iss.ImportPSModule(new string[] { sharegatePath });
                    using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(iss))
                    {
                        myRunSpace.Open();
                        using (PowerShell powershell = PowerShell.Create())
                        {
                            Dictionary<string, string> parameters = new Dictionary<string, string>();
                            powershell.AddScript("New-CopySettings -OnContentItemExists IncrementalUpdate");
                            powershell.AddScript("Connect-box -email " + _admin + " -admin");
                            powershell.AddScript("Connect-Site -Url \"https://xxxx-admin.sharepoint.com\" -Browser");

                            powershell.Runspace = myRunSpace;
                            var results = powershell.Invoke();
                            var errors = myRunSpace.SessionStateProxy.PSVariable.GetValue("Error");

                            foreach (var result in results)
                            {
                                progress.Report(result.ToString() + Environment.NewLine);
                            }
                        }
                        myRunSpace.Close();
                    }
                }
                catch (Exception ex)
                {
                    progress.Report(ex.Message);
                }
            }
            else
            {
                progress.Report("not connected to Box");
            }

            return Task.CompletedTask;
        }
    }
1 Answers

This will be fairly tricky to do if the commands you are calling do not support noninteractive execution.

What's going on:

Your application using the PowerShell api to call your scripts. This part's good. The problem is your scripts are using functionality of the PowerShell host (possibly prompting for credentials). Because the runspace is not associated with a host, any interactive capabilities will simply fail.

So you'd need to attach a host in order for it to work as expected (and that host would need to work the same as PowerShell.exe/pwsh.exe for whatever purposes your underlying cmdlets need).

If there were lots of implementations of a PowerShell host in the wild, I'd link you to them. Unfortunately, there are not. So unless you want to go down a deep rabbit hole, I'd suggest these alternatives:

  1. If the cmdlet supports providing credentials directly, try this
  2. If it does not, see if it "persists" credentials for a given user. (That is, open up a shell, login, close the shell, open another shell, and see if you can use the module without providing credentials).

If credentials do persist (and you can't do option 1), you should be able to call PowerShell.exe/pwsh.exe once to log in, and then load code normally.

If the credentials do not persist, you're stuck in a much more unfortunate situation, leaving you with paths 3,4, or 5:

  1. Call powershell.exe/pwsh.exe (hopefully in in a minimized window) and send the output back via JSON or CLIXML.

  2. Go down the rabbit hole and build yourself a host.

  3. Beg the cmdlet authors to better support noninteractive scenarios.

Between those options, I'd start with the last one.

Best of luck.

Related