Win32::Exchange - Exchange 5.5 and Exchange 2000 functions


NAME

Win32::Exchange - Exchange 5.5 and Exchange 2000 functions


SYNOPSIS

  $provider = Win32::Exchange->new($info_store_server)) ||
      die " - error creating new object\n';

  #--- Exchange 5.5
  $mailbox = $provider->CreateMailbox($info_store_server,$mailbox_alias_name);
  if (!$mailbox) {
    die "Error creating mailbox\n";
  }
  print "Create successful\n";
  $mailbox->SetAttributes(\%Attributes);
  $mailbox->SetOwner("$domain\\$mailbox_alias_name");
  $mailbox->SetPerms(\@Perms);

  #--- Exchange 2000
  $mailbox = $provider->CreateMailbox($info_store_server,
                                           $pdc,
                                           $mailbox_alias_name,
                                           "domainname.com") ||
    print 'Mailbox create failed\n';
  $mailbox->Win32::Exchange::SetAttributes(\%Attributes) ||
        print 'Set Attributes failed\n';
  $mailbox->SetPerms(\@Perms);


DESCRIPTION

For now, this module creates and modifies Exchange 5.5 and 2000 mailboxes, has growing support for distribution lists, and some server API queries. Eventually it will do more, but for now, that's it. Kind of vague.. isn't it?

Win32::Exchange uses Win32::OLE exclusively (and technically is just a wrapper for the underlying OLE calls) so feel free to look at them, and make a suggestion or two.

All methods return 0 (or undef) on failure and 1 for success unless otherwise noted.

Functions

$provider = Win32::Exchange->new($server_name | $version);
$provider = Win32::Exchange::new($server_name | $version);
The new() class method starts a new instance of an Exchange provider object. It returns a reference to this object or undef if the creation fails.


GetVersion can return an acceptable version number for your Exchange server.

$provider->AddDLMembers($info_store_server,$dl_name,\@new_dl_members);#Exchange 5.5
$provider->AddDLMembers($dl_name,\@new_dl_members);#Exchange 2000
The only noticable difference between Exchange 5.5 and 2000 for this call are the parameters sent to it, and the provider version that should be sent. In either case, both functions check parameters and provider versions, so you shouldn't hit any snafus.

#Exchange 5.5
if ($provider->AddDLMembers($info_store_server,$dl_name,/@new_dl_members)) {
print "Added members successfully\n";
}

#Exchange 2000
if ($provider->AddDLMembers($dl_name,/@new_dl_members)) {
print "Added members successfully\n";
}



$provider->CreateMailbox($server_name,$mailbox_name,[$org,$ou]);#Exchange 5.5
$provider->CreateMailbox($server_name,$dc_name,$mailbox_name,$mail_domain,[$storage_group,$mailbox_store]);#Exchange 2000
$provider->CreateMailbox($server_name,$dc_name,$mailbox_name,$mail_domain,[$mailbox_store_dn]);#Exchange 2000 using dn
The CreateMailbox() function behaves differently depending on which type of provider it is passed (CDO.Person [E2K] or ADsNamespaces[5.5]). As well, the arguments for the Exchange 5.5 and 2000 functions are different.

When making Exchange 2000 Mailboxes, it should be noted that if you are using multiple "storage groups", or multiple "mailbox stores" on the Exchange Server, the presence of $storage_group and $mailbox_store are not optional (unless replaced by a valid mailbox store distinguished name as the fifth parameter), as the function will fail because it doesn't know where to put your new mailbox.

Lastly, if you plan on running LocateMailboxStore before the CreateMailbox, you can pass the distinguished name of the "located" mailbox store instead of the "storage group" and "mailbox store" names.


Win32::Exchange::ErrorCheck($hex_error_str,$error_num,$error_name);
The ErrorCheck() method takes an input value something similar to "0x00000000" where the string is the error you are expecting. $error_num and $error_name are output variables if an error other than the value you expected has happenned. All values are checked against Win32::OLE->LastError for string comparisons.

if (Win32::Exchange::ErrorCheck("0x80073020",$error_num,$error_name)) {
print "Mailbox does not exist. Good... Let's create it.\n";
} else {
if ($error_num eq "0x00000000")) {
print "OOPS.. The mailbox already exists!!!\n";
} else {
print "Unexpected error getting mailbox! ($error_num,$error_name)\n";
}
}


