Scripting Games Advanced – Event 3 My Submission

May 15th, 2013 No comments

Lets first jote down the Event Requirement:

AE3-Description-1

Input:

ComputerName(s) as string (from pipeline possible).

A Path to save output file.

Output:

An html file for each computer name provided.

The output should look like this:

AE3-Description-2

Additional Requirements:

  • Paramaterize the inputs
  • Use Pipeline for ComputerName
  • The browser displays “Disk Free Space Report� in the page tab when viewing the report.
  • “Local Fixed Disk Report� is in the H2 (“Heading 2�) HTML style.  If you can actually add the computer name to that – bonus!
  •  The report ends with an HTML horizontal rule and the date and time that the report was generated.
  • The size and free space values are shown as gigabytes (GB) and megabytes (MB) respectively, each to two decimal places.

Here is the break down of my script:

Requirement one is to get free space, capacity, and drive letter of “Fixed” disks on a given machine. we can use Win32_LogicalDisk WMI Class to get this information. to filter and take only fixed disks we can use “DriveType=3″ as filter. you can read more about this class here.

Here is my parameter definitions:

    Param
    (
        #ComputerName to Generate Report For
        [Parameter(ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("CN","__SERVER","SERVER","host","PSComputerName","DNSHostName","MachineName")] 
        [String[]]
        $ComputerName = $env:COMPUTERNAME,

        #Path (Directory) to store the html file
        [Parameter()]
        [ValidateScript({Test-Path -Path $_ -PathType Container})]
        [String]
        $Path = [environment]::GetFolderPath(“MyDocuments�),

        #Credential to use if required
        [Parameter()]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty

    )
  • ComputerName is defined as a String Array, which accepts value from pipeline and by property name. There are also alisases defined here.  The default is set to local machine
  • Path is defined and a validation is done using Test-Path, the Default is set to Documents folder of the logged on user.
  • A third paramter Credential is defined as a feature to use if the target machine needs alternate credentials.

The Credential is added to the wmioptions hash table defined in the Begin Block based on its presence:

Begin {

        #Define WMI Query Options
        $WmiOptions = @{}
        $WmiOptions.Class = 'Win32_LogicalDisk'
        $WmiOptions.Property = 'Name','Size','FreeSpace'
        $WmiOptions.Filter = 'DriveType = 3'
        $WmiOptions.ErrorAction = 'Stop'
        $WmiOptions.ErrorVariable = 'WmiError'

        #Add Credential to the WMI Options if it was specified
        if ($PSBoundParameters['Credential']) {
               $wmiOptions.Credential = $Credential
        }

}

Credential is removed / preserverved based on the computer being processed, if its pointing to local machine credential is not used. we define an array for defining local hosts. and check the presense of computer name in it.

Begin {

$LocalIPAddresses = [net.dns]::GetHostAddresses($env:COMPUTERNAME) | Select-Object -ExpandProperty IpAddressToString
        $Localhost = @('', '.', 'localhost', $env:COMPUTERNAME, '::1', '127.0.0.1',"$env:COMPUTERNAME.$env:USERDNSDOMAIN") + $LocalIPAddresses

}

process {

#If Local Host Remove Credential from the Query Else Maintain/Add Credential to the Query
	            if ($Localhost -contains $computer) { $wmiOptions.Remove('Credential') } else {$wmiOptions.Add('Credential',$Credential)}

}

Next is the WMI Query:

$WmiOptions.ComputerName = $Computer

$DiskInfo = Get-WmiObject @WmiOptions

$DiskInfo = $DiskInfo | Select-Object Name,
                    @{n='Size(GB)';e={"{0:N2}" -f ($_.Size/1Gb)}},
                   @{n='FreeSpace(MB)';e={"{0:N2}" -f ($_.FreeSpace/1Mb)}}

Preparing and Generating and Saving the Output File:

$HtmlDataSection = @{}
$HtmlDataSection.As = 'Table'
$HtmlDataSection.PreContent =  "<h2>Local Fixed Disk Report For : $($Computer.ToUpper()) </h2>"
$HtmlDataSection.PostContent = "<hr/>`n$(Get-Date)" 
$HtmlDataSection.Head = "$css`n<title>Drive Free Space Report</title>"

$Html = $DiskInfo | ConvertTo-Html @HtmlDataSection
    $Html | Out-File -FilePath  $(Join-Path -Path $Path -ChildPath "$computer.html") -Force -ErrorAction Stop

I have also applied a short css style to the html report. and here is the output of my script:

AE3-Output

 

 

<#
.Synopsis
   This fucntion Get-DiskInfo Get Fixed Disks Free Space Report

.DESCRIPTION
   The Get-DiskInfo Collects disk info using WMI.
   It Only takes into account fixed disk.
   Fixed disks are identified using "DriveType" property of Win32_LogicalDisk WMI Instance.
   The Resulting Data is converted to a format suitable for html style display.

.PARAMETER ComputerName
    The computer name to collect data from, default is local machine.
    This can be a one or more computernames as strings

.PARAMETER Path
    The Directory Path to store resulting html file.
    Defaults to My Documents folder of currently logged on user.

.PARAMETER Credential
    The Credential parameter is used when specified for remote machines.
    This is useful when the logged on user doesnt have necessary rights to the target machine.

.EXAMPLE
   Get-DiskInfo
   This Provides the report of local machine as an html file in currently logged on users My Documents Folder

.EXAMPLE
   "RemoteHost","localhost","MyPC" | Get-DiskInfo -Path C:\Temp
   This Cmdlet will provide three html file output for each computer name provided and will be stroed in C:\Temp folder

.EXAMPLE
   "RemoteHost","localhost","MyPC" | Get-DiskInfo -Path C:\Temp -Credential Get-Credential
   This Cmdlet will provide three html file output for each computer name provided and will be stroed in C:\Temp folder
   In Addition it will use the alternate credential provided to query Remote PCs, for local Credential will not be used.

.INPUTS
   System.String

.OUTPUTS
   HTML File (No Output to Host Display)

.NOTES
   Version: 1.0

#>
function Get-DiskInfo
{

    [CmdletBinding()]

    Param
    (
        #ComputerName to Generate Report For
        [Parameter(ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("CN","__SERVER","SERVER","host","PSComputerName","DNSHostName","MachineName")] 
        [String[]]
        $ComputerName = $env:COMPUTERNAME,

        #Path (Directory) to store the html file
        [Parameter()]
        [ValidateScript({Test-Path -Path $_ -PathType Container})]
        [String]
        $Path = [environment]::GetFolderPath(“MyDocuments�),

        #Credential to use if required
        [Parameter()]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty

    )

    Begin {

        Write-Verbose "Initializing Function Environment"

        #Define WMI Query Options
        $WmiOptions = @{}
        $WmiOptions.Class = 'Win32_LogicalDisk'
        $WmiOptions.Property = 'Name','Size','FreeSpace'
        $WmiOptions.Filter = 'DriveType = 3'
        $WmiOptions.ErrorAction = 'Stop'
        $WmiOptions.ErrorVariable = 'WmiError'

        #Add Credential to the WMI Options if it was specified
        if ($PSBoundParameters['Credential']) {
               $wmiOptions.Credential = $Credential
        }

        #Defining entries which will be pointing at the local host from which the funtion/script is called
        $LocalIPAddresses = [net.dns]::GetHostAddresses($env:COMPUTERNAME) | Select-Object -ExpandProperty IpAddressToString
        $Localhost = @('', '.', 'localhost', $env:COMPUTERNAME, '::1', '127.0.0.1',"$env:COMPUTERNAME.$env:USERDNSDOMAIN") + $LocalIPAddresses

        $css = @'
        <style type="text/css">

        body { font-family: Tahoma, Geneva, Verdana, sans-serif;background-color: #DDDDDD}

		h2 { color:#6699CC}

        table {font-family:"Trebuchet MS", Arial, Helvetica, sans-serif; width:50%; border-collapse:collapse; background-color: #abcdef;
			border: 3px solid #ffffff;}

        th {font-size:14px; border:1px solid #ffffff;  padding-top:5px; padding-bottom:4px; text-align:left; 
		background-color:#abcdff; color:#ffffff; font-weight: bold;}

		tr {font-size:12px; border:1px solid #ffffff; padding:3px 7px 2px 7px; margin: 3px; vertical-align: top; color: #000;}

        td {font-size:12px; border:1px solid #ffffff; padding:3px 7px 2px 7px; margin: 3px; vertical-align: top; color: #000;}

        hr {background-color:#123456; border-width:1; color:#000000; height:2px; line-height:0; text-align:left; width:50%;}

		</style>
'@

    }

    Process {

        foreach($computer in $ComputerName) {

            Write-Verbose "Working on Computer:$computer"

            if(Test-Connection -ComputerName $computer -Count 1 -Quiet) {

                #If Local Host Remove Credential from the Query Else Maintain/Add Credential to the Query
	            if ($Localhost -contains $computer) { $wmiOptions.Remove('Credential') } else {$wmiOptions.Add('Credential',$Credential)}

                Write-Verbose "Collecting Disk Info from $computer using WMI"
                #collect WMI Data from computer
                $WmiOptions.ComputerName = $Computer
                try {

                    $DiskInfo = Get-WmiObject @WmiOptions
                    $DiskInfo = $DiskInfo | Select-Object Name,
                                @{n='Size(GB)';e={"{0:N2}" -f ($_.Size/1Gb)}},
                                @{n='FreeSpace(MB)';e={"{0:N2}" -f ($_.FreeSpace/1Mb)}}

                }
                catch {

                    Write-Warning "Unable to fetch wmi data from $computer"
                    Write-Verbose "Unable to fetch wmi data from $computer, below error occured : `n $_"
                    Write-Verbose "Completed Processing $Computer"

                }

                #prepare output only if WMI call was successful
                if(-not $WmiError) {

                    Write-Verbose "Generating HTML File Stream to create output file"
                    #Generating HTML File Stream
                    $HtmlDataSection = @{}
                    $HtmlDataSection.As = 'Table'
                    $HtmlDataSection.PreContent =  "<h2>Local Fixed Disk Report For : $($Computer.ToUpper()) </h2>"
                    $HtmlDataSection.PostContent = "<hr/>`n$(Get-Date)" 
                    $HtmlDataSection.Head = "$css`n<title>Drive Free Space Report</title>"

                    $Html = $DiskInfo | ConvertTo-Html @HtmlDataSection

                }

                try {

                    Write-Verbose "Saving Output to File $(Join-Path -Path $Path -ChildPath "$computer.html")"
                    $Html | Out-File -FilePath  $(Join-Path -Path $Path -ChildPath "$computer.html") -Force -ErrorAction Stop
                    Write-Verbose "Completed Processing $Computer"

                }
                catch {

                    Write-Error "Unable to Save the Output File $(Join-Path -Path $Path -ChildPath "$computer.html")"
                    Write-Verbose "Completed Processing $Computer"

                }

            }
            else {

                Write-Error "Unable to reach $computer on Network"
                Write-Verbose "Completed Processing $Computer"

            }
        }

    }

    End {

        Remove-Variable -Name WmiOptions,LocalIPAddresses,Localhost,css -Force -ErrorAction SilentlyContinue

    }
}

 

You can download the complete script here.

Cheers

Exchange 2013 Step By Step: AD Preparation Notes

February 28th, 2013 No comments

First step to get to an installation of Exchange server is to make the Active Directory ready for it. So lets have a quick look at AD Preparation for Exchange Server 2013.

 Steps involved in Active Directory Preparation

 Pre-Requisites:

  1. Schema master, Global Catalogue, and Domain Controller
    1. Minimum – Windows Server 2003 Standard with SP2
    1. Supported Up to – Windows Server 2012
    1. Read Only Domain Controller can not be used by Exchange
  1. Active Directory Forest Functional Level
    1. Windows Server 2003 or Higher
  1. Operating System and its components on the server where setup tasks are executed
    1. Install Pre-Requisite Softwares (These are included in Windows Server 2012 – so no need to install separately)
      1. Microsoft .Net Framework 4.5
      2. Windows Management Framework 3.0
    1. Install Active Directory Administration tools
      1. From Powershell run below cmdlets
      1. On Windows Server 2012: Install-WindowsFeature RSAT-ADDS
      1. On Windows Server 2008 R2 SP1: Add-WindowsFeature RSAT-ADDS
  1. Permissions

The account used to executed AD Preparation Tasks need below permissions

a) For SchemaPrep

=> Member of Schema Admins and Enterprise Admins AD Group

b) For PrepareAD

=> Member of Enterprise Admins AD Group

c) For preparing all of your domains together(if you have multiple Domains where you will have Exchange Servers or Recipients)

=> Member of Enterprise Admins AD Group

d) For Preparing the currently logged on Domain or a Named Domain

1) If the Domain was already existing before Preparing AD for Exchange

=> The account needs to be member of the Domain Admins AD Group on the particular domain you are preparing

2) If the Domain was created after the AD was prepared for Exchange

=> The account needs to be member of “Organization Management” RBAC Group and “Domain Admins” AD Group

  1. Multi forest environment: If you have multiple forests, make sure you are preparing your Exchange forest(the AD forest where you want to deploy Exchange Server 2013) and not the other forests.
  2. Where to run the AD Preparation Tasks
    1. On a 64bit Member Server or
    1. On a 64bit Writable Domain Controller
    1. PrepareSchema and PrepareAD should be run from the same AD site as Schema Master

 

Steps to prepare AD:

If your Active Directory and Exchange is managed by same IT Team, its quick straight forward:
Note: Setup program is available in Exchange 2013 Media
Note: With all the setup commands you must specify /IAcceptExchangeServerLicenseTerms, this is different from Exchange 2010.

 

From PowerShell or Command Prompt execute below (Make sure you run them with Elevated Rights):

Option 1)

Execution: Setup /PrepareAD [/OrganizationName:<organization name>]  or setup /p [/on:<organization name>]

Verification:  msExchProductId attribute value of the Organization Container should be 15.00.0516.032

 

Option 2) Run your first exchange server setup installation using GUI Wizard, it will prepare all the AD Preparation Tasks during the setup

Note:- This is not possible if you are not planning to install any Exchange server in the same site as Schema Master because some times Schema Master is kept in an isolated AD Site

 

If your AD Team and Exchange Team has separate Active Directory Rights and Restrictions:

 

Note: – Make sure you wait for AD Replication to be complete before proceeding from one step to another, otherwise you might end up in an error like below:

 

 

1) Preparing Schema

