HOW TO: Migrating Exchange 2007 PowerShell Managed code to work with Exchange 2010

Will my Exchange 2007 PowerShell Managed code work with Exchange 2010 as is?

Unfortunately the answer is NO, fortunately there are not many changes that you will have to make. The management experience given by Exchange 2010 through PowerShell has been moved all the way from Local to Remote. Dave Vespa has a detailed post that explains the difference between the Runspace’s.

Only exchange cmdlets will work in this remoting scenario, you will not be able to run most of the powershell cmdlets. The powershell cmdlets that work in the remoting scenario are Out-Default, Get-FormatData, Select-Object, Measure-Object, Exit-PSSession. If you find more please do let me know...

Yes, this does mean that you will not be able to run cmdlets like Where-Object and .PS1 scripts in the Remote Runspace. Is that a limitation? I don’t think so. We can very easily get around it by create a new Session and Importing it.

For Exchange 2007 we had to add the Microsoft.Exchange.Management.PowerShell.Admin, now we will have to use New-PSSession and Import-PSSession and then we can do what ever we want to. Below is a code sample that shows how to use New-PSSession, Import-PSSession and using Where-Object in the now Local Runspace :

 using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Remoting;
using System.Collections.ObjectModel;
using System.Security;

namespace CallingPSCmdlet
{
    class Program
    {
        static void Main(string[] args)
        {
            string password = "Password!";
            string userName = "Domain\\Administrator";
            System.Uri uri = new Uri("https://Exchange-Server/powershell?serializationLevel=Full");
            System.Security.SecureString securePassword = String2SecureString(password);

            PSCredential creds = new PSCredential(userName, securePassword);

            Runspace runspace = RunspaceFactory.CreateRunspace();

            PowerShell powershell = PowerShell.Create();
            PSCommand command = new PSCommand();
            command.AddCommand("New-PSSession");
            command.AddParameter("ConfigurationName", "Microsoft.Exchange");
            command.AddParameter("ConnectionUri", uri);
            command.AddParameter("Credential", creds);
            command.AddParameter("Authentication", "Default");
            PSSessionOption sessionOption = new PSSessionOption();
            sessionOption.SkipCACheck = true;
            sessionOption.SkipCNCheck = true;
            sessionOption.SkipRevocationCheck = true;
            command.AddParameter("SessionOption", sessionOption);

            powershell.Commands = command;

            try
            {
                // open the remote runspace
                runspace.Open();

                // associate the runspace with powershell
                powershell.Runspace = runspace;

                // invoke the powershell to obtain the results
                Collection<PSSession> result = powershell.Invoke<PSSession>();

                foreach (ErrorRecord current in powershell.Streams.Error)
                {
                    Console.WriteLine("Exception: " + current.Exception.ToString());
                    Console.WriteLine("Inner Exception: " + current.Exception.InnerException);
                }

                if (result.Count != 1)
                    throw new Exception("Unexpected number of Remote Runspace connections returned.");

                // Set the runspace as a local variable on the runspace
                powershell = PowerShell.Create();
                command = new PSCommand();
                command.AddCommand("Set-Variable");
                command.AddParameter("Name", "ra");
                command.AddParameter("Value", result[0]);
                powershell.Commands = command;
                powershell.Runspace = runspace;

                powershell.Invoke();

                
                // First import the cmdlets in the current runspace (using Import-PSSession)
                powershell = PowerShell.Create();
                command = new PSCommand();
                command.AddScript("Import-PSSession -Session $ra");
                powershell.Commands = command;
                powershell.Runspace = runspace;
                powershell.Invoke();

                
                // Now run get-ExchangeServer
                powershell = PowerShell.Create();
                command = new PSCommand();
                command.AddScript("Get-ExchangeServer | where-object{$_.Name -like \"*MBX\"}");
                powershell.Commands = command;
                powershell.Runspace = runspace;

                Collection<PSObject> results = new Collection<PSObject>();
                results = powershell.Invoke();

                foreach (PSObject PSresult in results)
                {
                    Console.WriteLine(PSresult.Properties["Name"].Value.ToString());
                }

            }
            finally
            {
                // dispose the runspace and enable garbage collection
                runspace.Dispose();
                runspace = null;

                // Finally dispose the powershell and set all variables to null to free
                // up any resources.
                powershell.Dispose();
                powershell = null;
            }
        }
        
        private static SecureString String2SecureString(string password)
        {
            SecureString remotePassword = new SecureString();
            for (int i = 0; i < password.Length; i++)
                remotePassword.AppendChar(password[i]);

            return remotePassword;
        }

    }
}

In my next post I will show how to call and pass parameter to a .PS1 script. Enjoy!