Also while I was at TechEd 2010 a few weeks back, I met up with Harold Wong and he recorded a short (9 min) interview with me about Rich Coexistence.
Have a look: http://blogs.technet.com/b/haroldwong/archive/2010/06/18/tech-ed-2010-interview-with-evan-dodds-exchange-server.aspx
At TechEd 2010 in New Orleans a week or two ago, I gave a talk entitled “Using Microsoft Exchange Server 2010 to Achieve Rich Coexistence with Exchange Online”. One of the cool things about TechEd talks is that now they’re recording them and posting them publicly for access by anyone, so if this is a topic that interests you, you might want to have a look at: http://www.msteched.com/2010/NorthAmerica/UNC309
What is “Rich Coexistence”? Well, it’s when you’ve configured Exchange to run on your premises and you’ve configured coexistence with the cloud… *AND* you’ve put at least one Exchange 2010 SP1 CAS/HUB server role in your on-prem environment as a “coexistence gateway”. Once you’ve got this set-up, your Exchange organization can be configured to appear to span both on-prem and the cloud in a pretty seamless way. The talk goes into more details on what you get if you set this up, but suffice to say it’s a great, high-fidelity coexistence scenario!
Today's post is to address a question we dealt with fairly recently through a customer escalation. The customer was trying to figure out the best way to programmatically get access to the entries in a MultiValuedProperty collection returned as part of an Exchange object.
Specific example we'll use here: Read in one or more mailbox objects (using Get-Mailbox cmdlet) and then iterate across and output some properties. Since we're outputting "Name" and "EmailAddresses" properties here, one will be a singleton ("Name") and one will be a collection ("EmailAddresses").
To accomplish extracting this data, immediately the customer had driven straight into the Exchange.Management namespaces looking for Mailbox. But that's not the right approach. Vivek talks about why in this blog post, but the short version is that there's nothing to be gained by tying your code directly into Exchange (no useful public methods and limited benefit from the property strong-types) and quite a bit to be lost (you become tied to the assembly version, etc).
So, how then do you do it? Well, you use PSObject and non-Exchange types everywhere. There's a pretty good starter example in Technet: http://msdn2.microsoft.com/en-us/library/bb332449.aspx
Which leads to the customer's real question -- "I can get the object, but how do I drill into the strong-type properties that are of types like MultiValuedProperty?"
Good news! MultiValuedProperty type implements ICollection, so it's actually pretty simple. When you extract the property value, just cast it as ICollection and you're golden!
Here's some sample code (based on the Technet code) with a few comments added:
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create(); PSSnapInException snapInException = null; PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapInException); Runspace myRunSpace = RunspaceFactory.CreateRunspace(rsConfig); myRunSpace.Open(); Pipeline pipeline = myRunSpace.CreatePipeline(); Command myCommand = new Command("Get-Mailbox"); pipeline.Commands.Add(myCommand); Collection<PSObject> commandResults = pipeline.Invoke(); // Ok, now we've got a bunch of mailboxes, cycle through them foreach (PSObject mailbox in commandResults) { //define which properties to get foreach (String propName in new string[] { "Name", "EmailAddresses" }) { //grab the specified property of this mailbox Object objValue = mailbox.Properties[propName].Value; // is it a collection? "Name" isn't, but "EmailAddresses" is in this example if (objValue is ICollection) { ICollection collection = (ICollection)objValue; // Loop through each entry in the collection and output it foreach (object value in collection) { Console.WriteLine(value.ToString()); } } else { Console.WriteLine(objValue.ToString()); } } } myRunSpace.Close();
RunspaceConfiguration rsConfig = RunspaceConfiguration.Create(); PSSnapInException snapInException = null; PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapInException); Runspace myRunSpace = RunspaceFactory.CreateRunspace(rsConfig); myRunSpace.Open();
Pipeline pipeline = myRunSpace.CreatePipeline(); Command myCommand = new Command("Get-Mailbox");
pipeline.Commands.Add(myCommand);
Collection<PSObject> commandResults = pipeline.Invoke();
// Ok, now we've got a bunch of mailboxes, cycle through them foreach (PSObject mailbox in commandResults) { //define which properties to get foreach (String propName in new string[] { "Name", "EmailAddresses" }) { //grab the specified property of this mailbox Object objValue = mailbox.Properties[propName].Value; // is it a collection? "Name" isn't, but "EmailAddresses" is in this example if (objValue is ICollection) { ICollection collection = (ICollection)objValue; // Loop through each entry in the collection and output it foreach (object value in collection) { Console.WriteLine(value.ToString()); } } else { Console.WriteLine(objValue.ToString()); } } } myRunSpace.Close();
Thanks to Mike Hamler for assistance with the generic solution!
Back in my PSS days, it was a common data-gathering/troubleshooting technique to collect "LDP Dumps" (ie - full AD propertyname+value data for a given object) as a way of collecting data for a troubleshooting investigation. If you're a really long-time Exchange administrator, just pretend I said "raw mode dump" and know that it's effectively the same thing, but for an AD object.
Recently, a customer suggested that we (Exchange) should produce a built-in cmdlet which can, given a pipeline input of an Exchange object, connect to the AD and provide this sort of LDP dump data, so that they could replace their use of LDP.exe. The rationale is that this could be pretty useful to collect this data more rapidly (and with fewer clicks) when troubleshooting an issue. I thought about it for a bit, and realized that because of the directory support built into PowerShell (or, rather, .Net) this would be something possible to write with a fairly simple script... no new cmdlet required!
Note that in an effort to keep the output looking roughly like the LDP output, I'm writing out strings rather than objects. This is a little counter to the spirit of PowerShell, of course, but it's also easily overcome with some minor changes to the script below.
Behold, the outcome of this experiment - I hope it's useful to you!
## LDPDump.ps1 script# This script is a PowerShell way to simulate the effects of the old-school "LDP Dump" used by Exchange PSS and other Exchange power-administrators# (ie - connecting with LDP.exe, navigating to an object, and double-clicking to get all of the property and value data onto the screen)# ## Discover whether the script is receiving piped input#if ( $input.movenext() ){ $inputExists = $true $input.reset()} # # There is no piped input - provide some guidance and exit#if (!$inputExists ) { write-error "You need to pipe in some object!" exit} # Let's loop on each of the things piped into the scriptforeach ($j in $input){ # Grab out the distinguished name (this is a little bit Exchange-centric, but it's also sort of standard-ish) $dn=$j.DistinguishedName # Did we find a DistinguishedName property? If not, ABORT! if($dn -eq $null) { write-error "THIS OBJECT DOES NOT HAVE A DN: $($j.ToString())" write-host "" } else { # OK, we found a DN... let's open a session to AD $ldapdn = "[ADSI]'LDAP://$dn'" $ldpdump = invoke-expression "$ldapdn" # Print a header for this object write-host "$($ldpdump.psbase.path)" foreach ($k in $ldpdump.psbase.properties.PropertyNames) { # Make each count, propertyname and value output look pretty much like LDP does (ie - "#>Name:Value") write-host "$($ldpdump.psbase.properties[$k].count)>$($k):$($ldpdump.psbase.properties[$k])" } write-host "" }}
## LDPDump.ps1 script# This script is a PowerShell way to simulate the effects of the old-school "LDP Dump" used by Exchange PSS and other Exchange power-administrators# (ie - connecting with LDP.exe, navigating to an object, and double-clicking to get all of the property and value data onto the screen)#
## Discover whether the script is receiving piped input#if ( $input.movenext() ){ $inputExists = $true $input.reset()}
# # There is no piped input - provide some guidance and exit#if (!$inputExists ) { write-error "You need to pipe in some object!" exit}
# Let's loop on each of the things piped into the scriptforeach ($j in $input){ # Grab out the distinguished name (this is a little bit Exchange-centric, but it's also sort of standard-ish) $dn=$j.DistinguishedName # Did we find a DistinguishedName property? If not, ABORT! if($dn -eq $null) { write-error "THIS OBJECT DOES NOT HAVE A DN: $($j.ToString())" write-host "" } else { # OK, we found a DN... let's open a session to AD $ldapdn = "[ADSI]'LDAP://$dn'" $ldpdump = invoke-expression "$ldapdn" # Print a header for this object write-host "$($ldpdump.psbase.path)" foreach ($k in $ldpdump.psbase.properties.PropertyNames) { # Make each count, propertyname and value output look pretty much like LDP does (ie - "#>Name:Value") write-host "$($ldpdump.psbase.properties[$k].count)>$($k):$($ldpdump.psbase.properties[$k])" } write-host "" }}
Thanks to Sebastian Bengochea for help getting at the ProxyCollection data!
I just heard that (Exchange VP) Terry's going to be giving the keynote at Interact this year, and he's promised the first real public details about ExchangeLabs! Sweet... this is exciting stuff!
Plus -- Robert and Andrew will be presenting the PowerShell Automation talk, so that alone makes the conference worthwhile, of course. :)
Aeons ago, Vivek posted about how to get DL membership in Exchange 2007. He commented that "This can be adapted to do nested membership as well—I’ll leave it to you to figure out how."
At the time, I remember reading this and thinking briefly about how I'd do this, but never really sitting down and doing it to prove my idea was correct.
Well, my time finally ran out on not looking at this. The other day, someone sent me a semi-working PowerShell function to get DL membership (including recursion) and asked me how to make it detect looping recursion. It's been over a year since Vivek delegated this problem "to you", so I guess that's about enough time and we should share an answer. Let's get to it!
Here's the function we started with:
function Get-DLMemberRecurse{ foreach ($varTemp in get-distributiongroupmember $args[0]) { $varTemp if ($varTemp.RecipientType -like "Mail*Group") { Get-DLMemberRecurse $varTemp.Identity } }}
Key problem? If you have DG1 which has DG2 as a member which then has DG1 as a member (a looping membership hierarchy which is totally possible to set, but totally useless practically), it loops forever until it overflows the stack. Whoops, that's no good!
So we need to make a couple of changes to solve this problem. We need to do at least two things (which I've color coded in my updated function below to highlight the changes):
function Get-DLMemberRecurse{ $ParentScriptBlock = (get-variable -scope 1 MyInvocation -value).MyCommand.ScriptBlock; if($ParentScriptBlock -ne $MyInvocation.MyCommand.ScriptBlock) { # we're not recursing (we should only hit this once and we'll use it to reset the DGList) $global:DGList = @() $global:DGList += (Get-DistributionGroup $args[0]).Identity } foreach ($varTemp in get-distributiongroupmember $args[0]) { $varTemp if ($varTemp.RecipientType -like "Mail*Group" -and -not ($global:DGList -contains $varTemp.identity) ) { $global:DGList += $varTemp.Identity; Get-DLMemberRecurse $varTemp.Identity } } }
function Get-DLMemberRecurse{ $ParentScriptBlock = (get-variable -scope 1 MyInvocation -value).MyCommand.ScriptBlock; if($ParentScriptBlock -ne $MyInvocation.MyCommand.ScriptBlock) { # we're not recursing (we should only hit this once and we'll use it to reset the DGList) $global:DGList = @() $global:DGList += (Get-DistributionGroup $args[0]).Identity } foreach ($varTemp in get-distributiongroupmember $args[0]) { $varTemp
if ($varTemp.RecipientType -like "Mail*Group" -and -not ($global:DGList -contains $varTemp.identity) ) { $global:DGList += $varTemp.Identity; Get-DLMemberRecurse $varTemp.Identity }
}
Special thanks to my buds on the PowerShell team for getting me going with the "how to know if I'm inside a recursion loop using MyInvocation variable" tip.
Dmitry blogs all about it here. I'd been meaning to write pretty much this very same post ever since I attended Devin's talk on managing Legacy Exchange with PowerShell at ExchangeConnections last November this past April, so I'm glad to see Dmitry write it up!
It turns out that my TechEd Exchange Scripting with PowerShell session was recorded to video, and now it's available for all to download (no more having to provide TechEd credentials!). Link below on the session title to the video of my session and to the rest of the sessions they've just released!
Microsoft Windows PowerShell Scripting for Microsoft Exchange Server 2007
Evan Dodds, Program Manager, Microsoft Corporation
Level: 300
This session covers the new Windows PowerShell-based Exchange cmdline and scripting interface. Learn how to convert your multiple page Visual Basic and COM scripts to mere one-liners in Exchange 2007. We cover the basics of the management shell, as well as the underlying design and key concepts. Additionally, we go into more depth on how to build larger scripts that can be used to automate small, medium, as well as enterprise business scenarios.
Link to list of all Tech Ed 2007 Recorded Sessions for ITs Showtime:
http://www.microsoft.com/emea/itsshowtime/result_search.aspx?event=65&x=10&y=3
Lots of people are asking lots of questions at the ExchangeNinjas wiki! Glad to see so much participation both from within the Exchange team and also from the broad Exchange community!!
This question struck me as pretty interesting, so I figured I'd surface it on the blog as well as answering it (a few weeks ago) on the wiki... From the wiki's Recipient Management FAQs page (originally was posted to the Ask a Question page) :
Q: I want to create a custom dynamic distribution group for all users in a storage group. I can get the user if I use: get-mailbox | where {$_.Database -like "*<SGName>*"} BUT this doesn't work for a new DDG :new-DynamicDistributionGroup -alias test1a -name test1a -recipientfilter {Database -like "*SG01-SUKMSDMBX03*"} -org exchorg.local I get no members! It will work on a per server basis, but this it no good for me. Can you help? Is this possible?
A: The problem is that the "Database" filter can't do partial string matches, because underneath this property is actually a distinguished name value in the AD (which can't do substring matching). Building an infrastructure that allows you to direct email to mailboxes on a storage group is definitely possible -- just not like this. Instead, you need to ensure you're using the full distinguished name for each database you want to compare against, and not using wildcards (ie, asterisk *)
There's an easy way to do this and a hard way. The hard way I'll just talk about, then I'll show you the easy way. The hard way would be to iterate through all of the MDBs in your selected storage group, concatenating a filter-parser string made up of their DNs... with appropriately placed "-or" operators between. Then pass this string into the New-DDG cmdlet as the RecipientFilter. Yuck.
The easy way is to create a DDG for each mailboxdatabase. Then create a DistributionGroup (doesn't have to be DDG) and add all of these per-MDB DDGs into the DG. Here's how that might look:
new-distributiongroup DG-MySg1 -Type Distribution -SamAccountName DG-MySg1get-mailboxdatabase -Server Srv1 -StorageGroup MySG1 | % { New-DynamicDistributionGroup "DDG-$($_.Name)" -RecipientFilter "Database -eq '$($_.Identity.DistinguishedName)'" } | % { Add-DistributionGroupMember DG-MySg1 -Member $_.Identity }
Updated July10: Added -Server switch to make it a more real-world syntax.
I realized this week that all of the sessions (and video recordings of each session -- I didn't even realize they were recording ALL of the sessions!) are available online. This means even if you ended up double-booked for two interesting sessions and had to choose just ONE in person, you can still review the deck and the video for the session you missed. Of course, that means you probably don't need to see my sessions, since you SURELY chose to see those in person and missed the OTHER, conflicting session. Right? :)
Anyway, here are the links to sessions I participated in this year. I think you may have to be signed in to the TechEd site to get through to these (and note that if you're signed in, you can get to these directly by selecting "Content Downloads & Recordings" in the left of the screen).
Also, I think the PowerShell session is going to be on the ITSShowtime site at some point in the near future as well. I'll post the link when it is up.
I'm generally very focused on Exchange and our PowerShell cmdlets, but I had an opportunity this week to attend a demo session for the Quest "ActiveRoles Management Shell" cmdlets, one of several PowerShell related tools from Quest. This suite of Cmdlets seems primarily targeted to supporting the Quest ActiveRoles server (their outboard provisioning system), but has the wonderful side effect of working directly against regular AD as well.
The cmdlets (in the version I downloaded this week, there are 22 in total) cover AD User, Group, and "Object" management, among several other things. They appear to be primarily wrappers for the LDAP DirectoryEntry behavior that I've blogged about here before. On the one hand, that really just means they're wrapping a function that's already available in the base PowerShell. But, as someone who's... er... "struggled" with the LDAP DirectoryEntry usage, there's definitely something to be said for a rich set of cmdlets to abstract that troublesome usability! Spoken like someone from the Exchange team, I realize -- in Exchange we have implemented lots of cmdlets and strongly-typed objects/properties to make usability easier and more predictable (rather than complex but generic syntax solutions). So, in that sense, I think this is a great solution!
In any case, these cmdlets are useful and FREE. So if that sounds interesting to you, here's the link.
I didn't get a chance to do this demo in my Exchange Automation with PowerShell session at Teched 2007, mostly due to time. However, I had a conversation with someone prior to my talk and I said that I'd cover it at some point. Well, let's cover it here in the blog and call it done. :)
The point of this blog/demo is primarily to show how Exchange 2007 object validation works, and how to use the powerful validation programmatically. A secondary benefit of this demo is that in order to "hack up" the object to get validation to fail, the most effective way is to use an LDAP DirectoryEntry object... so you get a brief view into how to do this as well. Hopefully together or separate these things are useful to you!
Ok, let's set the stage. Here's a simple mailbox object:
[PS] C:\>get-mailbox user1 | ft *quota* ProhibitSendQuo ProhibitSendRec UseDatabaseQuot IssueWarningQuo RulesQuotata eiveQuota aDefaults ta--------------- --------------- --------------- --------------- ----------unlimited unlimited True 2GB 64KB
[PS] C:\>get-mailbox user1 | ft *quota*
ProhibitSendQuo ProhibitSendRec UseDatabaseQuot IssueWarningQuo RulesQuotata eiveQuota aDefaults ta--------------- --------------- --------------- --------------- ----------unlimited unlimited True 2GB 64KB
Next, let's set some quota values on it so we have a baseline:
[PS] C:\>set-mailbox user1 -UseDatabaseQuotaDefaults:$false [PS] C:\>set-mailbox user1 -ProhibitSendQuota 800kbSet-Mailbox : The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '800KB', IssueWarningQuota: '2GB'.At line:1 char:12+ set-mailbox <<<< user1 -ProhibitSendQuota 800kb
[PS] C:\>set-mailbox user1 -UseDatabaseQuotaDefaults:$false
[PS] C:\>set-mailbox user1 -ProhibitSendQuota 800kbSet-Mailbox : The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '800KB', IssueWarningQuota: '2GB'.At line:1 char:12+ set-mailbox <<<< user1 -ProhibitSendQuota 800kb
Oh yeah, that won't work. We have logic in place to ensure that ProhibitSendQuota is always the same size or larger than IssueWarningQuota. Set-mailbox won't let us violate this rule. We still have a valid mailbox object at this point, because no ProhibitSendQuota value was written to the mailbox object (and we can easily confirm it's still valid):
[PS] C:\>(get-mailbox user1).IsValidTrue
But this demo is about validation FAILING; let's hack it so it'll fail!
[PS] C:\>$a = new-object System.DirectoryServices.DirectoryEntry "LDAP://$((get-mailbox user1).DistinguishedName)"[PS] C:\>$a.psbase.invokeset("mDBOverQuotaLimit",900*1024)[PS] C:\>$a.psbase.CommitChanges()[PS] C:\>Get-Mailbox user1 Name Alias ServerName ProhibitSendQuo ta---- ----- ---------- ---------------user1 user1 e12 900MBWARNING: Object e12dom.local/Users/user1 has been corrupted and it is in an inconsistent state. The following validation errors have been encountered:WARNING: The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '900MB', IssueWarningQuota: '2GB'.
[PS] C:\>$a = new-object System.DirectoryServices.DirectoryEntry "LDAP://$((get-mailbox user1).DistinguishedName)"[PS] C:\>$a.psbase.invokeset("mDBOverQuotaLimit",900*1024)[PS] C:\>$a.psbase.CommitChanges()[PS] C:\>Get-Mailbox user1
Name Alias ServerName ProhibitSendQuo ta---- ----- ---------- ---------------user1 user1 e12 900MBWARNING: Object e12dom.local/Users/user1 has been corrupted and it is in an inconsistent state. The following validation errors have been encountered:WARNING: The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '900MB', IssueWarningQuota: '2GB'.
Quick digression to talk about what we did here... we created a new LDAP DirectoryEntry object as $a. Then we updated one of the values (LDAP attribute mDBOverQuotaLimit) with the InvokeSet method. Finally, we saved the updated DirectoryEntry object with CommitChanges(). There are some useful references for how to use DirectoryEntry objects (including some explanation about psbase, etc) on the net. I blogged it here, pointing to BenP's post... that's probably the best place to start.
Anyway, back to the demo. We've now, evidently, succeeded in "hacking" the mailbox object into an invalid state. We got an error back telling us just what's wrong. And as a person, we can read the error, slap our forehead, and fix it up with a proper Set-mailbox operation that resets the proper quota relationship (note, you won't be able to make any other writes to this object through Set-Mailbox cmdlet until you fixup the Quota values).
But, what if you want to do it programmatically? You're in a script or whatever. Surely there has to be a way to check if an object is valid during the processing of a script? Well, of course there is. We've already used it just above... let's try it again now:
[PS] C:\>(Get-mailbox user1).IsValidWARNING: Object e12dom.local/Users/user1 has been corrupted and it is in an inconsistent state. The following validation errors have been encountered:WARNING: The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '900MB', IssueWarningQuota: '2GB'.False
And, since it's a boolean result, we can test it with if(), etc. Very powerful.
But we can go even one step further... what if I want to test if an object is valid before I even try to change the values? Great example of this one is now that I have an invalid object, I can't change any other properties on the object until I fix the invalid values (and make it valid again). So maybe my script should be super-smart and "test out" setting some value to see if it'll make the object valid before I try to write it back:
[PS] C:\>$b = get-mailbox user1WARNING: Object e12dom.local/Users/user1 has been corrupted and it is in an inconsistent state. The following validation errors have been encountered:WARNING: The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '900MB', IssueWarningQuota: '2GB'.[PS] C:\>$b.validate() | fl description Description : The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota : '900MB', IssueWarningQuota: '2GB'. [PS] C:\>$b.ProhibitSendQuota = "unlimited"[PS] C:\>$b.validate() | fl description <----- Note, nothing is returned here because the $b instance/object is valid now[PS] C:\>$b.isvalidTrue[PS] C:\>set-mailbox -Instance $b[PS] C:\>(get-mailbox user1).isvalidTrue
[PS] C:\>$b = get-mailbox user1WARNING: Object e12dom.local/Users/user1 has been corrupted and it is in an inconsistent state. The following validation errors have been encountered:WARNING: The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota: '900MB', IssueWarningQuota: '2GB'.[PS] C:\>$b.validate() | fl description
Description : The value of property 'ProhibitSendQuota' must be greater than or equal to that of property 'IssueWarningQuota'. ProhibitSendQuota : '900MB', IssueWarningQuota: '2GB'.
[PS] C:\>$b.ProhibitSendQuota = "unlimited"[PS] C:\>$b.validate() | fl description <----- Note, nothing is returned here because the $b instance/object is valid now[PS] C:\>$b.isvalidTrue[PS] C:\>set-mailbox -Instance $b[PS] C:\>(get-mailbox user1).isvalidTrue
So, you can see how this could be useful to "test the waters" in your scripts as you read/write/change objects. For instance, try the same sequence I used just above to set $b.ProhibitSendQuota back to "800kb" and you'll find it lets you do it... right up until you try to save the instance back with Set-Mailbox cmdlet. "Testing the water" with IsValid and Validate() can help you programmatically avoid this sort of error!
Wolfgang requested a reprint of the details from the "SMTP Demo" part of my TechEd talk last week. Here is (roughly) the transcript of the steps, and my comments interspersed...
[PS] C:\>$dl = Get-DistributionGroup SomeGroup
Ok, now we've got $dl variable loaded up with a distribution group object.
[PS] C:\>$dladdr = $dl.PrimarySmtpAddress.ToString()[PS] C:\>$dladdrSomeGroup@somedomain.com
And we've extracted the SMTP address from this group into the $dladdr variable.
[PS] C:\>$smtpclient = new-object System.Net.Mail.SmtpClient E12Server1, 587[PS] C:\>$smtpclient Host : E12Server1Port : 587UseDefaultCredentials : FalseCredentials : Timeout : 100000ServicePoint : System.Net.ServicePointDeliveryMethod : NetworkPickupDirectoryLocation : EnableSsl : FalseClientCertificates : {}
[PS] C:\>$smtpclient = new-object System.Net.Mail.SmtpClient E12Server1, 587[PS] C:\>$smtpclient
Host : E12Server1Port : 587UseDefaultCredentials : FalseCredentials : Timeout : 100000ServicePoint : System.Net.ServicePointDeliveryMethod : NetworkPickupDirectoryLocation : EnableSsl : FalseClientCertificates : {}
Created a DotNet object (thanks PowerShell!!) of System.Net.Mail.SmtpClient type. This sort of usability is a big part of why PowerShell is so wicked cool!
[PS] C:\>$cred = get-credential domain\exadmin[PS] C:\>$cred UserName Password-------- --------e12dom\exadmin System.Security.SecureString [PS] C:\>$smtpclient.Credentials = $cred.GetNetworkCredential()
[PS] C:\>$cred = get-credential domain\exadmin[PS] C:\>$cred
UserName Password-------- --------e12dom\exadmin System.Security.SecureString
[PS] C:\>$smtpclient.Credentials = $cred.GetNetworkCredential()
Save off a credential variable we can use in a moment (we'll need to authenticate to the connector since it's configured for basic auth and no anonymous relay).
[PS] C:\>$smtpclient.Send("exadmin@somedomain.com", $dladdr, "Welcome", "New list created.")
And... voila! Email is sent!
Now, challenge for the reader -- can you take these steps and build out a "mail-storm" script? I'd love to see your results on this idea posted here in the comments or at http://www.exchangeninjas.com/ wiki! Or any other cool variations or uses for this sort of functionality that you can come up with.
Thanks to all those die-hards who attended UNC309 session this morning at 8:30am, and to those who spent some time at the "Interactive Theatre" (ie - chalk-talk) session afterward for Q&A, much appreciated!
The session seemed to go fairly well, although even with placing a clock up on the presenter station to keep me on pace, I still ended up running a little long (and had to drop a couple of the more involved demos). 75 minutes just isn't enough time to demo the entirety of the greatness that is PowerShell+Exchange! At least not without talking EVEN FASTER than I already, naturally do!
So, in any case, sorry to the folks who I'd talked with before hand about the validation demo and using DirectoryEntry objects... I'll plan to post something up here on this topic in the near future since we didn't get to the demo in the breakout session.
After the primary session (PPT+Demos), we ended up back in the TLC area at "Green Threatre #5" for another 75 mins or so which we used directly for Q&A. Sorry to anyone who didn't find space in the theatre area - we ended up with quite a big crowd for this session, spilling out of the theatre, so I suspect it might have been hard to hear/see what was going on! Thanks to all for the great questions asked, and also to /n Software (Booth 1149 - check out their NetCmdlets product!) for the >_ stickers we handed out as freebies for asking questions during this session!
I'm going to step back and strategize what are the best couple of posts I can draw from the questions asked, demos skipped, etc. I'll try to post some good stuff here over the next week or so to wrap up. If you have any suggestions -- questions you didn't get to ask, things you thought of during lunch, whatever... please feel free to post here in the comments or send me an email!
The first Exchange breakout session this morning served as a bit of an "Exchange 2007 Keynote" talk, with our General Manager Terry Myerson doing the delivery. He covered E2k7's features and characteristics end-to-end in 75 mins, and had a couple of us help out with demos to dig in on a few areas (Unified Messaging, OWA, SysMgmt/PowerShell).
I covered the last of these three demos with a slimmed down version of my PowerShell opener demo that I'll be doing in my Wednesday Session. Now I'm super jazzed about the Wednesday talk; It's a shame it's not until Wednesday!
Here are the details for those of you who are here in Orlando:
UNC309: Microsoft Windows PowerShell Scripting for Microsoft Exchange Server 2007Wednesday, June 6thBright-and-early AM (ie - 8:30-9:45am)Location: S220 D
Some emails and comments on the previous post asking whether the outcome of the session will be posted afterward. I'll probably post some debrief from the session, and I know that the TechEd A/V staff are also planning to record the session. That said, I'm not sure what they'll do with the recording...? If it turns up online or somewhere I can link to, I'll be sure to do so.
Thanks everyone for your interest and hope you can make it on Wednesday (there's still time to get your plane ticket). :)
Wow, time sure flies. No posts since end of April; sorry about that! I'm (more or less) caught back up at work from these crazy few weeks (months?) just passed, and I'm even just returning from a week vacation... raring to go!
So what's next? Well... this is a short week, and then next week (June 4-8) is TECHED in Orlando!
I'll be down there to spend some time at the Exchange booth ("Technical Learning Center") and meet some of you folks, and also to present a breakout session and chalk talk on Exchange automation/scripting with PowerShell!
Here are the session details:
UNC309 - Microsoft Windows PowerShell Scripting for Microsoft Exchange Server 2007Track(s): Unified CommunicationsLevel: 300Speaker(s): Evan Dodds This session covers the new Windows PowerShell-based Exchange cmdline and scripting interface. Learn how to convert your multiple page Visual Basic and COM scripts to mere one-liners in Exchange 2007. We cover the basics of the management shell, as well as the underlying design and key concepts. Additionally, we go into more depth on how to build larger scripts that can be used to automate small, medium, as well as enterprise business scenarios.
UNC309 - Microsoft Windows PowerShell Scripting for Microsoft Exchange Server 2007Track(s): Unified CommunicationsLevel: 300Speaker(s): Evan Dodds
This is a great deck/presentation with great demos, so it should definitely be fun! Hope to see you there!
You might have noticed that after several weeks of very regular blog posts, they suddenly and abruptly stopped. I have an explanation: I'm super busy this month at work!
This should continue for a few more weeks. But expect to see more regular posts again for a while after that -- I've got a good queue of topics-to-be-written built up.
Rod got me the other day with a "5 things you don't know about me" post, so now it's my turn.
Five Things you (probably) don't know about me:
1) I'm president of my homeowner association. Before that I was secretary. Before that I was secretary of the association in our Charlotte neighborhood. Conclusion: I'm a glutton for punishment! (just kidding, they're a good bunch of homeowners, so I'm very lucky)
2) My left foot is wider than my right foot. So this means I always have to buy shoes 1/2 size too big and still I typically tear out the "sidewalls" in my left show before the soles wear out.
3) I am a media center (MCE) junkie, even though I don't really watch very much TV. I've had media center at the house since version 2004, and was on the pre-order queue to get the original Xbox extender kit. I recently bought an HDHomeRun so I can get Clear-QAM content into my MCE.
4) I am a Precinct Committee Officer (PCO) for my legislative precinct here in Seattle. I was on the election ballot last September, and I'm officially an "elected official" in the state of Washington.
5) My wife, Jodi, and I are having a baby boy this coming summer/July-- our first. He's already the cutest baby ever, and he's not even born yet.
On with the show: Vivek Sharma, Michael Palermiti, Nick Smith, and Jodi Dodds.
Vivek had originally posted about how to do some basic Exchange cmdlet access from C# -- several months before E2k7 released: Calling Exchange Cmdlets from .Net Code. However, by the time we released, some things had changed in the technique and Vivek's examples were out of date.
Recently, someone pointed me to the updated UE docs that explain how to do this sort of process: Using Exchange Management Shell Commands With Managed Code. Good stuff, everyone likes UE documentation that's officially out on Technet.
But then, best of all, the other day Nick posted his analysis of doing recipient management wrappers in C#: Managing Exchange 2007 Recipients with C#. This post is a great roll-up of all the stuff you need to know to get this working properly with Exchange 2007 RTM and a bunch of usage examples. Sweet!
I'll be presenting at Exchange Connections (Spring/April 2007) conference at 9:30am on Monday, April 2nd, during "Microsoft Day".
Here's the session abstract:
EMS05: Managing Exchange Server 2007: The New Exchange Management Console and ShellEvan DoddsImagine having a toolset that is flexible enough to easily deploy and administer a single Exchange server and yet powerful enough to completely automate those same actions for hundreds of servers. Yes, you heard right, Exchange Server 2007 will deliver a new intuitive GUI experience allowing you to quickly provision Exchange functionality while the new command-line experience will allow you to automate your world. This session is loaded with demonstrations showing off the new Exchange 2007 toolset and also highlights the underpinnings of this new revolutionary architecture which is built on the groundbreaking Windows PowerShell technology.
This is an Exchange 2007 "system management" session (covering both Console GUI and the Management Shell) so there'll be some good demos and views into the Exchange 2007 sysmgmt changes from both perspectives.
Added bonus: I'm going to be using a beta SP1 machine for my demos, so you'll get a chance to see some of the cool, new SP1 GUI too.
Hope to see you there!
In the last post, I walked through how to get mailboxes who met certain quota criteria (including inheriting these values from the mailbox database values).
Today, let's cover this from a different perspective: "who is over their quota".
Sure, it's possible to get this with a simple one-liner like:
Get-MailboxStatistics | fl DisplayName,StorageLimitStatus
But, in addition to that being a bit boring as an Exchange+PowerShell blog example, it's also not real-time. It only updates to represent "over quota" only perodically. If I want to check at 3pm based on current mailbox size at 3pm, I might need something more versatile. Plus, this is supposed to be an interesting Exchange+PowerShell scripting example, so we can't just do it the simple way. :)
Back to the problem... getting back the real-time size vs. quota information is a bit more involved than the last quota example, as it requires you to not only find the accurate mailbox quotas for the users, but be able to use the value to compare against the actual mailbox sizes for each mailbox. This is a bit more time consuming, since it requires a callout to each mailbox store to get the MailboxStatistics info.
Makes sense? Well, let's get to it then and hold onto your seatbelts for this one!
Let's first catalog what we need to do at a logical level:
Sounds easy. But at a glance it sounds like we're going to need to head back into hash-table land since we have two totally disparate objects we're trying to map against one another (see my "Bringing users and mailboxes together" post for the last time we hit this).
Here's a crazy-long one-liner that seems to do the trick (I've broken it out into many lines to make it easier to read):
1 Get-Mailbox -ResultSize Unlimited |2 % { if ($_UseDatabaseQuotaDefaults -eq $false)3 { New-Object psobject | 4 Add-Member -passthru noteproperty Name $_.Name |5 Add-Member -passthru noteproperty DistinguishedName $_.DistinguishedName |6 Add-Member -passthru noteproperty ExchangeGuid $_.ExchangeGuid |7 Add-Member -passthru noteproperty IssueWarningQuota $_.IssueWarningQuota 8 } else {9 New-Object psobject |10 Add-Member -passthru noteproperty Name $_.Name |11 Add-Member -passthru noteproperty DistinguishedName $_.DistinguishedName |12 Add-Member -passthru noteproperty ExchangeGuid $_.ExchangeGuid |13 Add-Member -passthru noteproperty IssueWarningQuota $($(Get-MailboxDatabase $_.Database).IssueWarningQuota)15 }16 } | % { $Quotas = @{} } 17 { $Quotas[$_.ExchangeGuid] = $_.IssueWarningQuota ; $_ } |18 % { Get-MailboxStatistics $_.DistinguishedName } |19 Where { $_.TotalItemSize -gt $Quotas[$_.MailboxGuid].Value }
Whew!
That "one-liner" dumps out mailbox statistics objects, filtered for only those mailboxes that are over their configured (effective) quota at the time they're evaluated. With not too much extra work (and following patterns demonstrated just above), this one-liner could easily be modified to output an object that had both the current size of the mailbox and the current, effective IssueWarningQuota of the mailbox... suitable for framing, er feeding into a report of some sort.
And how does it do it? Line by line analysis:
Now, this is certainly the longest contiguous one-liner I've created to date, and even at a glance it looks pretty inefficient (in terms of repetitive looping, if nothing else). I'm a bit curious if anyone has a more effective way of doing this same work. Feel free to weigh in at the comments section or send me some email if you have suggestions, and perhaps we can post about this again in the future.
During my MVP Summit session the other day, we talked about mailbox quotas and PowerShell a bit. We ran out of time to do a full demo of some useful oneliners around quotas, but I figure it'll be just as useful to go over the details here.
Mailbox size quotas in Exchange 2007 work just like they do in Exchange 2003 -- there are individual quota settings on each mailbox (IssueWarningQuota, ProhibitSendQuota, and ProhibitSendReceiveQuota) which are matched to the same settings available on the mailbox database holding the mailbox. There's a boolean "UseDatabaseQuotaDefaults" available on the mailbox to determine whether the mailbox database quota settings are used, or whether the mailbox quota settings are used. This is identical in both Exchange 2003 and in Exchange 2007.
For example: If I have set the mailbox database to 1gb "IssueWarningQuota" and a given mailbox to 2gb, it makes no difference that the mailbox is higher if the UseDatabaseQuotaDefaults value is set to $true on the mailbox.
Note: Setting the IssueWarningQuota (or any of the other quota values) on the mailbox does NOT toggle the UseDatabaseQuotaDefaults value on the mailbox.
So, why does this cause a problem? Well, it means you can't do a simple "show me all of the mailboxes with IssueWarningQuota set to >2gb" and get back valid results. Why not? Well, because you'd get back all of the mailboxes who have their quota set to >2gb directly on the mailbox... but you would NOT get back mailboxes who get their 2gb+ mailbox quota value by inheritance from the mailbox store value. Plus, you'd get back mailboxes where the value is set to >2gb directly on the mailbox, but which are configured to use the store value instead. Ouch!
Ok, so we need a slightly more complex one-liner to get back accurate results for this query.
Let's break it down. Here's what we need for the example above (>2gb IssueWarningQuota):
... which translates into a one-liner like this:
Get-Mailbox -ResultSize unlimited | where { ( ( $_.IssueWarningQuota -gt 2gb -and $_.UseDatabaseQuotaDefaults -eq $false) -or ( ( Get-MailboxDatabase $_.Database).IssueWarningQuota -gt 2gb -and $_.UseDatabaseQuotaDefaults -eq $true ) ) }
As usual, let's run through what we're doing here:
Pretty straight-forward logic... and I hope it's useful to you!
... over at EHLO team blog in this post. Bill's been working on this one for a month or two, tweaking the conversion logic to make it as effective as possible.
So if you are struggling with the manual steps I blogged about to convert your legacy LDAP EAP/AL/DDG filters into OPATH filter format for the object upgrade, you'll probably want to give this script a look!
Someone asked me the other day about how to get additional UPN suffixes listed in the UPN suffix dropdown in New Mailbox GUI wizard. My initial instinct was the obvious: Add some additional AD domains to your forest and they'll show up.
Of course, this is not what this person was really asking... they wanted to know how to add "alternative UPN" suffixes (for domains that do not exist in the AD forest).
Good news is that there is support for this scenario in Exchange 2007! All you have to do is add the alternative UPNs to your AD and they'll automatically show up in the Exchange 2007 GUI wizards where appropriate.
Here are the steps I tested to get this working:
1) Open up AD Domains and trusts (domain.msc)
2) Right click on the root note and select properties – you’ll get this:
3) Add/remove alternative suffixes here to your hearts desire. Then close out of the dialog.
4) Back inside the Exchange console GUI you will now see this in New Mailbox wizard:
My earlier post about the MVP summit this week didn't really talk much about the Exchange participation because I didn't have the numbers. Thanks to Melissa's post at EHLO, now we have the numbers!
Approximately every 12-18 months, Microsoft invites the MVPs to Redmond for the MVP Global Summit. This year over 1700 MVPs will be joining us March 12 -15th, making this by far the largest MVP Summit ever. It is also the largest event to be held on the Microsoft campus. The highlight will be the keynote delivered by Microsoft Chairman Bill Gates on the 13th. The rest of the week, MS product teams will host 533 sessions designed to not only connect the MVPs with one another but also with their respective product groups. The technical sessions are followed every evening with special dinners and social events. With MVPs registered to attend from 88 countries, this will also be the most globally diverse summit in our history. Over 80 Exchange MVPs from around the world will attend this year's big event!
Today I'll be meeting with several groups of MVPs to talk about Exchange 2007 and PowerShell in various "deep dive" sessions. Hope to see some of you there!