July, 2011

  • Akash Blogging......

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

    • 2 Comments

    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!

  • Akash Blogging......

    Another example of using EWS Managed API 1.1 from PowerShell (Impersonation, SearchFilter, FindItems, Paging)

    • 0 Comments

    In this case the customer wanted to know the Item count, size of all the item in the Inbox that were older than 5 years. As usual, the requirement was to run the script against multiple mailboxes. I could have easily written this in C# in no time but preferred to do this in PowerShell as:

    • I would not have to compile the code every time I make change
    • It would also be easier for the Administrator who don’t have access to Development Environment
    • Getting the list of mailboxes is easy in Exchange PowerShell

    The downside is that you do not get the IntelliSense that you get with Visual Studio. With all that said lets get down straight to code. This example shows the use of Impersonation, SearchFilter, FindItems, Paging in Exchange Web Services. Create a .PS1 as below:

    # The script requires the EWS managed API, which can be downloaded here:
    # http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1
    # This also requires PowerShell 2.0
    # Make sure the Import-Module command below matches the DLL location of the API.
    # This path must match the install location of the EWS managed API. Change it if needed.

    [string]$info = "White" # Color for informational messages
    [string]$warning = "Yellow" # Color for warning messages
    [string]$errorMessage = "Red" # Color for error messages
    [string]$LogFile = "C:\Temp\Log.txt" # Path of the Log File

    function GetItemCount($MailboxName)
    {
    Add-Content $LogFile ("-------------------------------------------------" )
    Write-host "Searching Inbox for Mailbox Name:" $MailboxName -foregroundcolor $info
    Add-Content $LogFile ("Searching Inbox for Mailbox Name:" + $MailboxName)

    #Change the user to Impersonate
    $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName);

    #Number of Items to Get
    $pageSize =50
    $Offset = 0
    $MoreItems =$true
    $ItemCount=0
    $ItemSize=0

    while ($MoreItems)
    {
    #Setup the View to get a limited number of Items at one time
    $itemView = new-object Microsoft.Exchange.WebServices.Data.ItemView($pageSize,$Offset,[Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning)
    $itemView.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Shallow

    #Get the Size of the Items
    $itemView.PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size);


    #Create the Search Filter - Subtract 5 Years from todays date.
    $oSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived,
    ([System.Datetime]::now).AddMonths(-60))

    $oFindItems = $service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$oSearchFilter,$itemView)


    $ItemCount += $oFindItems.Items.Count

    $i=0

    #Loop through the items returned and add up the size
    foreach ($Item in $oFindItems.Items)
    {
    $i++
    $ItemSize += $Item.Size
    Write-Progress -activity "Adding Item Sizes" -status "Percent added: " -PercentComplete (($i /$oFindItems.Items.Count) * 100)

    }

         if ($oFindItems.MoreAvailable -eq $false)
    {$MoreItems = $false}


    if ($MoreItems)
    {$Offset += $pageSize}

    }

    Write-host "Items Found in Inbox:" $ItemCount -foregroundcolor $info
    Add-Content $LogFile ("Items Found in Inbox:" + $ItemCount )

    Write-host "Size of Items:" ($ItemSize / 1Mb) "MB" -foregroundcolor $info
    Add-Content $LogFile ("Size of Items:" + ($ItemSize / 1Mb) + " MB" )
    Add-Content $LogFile ("-------------------------------------------------" )

    $service.ImpersonatedUserId = $null
    }

    #Change the path if needed
    Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"

    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)

    # Set the Credentials
    $service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("serviceAccount","Password","Domain")

    # Change the URL to point to your cas server
    $service.Url= new-object Uri(“http://CAS-SERVER/EWS/Exchange.asmx”)

    # Set $UseAutoDiscover to $true if you want to use AutoDiscover else it will use the URL set above
    $UseAutoDiscover = $false

    Get-Mailbox | foreach-object {
    $WindowsEmailAddress = $_.WindowsEmailAddress.ToString()

    if ($UseAutoDiscover -eq $true) {
    Write-host "Autodiscovering.." -foregroundcolor $info
    $UseAutoDiscover = $false
    $service.AutodiscoverUrl($WindowsEmailAddress)
    Write-host "Autodiscovering Done!" -foregroundcolor $info
    Write-host "EWS URL set to :" $service.Url -foregroundcolor $info

    }
    #To catch the Exceptions generated
    trap [System.Exception]
    {
    Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $errorMessage;
    Add-Content $LogFile ("Error: " + $_.Exception.Message);
    continue;
    }
    GetItemCount($WindowsEmailAddress)
    }


    To Run the script:
    1)Open the Exchange Management Shell
    2)Navigate to the location where the script file is.
    3)Type in .\ScriptName.PS1 and hit enter to execute the script


    Enjoy!
  • Akash Blogging......

    Exchange 2010 SP1 Rollup 4 re-released

    • 0 Comments

    On Wednesday, 7/27, the Exchange Sustained Engineering team re-released Exchange 2010 SP1 RU4.  This release is being tracked by KB 2579150 and will contain a download file that is unique from the previous release. This updated rollup contains all of the changes that were in the original rollup with the exception of the regression because if which the previous RU4 was pulled back.

    For customers who have the previous version of RU4 and the IU 2581545 installed, there is technically no need to install this updated version of RU4 as the combination of those two releases is functionally equivalent to the updated RU4 dated Wednesday, 7/27.  The only thing a customer would gain by installing the updated rollup 4 would be performing the IU maintenance now that they would need to perform later when installing RU5.

    More details on the Exchange 2010 SP1 RU4 re-release can be found here and here. As for RU5, it is expected to be released by the end of August 2011.

    Enjoy!

  • Akash Blogging......

    Creating folder using EWS Managed API 1.1 from PowerShell

    • 4 Comments

    A customer of mine wanted to create a folder under Inbox for thousands of mailboxes. He also wanted to use PowerShell to do it. What better way that using Exchange Web Service(Managed API)  with Impersonation to do the job.

    The list of the users are available in a text file. The First row denotes the Field Names.The file is named UserAccounts.txt. Format of the text file is as below:

    WindowsEmailAddress
    akasha@contoso.com
    akashb@contoso.com
    akashc@contoso.com

    Create a .PS1 file as below:

    # The script requires the EWS managed API, which can be downloaded here:
    # http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c3342fb3-fbcc-4127-becf-872c746840e1
    # This also requires PowerShell 2.0
    # Make sure the Import-Module command below matches the DLL location of the API.
    # This path must match the install location of the EWS managed API. Change it if needed.

    [string]$info = "White" # Color for informational messages
    [string]$warning = "Yellow" # Color for warning messages
    [string]$error = "Red" # Color for error messages
    [string]$LogFile = "C:\Temp\Log.txt" # Path of the Log File

    function CreateFolder($MailboxName)
    {
    Write-host "Creating Folder for Mailbox Name:" $MailboxName -foregroundcolor $info
    Add-Content $LogFile ("Creating Folder for Mailbox Name:" + $MailboxName)

    #Change the user to Impersonate
    $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName);

    #Create the folder object



    $oFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)
    $oFolder.DisplayName = $FolderName

    #Call Save to actually create the folder
    $oFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)

    Write-host "Folder Created for " $MailboxName -foregroundcolor $warning
    Add-Content $LogFile ("Folder Created for " + $MailboxName)

    $service.ImpersonatedUserId = $null
    }

    #Change the name of the folder
    $FolderName = "My Folder"

    Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"

    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)

    # Set the Credentials
    $service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials("serviceAccount","Password","Domain")

    # Change the URL to point to your cas server
    $service.Url= new-object Uri(http://CAS-Server/EWS/Exchange.asmx)

    # Set $UseAutoDiscover to $true if you want to use AutoDiscover else it will use the URL set above
    $UseAutoDiscover = $false

    #Read data from the UserAccounts.txt
    import-csv UserAccounts.txt | foreach-object {
    $WindowsEmailAddress = $_.WindowsEmailAddress.ToString()

    if ($UseAutoDiscover -eq $true) {
    Write-host "Autodiscovering.." -foregroundcolor $info
    $UseAutoDiscover = $false
    $service.AutodiscoverUrl($WindowsEmailAddress)
    Write-host "Autodiscovering Done!" -foregroundcolor $info
    Write-host "EWS URL set to :" $service.Url -foregroundcolor $info

    }
    #To catch the Exceptions generated
    trap [System.Exception]
    {
    Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $error;
    Add-Content $LogFile ("Error: " + $_.Exception.Message);
    continue;
    }
    CreateFolder($WindowsEmailAddress)
    }


    To Run the script:
    1)Open the Exchange Management Shell
    2)Navigate to the location where you have stored the Data and the script file.
    3)Type in .\ScriptName.PS1 and hit enter to execute the script

    Enjoy!

  • Akash Blogging......

    Use Windows PowerShell in Exchange Online

    • 0 Comments

    A few articles on working with Exchange Online (Office 365 for enterprises, Live@edu) with Windows Powershell.

    Install and Configure Windows PowerShell
    http://technet.microsoft.com/en-us/exchangelabshelp/cc952756

    Connect Windows PowerShell to the Service
    http://technet.microsoft.com/en-us/exchangelabshelp/cc952755

    Reference to Available PowerShell Cmdlets in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/dd575549

    Administrator Role Groups in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/ee441216

    Role Based Access Control in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/dd207274

    Role Assignment Policies in Exchange Online
    http://technet.microsoft.com/en-us/exchangelabshelp/ee424427

    Enjoy!

Page 1 of 1 (5 items)