Search code examples
c#.netpowershellexchange-server-2010

Refining a function: How to run command on remote exchange server and receive results?


I have a .NET function which:

  1. Connects to Exchange
  2. Imports the session to Localhost
  3. Then runs the command

I have not been able to figure out how to just directly run a command on the Exchange server (via powershell) and then receive the response back - I have only been able to do it with this rather roundabout way.

How can I remotely run a Powershell command on Exchange and receive the response back in .NET?

This is my function:

protected string Receive_Exchange(string lscript)
{
//create credentials + encrypt them 
    string runasUsername = loginDetails.Username;
    string runasPassword = loginDetails.Password;
    SecureString ssRunasPassword = new SecureString();
    foreach (char x in runasPassword)
    {
        ssRunasPassword.AppendChar(x);
    }
// bind to PS credentials Object
    PSCredential credentials =
        new PSCredential(runasUsername, ssRunasPassword);

// prepare the connection to Remote server (Exchange 2010)
    var connInfo = new WSManConnectionInfo(
    new Uri("http://exchangeServer.domain.local/PowerShell"),
    "http://schemas.microsoft.com/powershell/Microsoft.Exchange",
    credentials);
    object psSessionConnection;
//need .default in order to send it (not .basic)
    connInfo.AuthenticationMechanism = AuthenticationMechanism.Default;
//Define/set executionPolicy
    InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
    initialSessionState.ExecutionPolicy = ExecutionPolicy.RemoteSigned;
//open runspace
    Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState);
    runspace.Open();
    PowerShell powershell = PowerShell.Create();
//send the remote connection request to exchange
    var command = new PSCommand();
    command.AddCommand("New-PSSession");
    command.AddParameter("ConfigurationName", "Microsoft.Exchange");
    command.AddParameter("ConnectionUri", new Uri("http://exchangeServer.domain.local/PowerShell"));
    command.AddParameter("Authentication", "Kerberos");
    powershell.Commands = command;
    powershell.Runspace = runspace;
    var result = powershell.Invoke();
    psSessionConnection = result[0];
//start to build command to import exchange connection to localhost
    var command1 = new PSCommand();
    command1.AddCommand("Import-PSSession");
    command1.AddParameter("Session", psSessionConnection);
    powershell.Commands = command1;
    powershell.Runspace = runspace;
//after importing connection, actually run the script
    powershell.AddScript(lscript);
    var result22 = powershell.Invoke();
    runspace.Close();
    string json = JsonConvert.SerializeObject(result22, Formatting.Indented,
                new JsonSerializerSettings()
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                });
    return json;
} 

It is a behemoth and it is inefficient causing my entire app to slow down.

I love that it works but I hate how it works - please help me in making it run in a more straightforward way.


Solution

  • So thanks to the comments, they were very helpful.

    I have bad news, since April 2021 Microsoft introduced an update that makes all Runspaces' run in NoLanguageMode please see the link for more detail.

    https://support.microsoft.com/en-us/topic/-the-syntax-is-not-supported-by-this-runspace-error-after-installing-april-2021-exchange-security-update-or-later-updates-ac2d4e97-62f6-4ad4-9dbb-0ade9b79f599

    Another project with lots of details/different methods to connect to Exchange in powershell:

    https://github.com/David-Barrett-MS/ExchangePowerShellAutomationSample/blob/master/ExchangePSAutomationTest/FormMain.cs

    So the correct way is to indeed:

    1. create a local instance of powershell
    2. Add a new PS-Session
    3. Import this PSSession to your local session
    4. Run powershell scripts at your leisure

    I therefore was not able to really change the functionality of my function, but to make it neater and more readable I broke it into three parts:

    To prepare the powershell session, defining the Method (microsoft.exchange) the Uri (exchange.domain.local/powershell) and the credential (credentials)

      private PSCommand NewPSSession()
      {
          //grab credentials from Model + encrypt them 
          string runasUsername = loginDetails.Username;
          string runasPassword = loginDetails.Password;
          SecureString ssRunasPassword = new SecureString();
          foreach (char x in runasPassword)
          {
              ssRunasPassword.AppendChar(x);
          }
          // bind to PS cred Obj
          PSCredential credentials =
              new PSCredential(runasUsername, ssRunasPassword);
          //create the PSCommand to connect to exchangeserver
          PSCommand command = new PSCommand();
          command.AddCommand("New-PSSession");
          command.AddParameter("ConfigurationName", "Microsoft.Exchange");
          command.AddParameter("ConnectionUri", new Uri("http://exchange.domain.local/PowerShell"));
          command.AddParameter("Credential", credentials);
    
          return command;
      }
    

    Prepare the import command, using the result returned from NewPSSession()

     private PSCommand ImportSession(Collection<PSObject> result)
     {
         PSCommand command = new PSCommand();
         command.AddCommand("Import-PSSession");
         command.AddParameter("Session", result[0]);
    
         return command;
     }
    

    Finally the function to combine it all together and call the script

      protected string Receive_Exchange(string script)
      {
          //Define sessionState for local Powershell + set executionPolicy
          InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
          initialSessionState.ExecutionPolicy = ExecutionPolicy.RemoteSigned;
          //Define runspace passing sessionState
          Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState);
          //create powershell, binding runspace to it
          PowerShell powershell = PowerShell.Create(runspace);
          runspace.Open();
          // first function: defining the exchange PSSession
          powershell.Commands = NewPSSession();
          var result = powershell.Invoke();
          //Import the session using 2nd function
          powershell.Commands = ImportSession(result);
          //after importing connection, actually run the script
          powershell.AddScript(script); 
          try
          {
              var result2 = powershell.Invoke();
              runspace.Close();
              string json = JsonConvert.SerializeObject(result2, Formatting.Indented,
                          new JsonSerializerSettings()
                          {
                              ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                          });
              return json;
          } catch (Exception ex)
          {
              Console.WriteLine(ex.ToString());
              return ex.ToString(); 
          }
      }