How to get information on Database Copies using Managed code and Remote Powershell(Exchange 2010)

It all started with a customer who wanted to know which servers in the DAG setup have the copies for a specific Exchange database. There were other properties like Activation Preferences that he was interested in too.

The easiest way to get the details about the copies for a database is by using the command (Get-MailboxDatabase "Database Name").DatabaseCopies. When I did this on the CAS Server using Exchange Management Shell, I got the following output:

 
[PS] C:\Windows\system32>$copies = (Get-MailboxDatabase "Mailbox Database 1056078029").DatabaseCopies
[PS] C:\Windows\system32>$copies

DatabaseName         : Mailbox Database 1056078029
HostServerName       : AKAS23474121
ActivationPreference : 1
ParentObjectClass    : msExchPrivateMDB
ReplayLagTime        : 00:00:00
TruncationLagTime    : 00:00:00
AdminDisplayName     :
ExchangeVersion      : 0.10 (14.0.100.0)
DistinguishedName    : CN=AKAS23474121,CN=Mailbox Database 1056078029,CN=Databases,CN=Exchange Administrative Group (FY
                       DIBOHF23SPDLT),CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services,
                       CN=Configuration,DC=DOM234741,DC=LOCAL
Identity             : Mailbox Database 1056078029\AKAS23474121
Guid                 : 562cb271-7cee-4130-9582-e3ce0f08cb3d
ObjectCategory       : DOM234741.LOCAL/Configuration/Schema/ms-Exch-MDB-Copy
ObjectClass          : {top, msExchMDBCopy}
WhenChanged          : 7/21/2011 4:29:06 PM
WhenCreated          : 7/21/2011 4:29:06 PM
WhenChangedUTC       : 7/21/2011 10:59:06 AM
WhenCreatedUTC       : 7/21/2011 10:59:06 AM
OrganizationId       :
OriginatingServer    : AKAS23474118.DOM234741.LOCAL
IsValid              : TrueDatabaseName         : Mailbox Database 1056078029
HostServerName       : AKAS23474120
ActivationPreference : 2
ParentObjectClass    : msExchPrivateMDB
ReplayLagTime        : 00:00:00
TruncationLagTime    : 00:00:00
AdminDisplayName     :
ExchangeVersion      : 0.10 (14.0.100.0)
...

The $copies variable contains live objects, we can then pipe it into a foreach-object and print out the selected properties we want to. The reason I am saying this is because there is a difference which I will talk about later. For now, I connect to the same CAS Server from a remote machine and run the exact same commands and following is the output:

 PS C:\Users\Superman> $copies=(Get-MailboxDatabase "Mailbox Database 1056078029").DatabaseCopies
PS C:\Users\Superman> $copies
Mailbox Database 1056078029\AKAS23474121
Mailbox Database 1056078029\AKAS23474120

Why is this difference? Why do I see just the Database name? What happened to all the other properties?

I then did a Get-Member on the $copies variable first on the local CAS box and below is what I see

 
[PS] C:\Windows\system32>$copies | Get-Member -MemberType Property


   TypeName: Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopy

Name                 MemberType Definition
----                 ---------- ----------
ActivationPreference Property   System.Int32 ActivationPreference {get;}
AdminDisplayName     Property   System.String AdminDisplayName {get;}
DatabaseName         Property   System.String DatabaseName {get;}
DistinguishedName    Property   System.String DistinguishedName {get;}
ExchangeVersion      Property   Microsoft.Exchange.Data.ExchangeObjectVersion ExchangeVersion {get;}
Guid                 Property   System.Guid Guid {get;}
HostServerName       Property   System.String HostServerName {get;}
Identity             Property   Microsoft.Exchange.Data.ObjectId Identity {get;}
IsValid              Property   System.Boolean IsValid {get;}
ObjectCategory       Property   Microsoft.Exchange.Data.Directory.ADObjectId ObjectCategory {get;}
...

When I do Get-Member on the $copies variable on the Remote machine below is what I see

 PS C:\Users\Superman> $copies | Get-Member -MemberType Properties


   TypeName: System.String

Name   MemberType Definition
----   ---------- ----------
Length Property   System.Int32 Length {get;}

Notice the difference in TypeName. On the Local box it is “Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopy” and on the Remote box it is “System.String”. Why does this happen?