Execution: Setup /PrepareSchema  or setup /ps

Verification: After the successful completion of Schema Preparation, the value of rangeUpper will be 15137 on ms-Exch-Schema-Version-Pt Attribute, you can view this by using ADSIEDIT, LDP.exe, Schema Management MMC Snapin, or even by using Powershell.

 

2) Preparing AD (Exchange Organization Preparation and Current Domain Preparation)

Execution: setup /PrepareAD [/OrganizationName:<organization name>] or setup /p [/on:<organization name>]

Verification:

ObjectVersion attribute value of the Organization container should be 15449

msExchProductId attribute value of the Organization Container should be 15.00.0516.032

ObjectVersion attribute value of the “Microsoft Exchange System Objects” OU in the root domain should be 13236

A New group called “Exchange Install Domain Servers” is created under the MESO OU (read previous line).

Check  if the “Microsoft Exchange Security Groups” OU is created in the root domain and it contains the default RBAC Security Groups:

 

3) Preparing a Domain (Other than the domain where we did ran PrepareAD – PrepareAD prepares the Local Domain as well)

Execution:  setup /PrepareDomain or setup /pd

To prepare all the domains in your Forest:  setup /PrepareAllDomains or setup /pad

Verification:

A new group called “Exchange Install Domain Servers” is created in the local Domain under the “MESO”(Microsoft Exchange System Objects) OU