Win32::Exchange::GetDistinguishedName($server_name,$serach_object,$result);
The GetDistinguishedName function takes the Server name you want to query, an object type (or properly formatted ADODB filter, see below) and returns the distinguished name of the object on that server. This function is cross-compatible within Exchange versions, but not tested in Exchange 2000 (but would probably be useful in querying Active Directory Server attributes.

This function opens LDAP on the $server_name, applies the $filter, and then performs an ADODB search on the entire sub-tree. Once it finds values that meet your search criteria, it performs a pattern-match to see if $server_name is found anywhere in the distinguished name of search results. If it finds the server name in the results, it returns the first match.

if (Wi32::Exchange::GetDistinguishedName($info_store_server,'Home-MTA',$result)) {
print 'result = $result\n';
} else {
print 'Error Returning from GetDistinguishedName\n';
}

Currently this type of "pre-declaration" of a filter is available for 'Home-MTA' and 'Home-MDB' (case sensitive), but the function also accepts ADODB stype filters such as (objectClass=MTA) or (objectClass=MHS-Message-Store), which is the same as declaring 'Home-MTA' or 'Home-MDB'

Get familiar with the ADSVW program found in the ADSI 2.5 SDK. If you can find something helpful in ADSVW, chances are you can come up with an ADODB filter to meet your needs.

$provider->GetLDAPPath($server_name,$org,$ou);#Exchange 5.5
The GetLDAPPath method takes a server name (Exchange 5.5) and returns the Organization and OU for the Server.

The function has no corresponding Exchange 2000 function, but performs the same objective (of determinig where to eventually create a mailbox) as provided by LocateMailboxStore.

if ($provider->GetLDAPPath($info_store_server,$org,$ou)) {
print 'returned -> o='.$org.',ou='.$ou.'\n';
} else {
print 'Error Returning from GetLDAPPath\n';
}



$provider->GetMailbox($server_name,$mailbox_name,[$org,$ou]);
The GetMailbox() function is implemented for Exchange 5.5 and 2000.

The base object that is returned for Exchange 2000 mailboxes is the LDAP user object, and the object that is returned for Exchange 5.5 is the 5.5 mailbox object.

$mailbox = $provider->GetMailbox($info_store_server,$mailbox_alias_name,$org,$ou);
if ($mailbox) {
print "Mailbox exists\n";
}


$mailbox->GetOwner($nt_user,[$sid_type]);#Exchange 5.5
The GetOwner() method takes an Exchange 5.5 mailbox object like one provided by GetMailbox() or CreateMailbox() along with a variable to store the results in, and an optional "SID type", in order for the function to return a string type of your choosing.

This function defaults to ADS_SID_WINNT_PATH if no sid_type is specified. I don't believe there is an E2K equivelant.

$mailbox->GetOwner($nt_user,0x2) || print 'Error getting owner\n'; #0x2 == ADS_SID_SAM


Win32::Exchange::GetVersion($server_name,\%version);
Win32::Exchange->GetVersion($server_name,\%version);
This function returns a HASH of version information in the form of:


if (!Win32::Exchange->GetVersion($info_store_server,\%ver)) {
die "$rtn - Error returning from GetVersion\n";
}

print "version = $ver{ver}\n";
print "build = $ver{build}\n";
print "service pack = $ver{sp}\n";

Win32::Exchange->LocateMailboxStore($info_store_server,$storage_group,$mailbox_store,$store_name,[\@counts]);
Win32::Exchange::LocateMailboxStore($info_store_server,$storage_group,$mailbox_store,$store_name,[\@counts]);
This method allows you to query the Exchange 2000 server to determine:

if (Win32::Exchange::LocateMailboxStore($info_store_server,$storage_group,$mailbox_store,$store_name,\@counts)) {
print "storage group = $storage_group\n";
print "mailbox store = $mailbox_store\n";
print "located store distinguished name= $store_name\n";
print "$info_store_server\n";
print " Total:\n";
print " storage groups = @counts[0]\n";
print " mailbox stores = @counts[1]\n";
}

In this example, @counts is an optional parameter and should be populated providing the function is able to query the:
classes, and even if the function is unable to locate $storage_group and $mailbox_store, either because these variables are null strings and there is more than 1 mailbox store or storage group, or because they were not found as a valid storage group and mailbox store (useful for debugging purposes).
$mailbox->SetAttributes(\%attrs);Exchange 5.5
$ad_user_object->SetAttributes(\%attrs);Exchange 2000
The SetAttributes() method takes a specially formed hash structure, and is different depending on which version of Exchange you are trying to set attributes for:

Exchange 5.5:

$Exchange_Info{'Deliv-Cont-Length'}='6000';
$Exchange_Info{'Submission-Cont-Length'}='6000';
$Exchange_Info{'givenName'}="This";
$Exchange_Info{'sn'}="Isatest";
$Exchange_Info{'cn'}=$mailbox_full_name;
$Exchange_Info{'mail'}="$mailbox_alias_name\@manross.net";
$Exchange_Info{'rfc822Mailbox'}="$mailbox_alias_name\@manross.net";

push (@$Other_MBX,"RFAX:$Exchange_Info{'cn'}\@");
push (@$Other_MBX,"smtp:secondary\@$mail_domain");
push (@$Other_MBX,"smtp:tertiary\@$mail_domain");
$Exchange_Info{'otherMailbox'}=$Other_MBX;

Note:
See Also (Exchange 5.5):
Exchange 5.5 and ADSI (ADSI Exchange)

Exchange 2000:

push (@$proxies,'SMTP:'.$mailbox_alias_name.'@manross.net');
push (@$proxies,'smtp:secondary@manross.net');
push (@$proxies,'smtp:tertiary@manross.net');
$Attributes{"IMailRecipient"}{ProxyAddresses} = $proxies;
$Attributes{"IMailRecipient"}{IncomingLimit} = 6000;
$Attributes{"IMailRecipient"}{OutgoingLimit} = 6000;
$Attributes{"IMailboxStore"}{EnableStoreDefaults} = 0;
$Attributes{"IMailboxStore"}{StoreQuota} = 100;
$Attributes{"IMailboxStore"}{OverQuotaLimit} = 120;
$Attributes{"IMailboxStore"}{HardLimit} = 130;

See Also (Exchange 2000):
Interfaces and attributes


$mailbox->SetOwner($user);
The SetOwner() method takes a string reference (ex. "DOMAIN\USERNAME") and is currently only applicable for use in setting the owner on Exchange 5.5 mailboxes (the "Assoc-NT-Account" property of the mailbox). I don't believe there is an E2K equivelant.

$mailbox->SetOwner("DOMAIN\username") || print 'Error setting owner\n'


$mailbox->SetPerms(\@users);
The SetPerms() method takes an array reference of user or group names. This function works on Exchange 5.5 and Exchange 2000 mailboxes. The Exchange 2000 version requires Service Pack 1, with a hotfix, Service Pack 2, or later Service pack release, and is reccommended that the Exchange Client Tools be of the same Service Pack level as the server.

push (@PermsUsers,"$domain\\$mailbox_name");
push (@PermsUsers,"$domain\\Some Group");
$mailbox->SetPerms(\@PermsUsers) || print 'Error setting perms\n'


Module Options

Currently there are none, but I intend to make DEBUG a passable parameter as it is currently hard-coded to 1 (enabled).

EXAMPLES

use Win32::Exchange;
use Win32::AdminMisc;

$domain = Win32::DomainName();
$pdc = Win32::AdminMisc::GetPDC($domain);
$mailbox_alias_name='thisisatest';
$mailbox_full_name="This $mailbox_alias_name Isatest";
$info_store_server="HOMEEXCH2";

if (!Win32::Exchange->GetVersion($info_store_server,\%ver) ) {
  die "$rtn - Error returning into main from GetVersion\n";
}

print "version      = $ver{ver}\n";
print "build        = $ver{build}\n";
print "service pack = $ver{sp}\n";

if (!($provider = Win32::Exchange->new($ver{'ver'}))) {
  die "$rtn - Error returning into main from new ($Win32::Exchange::VERSION)\n";
}

if ($ver{ver} eq "5.5") {
  if (!Win32::Exchange::GetLDAPPath($info_store_server,$org,$ou)) {
    print "Error returning into main from GetLDAPPath\n";
    exit 1;
  }
  print "GetLDAPPath succeeded\n";
  if ($mailbox = $provider->GetMailbox($info_store_server,$mailbox_alias_name,$org,$ou)) {
    print "Mailbox already existed\n";
    if ($mailbox->SetOwner("$domain\\$mailbox_alias_name")) {
      print "SetOwner in GetMailbox worked!\n";
    }
  } else {
    $mailbox = $provider->CreateMailbox($info_store_server,$mailbox_alias_name,$org,$ou);
    if (!$mailbox) {
      die "error creating mailbox\n";
    }
    print "We created a mailbox!\n";
  }

  $Exchange_Info{'Deliv-Cont-Length'}='6000';
  $Exchange_Info{'Submission-Cont-Length'}='6000';
  $Exchange_Info{'givenName'}="This";
  $Exchange_Info{'sn'}="Isatest";
  $Exchange_Info{'cn'}=$mailbox_full_name;
  $Exchange_Info{'mail'}="$mailbox_alias_name\@insight.com";
  $Exchange_Info{'rfc822Mailbox'}="$mailbox_alias_name\@insight.com";

  $smtp="smtp:another_name_to_send_to\@$mail_domain";
  push (@$Other_MBX,$smtp);
  #be careful with 'otherMailbox'es..  You are deleting any addresses that may exist already
  #if you set them via 'otherMailbox' and don't get them first (you are now forewarned).
  $Exchange_Info{'otherMailbox'}=$Other_MBX;

  $mailbox->SetAttributes(\%Exchange_Info);
  $mailbox->SetOwner("$domain\\$mailbox_alias_name");

  my @PermsUsers;
  push (@PermsUsers,"$domain\\$mailbox_alias_name");
  push (@PermsUsers,"$domain\\Exchange Perm Users"); #Group that needs perms to the mailbox...

  $mailbox->SetPerms(\@PermsUsers);
  my @new_dl_members;
  push (@new_dl_members,$mailbox_alias_name);
  $provider->AddDLMembers($info_store_server,"newdltest",\@new_dl_members);

} elsif ($ver{ver} eq "6.0") {
  $storage_group = ""; #you'd need to define this if you had more than 1 storage group on 1 server.
  $mailbox_store = ""; #you'd need to define this if you had more than 1 mailbox store on 1 or more storage groups.
  if (Win32::Exchange::LocateMailboxStore($info_store_server,$storage_group,$mailbox_store,$store_name,\@counts)) {
    print "storage group = $storage_group\n";
    print "mailbox store = $mailbox_store\n";
    print "located store distinguished name= $store_name\n";
    print "$info_store_server\n";
    print "  Total:\n";
    print "    storage groups = @counts[0]\n";
    print "    mailbox stores = @counts[1]\n";
  }
  if ($mailbox = $provider->CreateMailbox($info_store_server,
                                              $pdc,
                                              $mailbox_alias_name,
                                              "insight.com",
                                              $store_name
                                             )
     ) {
    print "Mailbox create succeeded\n";
  } else {
    die "Failure is the option that you have selected!\n";
  }
  #be careful with proxy addresses..  You are deleting any addresses that may exist already
  #if you set them via ProxyAddresses (you are now forewarned).
  push (@$proxies,'SMTP:'.$mailbox_alias_name.'@manross.net');
  push (@$proxies,'smtp:secondary@manross.net');
  push (@$proxies,'smtp:primary@manross.net');
  push (@$proxies,'smtp:tertiary@manross.net');
  $Attributes{"IMailRecipient"}{ProxyAddresses} = $proxies;
  $Attributes{"IMailRecipient"}{IncomingLimit} = 6000;
  $Attributes{"IMailRecipient"}{OutgoingLimit} = 6000;
  $Attributes{"IMailboxStore"}{EnableStoreDefaults} = 0;
  $Attributes{"IMailboxStore"}{StoreQuota} = 100; #at 100KB starts getting warnings
  $Attributes{"IMailboxStore"}{OverQuotaLimit} = 120; #at 120KB can't send...
  $Attributes{"IMailboxStore"}{HardLimit} = 130; #at 130KB, can't do anything...
  if (!$mailbox->Win32::Exchange::SetAttributes(\%Attributes)) {
    die "Error setting 2K Attributes\n";
  } else {
    print "Set Attributes correctly\n";
  }
  my @PermsUsers;
  push (@PermsUsers,"$domain\\$mailbox_alias_name");
  push (@PermsUsers,"$domain\\Exchange Perm Users"); #Group that needs perms to the mailbox...

  $mailbox->SetPerms(\@PermsUsers);
  exit 1;
}

NOTES

Incompatabilities

Bugs and Limitations


SEE ALSO


AUTHORS

This module is based on an Exchange 5.5 mailbox creation script that has been traveling around the Internet and Activestate's mailing list archives for years.

I picked up on the thread that started my mailbox creation frenzy in 1999, and have been modifying the subroutines ever since.

With the advent of Exchange 2000, another script came to light, that tried to parse the Storage Group name and Mailbox Store names into an incredibly long string to allow for Exchange 2000 mailbox creation (circa 2001?).

I knew that the entire string had to be all parsed together somewhere in the Directory, and it was just a matter of finding it.

It was; LocateMailboxStore is an implementation of that idea.

As it turns out, there are a lot of tricks like LocateMailboxStore that have helped develop this module from a string concatenation mess into a lot of fancy searches for the complete ldap paths and distinguished names that power mailbox creation.

Most of the fancy searches were created by poking around in the objects themselves with ADSVW.EXE (an ADSI SDK tool), and then writing an ADODB search to return the right result set.

Thanks for taking the time to read all of this..

I'd like to extend thanks to the following:

Please send questions, comments or suggestions about this module to Steven Manross <steven@manross.net>.


VERSION

Version 0.0.0.031 October 4, 2002

Copyright

Microsoft, Active Directory, ADSI, Windows, Windows NT, MSDN, and Exchange are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.


 Win32::Exchange - Microsoft Exchange related functions