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 : True

DatabaseName : 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(“http://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!

Comments (2)

  1. Vishal says:

    Perfect!!!

    In addition to this:

    IS THE OUTPUT OF REMOTE COMMANDS DIFFERENT FROM LOCAL OUTPUT?

      When you use Windows PowerShell locally, you send and receive "live" .NET

      Framework objects; "live" objects are objects that are associated with

      actual programs or system components. When you invoke the methods or change

      the properties of live objects, the changes affect the actual program or

      component. And, when the properties of a program or component change,

      the properties of the object that represent them also change.

      However, because most live objects cannot be transmitted over the network,

      Windows PowerShell "serializes" most of the objects sent in remote commands,

      that is, it converts each object into a series of XML (Constraint Language

      in XML [CLiXML]) data elements for transmission.

      When Windows PowerShell receives a serialized object, it converts

      the XML into a deserialized object type. The deserialized object

      is an accurate record of the properties of the program or component at

      a previous time, but it is no longer "live", that is, it

      is no longer directly associated with the component. And, the methods are

      removed because they are no longer effective.

      Typically, you can use deserialized objects just as you would use live

      objects, but you must be aware of their limitations. Also, the objects

      that are returned by the Invoke-Command cmdlet have additional properties

      that help you to determine the origin of the command.

      Some object types, such as DirectoryInfo objects and GUIDs, are converted

      back into live objects when they are received. These objects do not need

      any special handling or formatting.  

      For information about interpreting and formatting remote output, see

      about_Remote_Output.

  2. Akashb says:

    Thank you Vishal for the additonal details!

Skip to main content