Exchange Install Domain Servers group is member of the “Exchange Servers” Group in the root domain

 

For a granular steps on the verification aspect, refer an old post here, which is for Exchange 2013 Preview but the verification methods still remain same.

In the next post we will have a look at the Server Installation steps in different scenarios.

Cheers, and share your comments below if any.

Powershell Scripting for Exchange Server – Some Tips

December 30th, 2012 No comments

Here is a consolidation of few features and tips from my scripting practice for Exchange Server.

First thing any scripter need is a script editor, and when you talk about script editors for Powershell, there is a minimum feature requirement. Of them are Tab completion, Testing and debugging the script from the editor itself.

Getting the Exchange cmdlets inside the editor, for this first thing required is to have Exchange Management Tools Installed.  After that we open Powershell ISE and check the loaded PS Snapins

PS C:\> Get-PSSnapin | Format-Table Name

Name                                                                                                                                  
----                                                                                                                                  
Microsoft.PowerShell.Diagnostics                                                                                                      
Microsoft.WSMan.Management                                                                                                            
Microsoft.PowerShell.Core                                                                                                             
Microsoft.PowerShell.Utility                                                                                                          
Microsoft.PowerShell.Host                                                                                                             
Microsoft.PowerShell.Management                                                                                                       
Microsoft.PowerShell.Security                                                                                                         

