Connect to Microsoft Exchange PowerShell within C#

Viewed 10301

I'm trying to connect to remote powershell from C# .NET WinForms app. My goal is to create my own version of Microsoft PowerShell ISE. So i need a way to execute PowerShell Scripts from my app on Remote Machines. I've created couple of methods and tested it on local machine from my app. If I don't use WSManConnectionInfo and use using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace()) i can execute scripts locally as if it was true powershell (little scripts, usage of variables, output data using ft, fl, do a lot of other things I usually do with powershell. Problem starts when I add WSManConnectionInfo and point it to my Exchange Server instead of using local connection. It seems it's able to execute basic stuff like "get-mailbox" but as soon as i try to pipe things, use some scripting capabilities like $variables it breaks saying it's unsupported.

Similarly I have to disable powershell.AddCommand("out-string"); when not using it locally.

An unhandled exception of type 'System.Management.Automation.RemoteException' occurred in System.Management.Automation.dll.

Additional information: The term 'Out-String' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

The very same error doesn't appear if I don't force remote connection but simply do it locally. It seems like the SchemaUri is making it very strict to only execute basic commands. I saw other examples where people where using very direct information such us:

powershell.AddCommand("Get-Users");
powershell.AddParameter("ResultSize", count);

But with that approach I would have to define a lot of possible options and I don't want to go thru defining parameters and other stuff. I simply would like to load "script" and execute it just like in PowerShell window.. Here's an example of what I use now.

    public static WSManConnectionInfo PowerShellConnectionInformation(string serverUrl, PSCredential psCredentials)
    {
        var connectionInfo = new WSManConnectionInfo(new Uri(serverUrl), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", psCredentials);
        //var connectionInfo = new WSManConnectionInfo(new Uri(serverUrl), "http://schemas.microsoft.com/powershell", psCredentials);
        connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
        connectionInfo.SkipCACheck = true;
        connectionInfo.SkipCNCheck = true;
        connectionInfo.SkipRevocationCheck = true;
        connectionInfo.MaximumConnectionRedirectionCount = 5;
        connectionInfo.OperationTimeout = 150000;
        return connectionInfo;
    }
    public static PSCredential SecurePassword(string login, string password)
    {
        SecureString ssLoginPassword = new SecureString();
        foreach (char x in password) { ssLoginPassword.AppendChar(x); }
        return new PSCredential(login, ssLoginPassword);
    }
    public static string RunScriptPs(WSManConnectionInfo connectionInfo, string scriptText)
    {
        StringBuilder stringBuilder = new StringBuilder();
        // Create a remote runspace using the connection information.
        //using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace())
        using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo))
        {
            // Establish the connection by calling the Open() method to open the runspace. 
            // The OpenTimeout value set previously will be applied while establishing 
            // the connection. Establishing a remote connection involves sending and 
            // receiving some data, so the OperationTimeout will also play a role in this process.
            remoteRunspace.Open();
            // Create a PowerShell object to run commands in the remote runspace.
            using (PowerShell powershell = PowerShell.Create())
            {
                powershell.Runspace = remoteRunspace;
                powershell.AddScript(scriptText);
                //powershell.AddCommand("out-string");
                powershell.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
                Collection<PSObject> results = powershell.Invoke();
            
                foreach (PSObject result in results) {
                        stringBuilder.AppendLine(result.ToString());
                }

            }
            // Close the connection. Call the Close() method to close the remote 
            // runspace. The Dispose() method (called by using primitive) will call 
            // the Close() method if it is not already called.
            remoteRunspace.Close();
        }

        // convert the script result into a single string
        return stringBuilder.ToString();
    }

Any advice on why this is happening and workaround how to get it to behave the same way? I've seen a lot of blogs like this but defining every simple command doesn't make sense to me. I also saw an option to create local connection and then execute remote connection within that but that's gotta be last resort since it relies on multiple other factors.

3 Answers
Related