When you run remote commands that generate output, the command output is transmitted across the network back to the local computer. Because most live Microsoft .NET Framework objects (such as the objects that Windows PowerShell cmdlets return) cannot be transmitted over the network, the live objects are "serialized". In other words, the live objects are converted into XML representations of the object and its properties. Then, the XML-based serialized object is transmitted across the network. On the local computer, Windows PowerShell receives the XML-based serialized object and "deserializes" it by converting the XML-based object into a standard .NET Framework object. However, the deserialized object is not a live object. It is a snapshot of the object at the time that it was serialized. This is how an MSDN article defines serialization in .NET framework. More details can be found in the article “How objects are sent to and from remote sessions”. 

Luckily the data that we needed was available buy just using the Get-MailboxDatabase cmd-let by accessing the DatabaseCopies Property. To prove what is said above and show how to do it in C#, below is the code that I wrote:

 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;
using System.Collections;

namespace CallingPSCmdlet
{
    class Program
    {
        static void Main(string[] args)
        {
            string password = "Passowrd";
            string userName = "Domain\\UserName";            System.Uri uri = new Uri(“https://CAS-SERVER/powershell?serializationLevel=Full”);
            System.Security.SecureString securePassword = String2SecureString(password);

            System.Management.Automation.PSCredential creds = new System.Management.Automation.PSCredential(userName, securePassword);            Runspace runspace = System.Management.Automation.Runspaces.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("The following Error happen when opening the remote Runspace: " + current.Exception.ToString() + 
                                                                                " | InnerException: " + 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)
                command = new PSCommand();
                command.AddScript("Import-PSSession -Session $ra");
                powershell.Commands = command;
                powershell.Runspace = runspace;
                powershell.Invoke();          // Now call the Get-MaiboxDatabase

          command = new PSCommand();
                command.AddCommand("Get-MailboxDatabase");
                
                //Change the name of the database
                command.AddParameter("Identity", "Mailbox Database XXXXXXXX");

                powershell.Commands = command;
                powershell.Runspace = runspace;

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

                PSMemberInfo Member = null;                foreach (PSObject oResult in results)
                {
                    foreach (PSMemberInfo psMember in oResult.Members)
                    {
                        Member = psMember;
                        DumpProperties(ref Member);
                    }
                }

                results = null;
                Member = null;
            }
            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;
            }

        }        //Method to Dump out the Properties
        public static void DumpProperties(ref PSMemberInfo psMember)
        {
            // Only look at Properties
            if (psMember.MemberType.ToString() == "Property")   
            {
                switch (psMember.Name)
                {
                    case "ActivationPreference":
                    case "DatabaseCopies":
                        if (psMember.Value != null)
                        {
                            PSObject oPSObject;
                            ArrayList oArrayList;
                            oPSObject = (PSObject)psMember.Value;
                            oArrayList = (ArrayList)oPSObject.BaseObject;
                            Console.WriteLine("Member Name:" + psMember.Name);
                            Console.WriteLine("Member Type:" + psMember.TypeNameOfValue);
                            Console.WriteLine("----------------------");
                            foreach (string item in oArrayList)
                            {
                                Console.WriteLine(item);
                            }
                            Console.WriteLine("----------------------");
                        }
                        break;
                }
            }
        }

      private static SecureString String2SecureString(string password)
        {
            SecureString remotePassword = new SecureString();
            for (int i = 0; i < password.Length; i++)
                remotePassword.AppendChar(password[i]);

            return remotePassword;
        }

    }
}

Following is the output that I get from running the above code:

 Member Name:DatabaseCopies
Member Type:Deserialized.Microsoft.Exchange.Data.Directory.SystemConfiguration.DatabaseCopy[]
----------------------
Mailbox Database 1056078029\AKAS23474121
Mailbox Database 1056078029\AKAS23474120
----------------------

Member Name:ActivationPreference
Member Type:Deserialized.System.Collections.Generic.KeyValuePair`2[[Microsoft.Exchange.Data.Directory.ADObjectId, Microsoft.Exchange.Data.Directory, Version=14.
0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Int32, mscorlib
, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]][]
----------------------
[AKAS23474121, 1]
[AKAS23474120, 2]
----------------------

Notice the word “Deserialized” in the values of the “Member Type” field.

Enjoy!