You can see from above all the loaded snapins, and you wont see Exchange Snapins.

Now, lets check for the Registered but not loaded Snapins

PS C:\> Get-PSSnapin -Registered | Format-Table Name

Name                                                                                                                                  
----                                                                                                                                
Microsoft.Exchange.Management.PowerShell.E2010                                                                                        
Microsoft.Exchange.Management.PowerShell.Setup                                                                                        
Microsoft.Exchange.Management.Powershell.Support 

You can see above there are three snapins registered by Exchange Server 2010

Now load the Registered Snapins to the current session

PS C:\> Get-PSSnapin -Registered | Add-PSSnapin

PS C:\> Get-PSSnapin | Format-Table Name

Name                                                                                                                                  
----                                                                                                                                  
Microsoft.PowerShell.Diagnostics                                                                                                      
Microsoft.WSMan.Management                                                                                                            
Microsoft.PowerShell.Core                                                                                                             
Microsoft.PowerShell.Utility                                                                                                          
Microsoft.PowerShell.Host                                                                                                             
Microsoft.PowerShell.Management                                                                                                       
Microsoft.PowerShell.Security                                                                                                         
Microsoft.Exchange.Management.PowerShell.E2010                                                                                        
Microsoft.Exchange.Management.PowerShell.Setup                                                                                        
Microsoft.Exchange.Management.Powershell.Support                                                

                                      

