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!
Comments
- Anonymous
July 31, 2011
The comment has been removed - Anonymous
July 31, 2011
Thank you Vishal for the additonal details!