Now we have all the exchange cmdlets loaded in our session, good to go.

Lets try a simple Exchange 2010 Cmdlet.

PS C:\> Get-Mailbox
Get-Mailbox : Value cannot be null.
Parameter name: serverSettings
At line:1 char:12
+ Get-Mailbox <<<< 
    + CategoryInfo          : NotSpecified: (:) [Get-Mailbox], ArgumentNullException
    + FullyQualifiedErrorId : System.ArgumentNullException,Microsoft.Exchange.Management.RecipientTasks.GetMailbox

Why is this error, whenever you start Exchange Management Console or Exchange Management Shell, it initiates a Powershell Remoting Session, and sets some default settings such as the Domain Controller, the Exchange Server to connect etc. In above case these things didnt happen as we have just loaded the raw cmdlets using PSSnapin.

Another important point to note is RBAC, when you load just the snapins, you are loading the raw cmdlets to the session.  Exchange is not intended to be managed like that. There is Robust Access Control layer built into the management tools, the Role Based Access Control (RBAC). When we do execute cmdlets by just loading PSSnapin it DOESNT RESPECT RBAC, and when RBAC is not respected the cmdlets may give unexpected results.

PS: But this doenst block us from using the snapins to easily write or edit scripts, only thing is that you cannot run the scripts here.

The solution is to use Implicit Remoting:

In this method, we are not loading any Snapins, we just create an implicit remoting session using Import-PSSession and that session loads all the cmdlets we need,  it also respects the RBAC (So you only get access to the Cmdlets you have been assigned inside the RBAC Roles).

Creating an Implicit Remoting session to Exchange Server 2010

#If you want to pass an alternate credential to connect to exchange you can use -Credential parameter

#To interactively pass the credentials use -Credential (Get-Credential) to the New-PSSession Cmdlet

$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri <a href="http://EXSRV1/Powershell&lt;/a&gt;Import-PSSession">http://EXSRV1/Powershell&lt;/a&gt; Import-PSSession</a> -Session $session
WARNING: Some imported command names include unapproved verbs which might make them less discoverable.  Use the Verbose parameter for mo
re detail or type Get-Verb to see the list of approved verbs.

ModuleType Name                      ExportedCommands                                                          
---------- ----                      ----------------                                                          
Script     tmp_819a88a7-c59d-464e... {Get-IRMConfiguration, New-MailUser, Enable-CmdletExtensionAgent, New-Ac...

There is one warning always comes as powershell checks for the cmdlet verbs used, its due to the cmdlets Clean-MailboxDatabase, and Retry-Queue, both Clean and Retry are not in the Approved verbs you get when executing the cmdlet Get-Verb

Now lets try Get-Mailbox

PS C:\> Get-Mailbox

Name                      Alias                ServerName       ProhibitSendQuota                                                     
----                      -----                ----------       -----------------                                                     
Administrator             Administrator        ex2010mbx11      unlimited                                                             
DiscoverySearchMailbox... DiscoverySearchMa... ex2010mbx11      50 GB (53,687,091,200 bytes)  

All is well!

Now lets see another difference to keep in mind while scripting for Exchange

Lets see some examples from Exchange Management Shell

[PS] C:\>$mbx = Get-Mailbox -Identity Administrator
[PS] C:\>$mbx.EmailAddresses

SmtpAddress        : [email protected]
AddressString      : [email protected]
ProxyAddressString : smtp:[email protected]
Prefix             : SMTP
IsPrimaryAddress   : False
PrefixString       : smtp
SmtpAddress        : [email protected]
AddressString      : [email protected]
ProxyAddressString : SMTP:[email protected]
Prefix             : SMTP
IsPrimaryAddress   : True
PrefixString       : SMTP

you can see from above that you are getting a complete object of email address which has properties like prefixstring, IsPrimaryAddress etc. see the members of email address object

[PS] C:\>$mbx.emailaddresses | Get-Member
   TypeName: Microsoft.Exchange.Data.SmtpProxyAddress

Name               MemberType Definition
----               ---------- ----------
CompareTo          Method     int CompareTo(Microsoft.Exchange.Data.ProxyAddress othe
Equals             Method     bool Equals(System.Object obj), bool Equals(Microsoft.E
GetHashCode        Method     int GetHashCode()
GetSimilarProxy    Method     Microsoft.Exchange.Data.ProxyAddressBase GetSimilarProx
GetType            Method     type GetType()
ToPrimary          Method     Microsoft.Exchange.Data.ProxyAddressBase ToPrimary()
ToSecondary        Method     Microsoft.Exchange.Data.ProxyAddressBase ToSecondary()
ToString           Method     string ToString()
TryDeencapsulate   Method     bool TryDeencapsulate(Microsoft.Exchange.Data.ProxyAddr
AddressString      Property   System.String AddressString {get;}
IsPrimaryAddress   Property   System.Boolean IsPrimaryAddress {get;}
Prefix             Property   Microsoft.Exchange.Data.ProxyAddressPrefix Prefix {get;
PrefixString       Property   System.String PrefixString {get;}
ProxyAddressString Property   System.String ProxyAddressString {get;}
SmtpAddress        Property   System.String SmtpAddress {get;}

Similarly you can see the members of the Mailbox itself, for readability i have remove most of them. The relevant member to our discussion is EmailAddresses properyt, you can see here that its a of ProxyAddressCollection

[PS] C:\>$mbx | Get-Member
   TypeName: Microsoft.Exchange.Data.Directory.Management.Mailbox

Name                                   MemberType   Definition
----                                   ----------   ----------
Clone                                  Method       System.Object Clone()
Equals                                 Method       bool Equals(System.Object obj)
GetHashCode                            Method       int GetHashCode()
GetProperties                          Method       System.Object[] GetProperties(Syst
GetType                                Method       type GetType()
ToString                               Method       string ToString()
...
EmailAddresses                         Property     Microsoft.Exchange.Data.ProxyAddressCollection EmailAddresses {g...

Now lets observe the same from Powershell Console or the ISE

PS C:\> $mbx = Get-Mailbox -Identity Administrator
PS C:\> $mbx.EmailAddresses
smtp:[email protected]
SMTP:[email protected]

you can see here, that you are not getting anything like prefix or IsPrimary etc. you are just getting the data as string.

To prove that lets use Get-Member on the EmailAddresses property or you can also use

# $mbx.EmailAddresses.GetType()
PS C:\> $mbx.EmailAddresses | Get-Member
   TypeName: System.String

Name             MemberType            Definition                                                                                     
----             ----------            ----------                                                                                     
Clone            Method                System.Object Clone()                                                                          
CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB)                                 
Contains         Method                bool Contains(string value)                                                                    
CopyTo           Method                System.Void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)       
EndsWith         Method                bool EndsWith(string value), bool          
So, having looked at all those details, just see if we see any difference in the Retried Object itself in the $mbx variable

PS C:\> $mbx | Get-Member
   TypeName: Deserialized.Microsoft.Exchange.Data.Directory.Management.Mailbox

Name                                   MemberType   Definition                                                                        
----                                   ----------   ----------                                                                        
ToString                               Method       string ToString(), string ToString(string format, System.IFormatProvider formatP...
PSComputerName                         NoteProperty System.String PSComputerName=ex2010mbx11                                          
RunspaceId                             NoteProperty System.Guid RunspaceId=1c8913bb-b202-4b52-8a13-17da41b77056                       
AcceptMessagesOnlyFrom                 Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
AcceptMessagesOnlyFromDLMembers        Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
AcceptMessagesOnlyFromSendersOrMembers Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
AddressBookPolicy                      Property      {get;set;}                                                                       
AddressListMembership                  Property     Deserialized.Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsof...
Alias                                  Property     System.String {get;set;}                                                          
AntispamBypassEnabled                  Property     System.Boolean {get;set;}                                                         
ArbitrationMailbox                     Property      {get;set;}                                                                       
ArchiveDatabase                        Property      {get;set;}                                                                       
ArchiveDomain                          Property      {get;set;}             
....
EmailAddresses                         Property     Deserialized.Microsoft.Exchange.Data.ProxyAddressCollection {get;set;}

Important information to look here is the word "Deserialized" in the beginning of Type Name and also in the definition of EmailAddresses property.  This brings us another concept to understand, "How Powershell Remoting perform Data Transfer". This discussion is to not dig into that area, i will provide some reference links to read abou it at the end of this post. Its already explained by Powershell Experts out there.

Deserialized Objects

In short, it does somthing called serialization and deserializaion - a form of transporting data or live objects using XML. When objects get deserialized they loose their dynamic nature, so you cannot perform most of the method calls or explore dynamic nature of the Object.

Lets understand this by looking at another example:

Exploring maibox database object inside the script, lets first look at from Powershell Console or ISe

$Db = Get-MailboxDatabase MBX11-DB01

PS C:\> $Db.EdbFilePath
C:\MBX11-DB01\mbx11-db01.edb

the property EdbFilePath gives you the complete path to the Database File.

you can see the type of this property is string

PS C:\> $Db.EdbFilePath.GetType()

IsPublic IsSerial Name                                     BaseType                                     
-------- -------- ----                                     --------                       
True     True     String                                   System.Object

Also if you execute below you get no output:

PS C:\&gt; $Db.EdbFilePath.PathName
PS C:\&gt;

Now, perform the same steps from Exchange Management Shell

[PS] C:\>$Db = Get-MailboxDatabase MBX11-DB01

[PS] C:\>$Db.EdbFilePath
IsPathInRootDirectory : False
PathName              : C:\MBX11-DB01\mbx11-db01.edb
IsLocalFull           : True
IsUnc                 : False
DriveName             : C:
ServerName            :

You can see here the property EdbFilePath is not just a string here its a complete object with more classified information, such as the DriveName property, PathName property etc.

[PS] C:\>$Db.EdbFilePath.PathName
C:\MBX11-DB01\mbx11-db01.edb

here you get an output from the EdbFilePath.PathName, notice this failed in the pervious example in ISE.

This is because EdbFilePath is not a string object here.

[PS] C:\>$Db.EdbFilePath | gm
   TypeName: Microsoft.Exchange.Data.EdbFilePath

Name                        MemberType Definition
----                        ---------- ----------
CompareTo                   Method     int CompareTo(Microsoft.Exchange.Data.Lo
Equals                      Method     bool Equals(System.Object value), bool E
GetHashCode                 Method     int GetHashCode()
GetType                     Method     type GetType()
ToString                    Method     string ToString()
ValidateDirectoryPathLength Method     System.Void ValidateDirectoryPathLength(
ValidateEdbFileExtension    Method     System.Void ValidateEdbFileExtension()
ValidateFilePathLength      Method     System.Void ValidateFilePathLength()
DriveName                   Property   System.String DriveName {get;}
IsLocalFull                 Property   System.Boolean IsLocalFull {get;}
IsPathInRootDirectory       Property   System.Boolean IsPathInRootDirectory {ge
IsUnc                       Property   System.Boolean IsUnc {get;}
PathName                    Property   System.String PathName {get;}
ServerName                  Property   System.String ServerName {get;}
[PS] C:\>

What is the take way from this is while writing the script keep in mind the differences, such that in EMS $Db.EdbFilePath is an object by itself and if you requirement is to just get the path you can use $Db.EdbFilePath.PathName But this will not work when you are executing from PowerShell ISE.

Next thing to consider is testing and executing your Scripts for Exchange Environment, you can use PowerShell ISE to write scripts, but for testing run them from Exchange Management Shell, and keep in mind the difference like above and due to that the intellisence or tab completion will not show the nested properties.

Scheduling Exchange Server Scripts

Now a simple tip on how to schedule Exchange Server Scripts using Task Scheduler. Most of the times, you need to run scripts on a repeated intervel without user intervention. you can schedule your Exchange scripts and still use the EMS as the execution platform. i will just show the Action to trigger while creating a Scheuled Task for Exchange Server 2010 Script.

The Program to Run will be Powershell.exe

You should pass optional arguments to Powershell.exe to do things like running it in non-interactive Mode and/or hide the window etc. you can use -Command to say which command to run once powershell.exe is loaded

Field – Program to Run: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Field – Optional Arguments:

-NonInteractive -WindowStyle Hidden -command “. ‘C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1′; Connect-ExchangeServer -auto; “& ‘C:\Scripts\Report-ServerHealth.ps1′”

There different aspects in the optional arguments field:

1. -NonInteractive – Non Interactive Window

2. -WindowStyle Hidden  – The Powershell windows is hidden (you can still the process in task manager)

3. -command – the command to execute after loading powershell.exe, this can be one or multiple cmdlets(seperated by “;”)

4. The first cmdlet “. ‘C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1′; -> the is the default script loaded when you open EMS

5. Connect-ExchangeServer -auto; -> this is the function which was loaded by the previous script called RemoteExchange.ps1

6. “& ‘C:\Scripts\Report-ServerHealth.ps1′” -> now the real script we have created (finally!!)

 

Its the era of Powershell v3 now, which bring more interesting features for writing scripts, such as Intellisense, auto module loading, show command feature etc.

PS v3 is not yet supported by Exchange server 2010 (that is at version SP2 RU5) so DO NOT go and install it on your Exchange Server 2010 Server, you will breat it.

but you can use it for script editing, on your desktop or scripting machine. below are few useful features:

The Improved Intellisense feature, this will help to discover cmdlets and parameter and even parameter values

Intellisense Examples:

Exploring Objects made easy (remember “Deserialization” EdbFilePath is a String here as you can see):

 

The Show-Command cmdlet to find out the syntax to use before you start scripting, you can fill out the GUI Form and then click on Copy to copy the syntax to clipboard.

Paste it back in the editor and you just got a complete syntax to use.

Some PSRemoting References:

 About_Remot_Output

 How objects are send to and from remote sessions

What else to look for while Writing or Editing Scripts for Exchange Server, do give your comments!

 

 

Categories: Exchange, Powershell Tags: ,