Archive for the ‘ Identity Management ’ Category

Permanently deleting Office 365 accounts from the Recycle Bin

After long pause in my posts, I want to return with this simple Office 365 script I found to be useful.

Since introduction of soft-delete functionality the “Microsoft Online Services Module for Windows PowerShell” was updated with new switch. Namely –RemoveFromRecycleBin switch was added to Remove-MsolUser cmdlet.

Soft-delete is a great failsafe feature allowing an administrator some time to reconcile accidental deletes. Let’s say you’ve accidently deleted or moved out of Dirsync’s scope AD user; your best friend DirSync faithfully synchronized your change (delete) into the office 365. Shortly thereafter, your favorite user calls you and reports that his mailbox is no longer there. Oops! Good news is that you can restore the account and move on to your next cup of coffee.

However, while you are testing and setting things up, soft-delete can be a little bit of an annoyance. When you really really want to kill those test accounts and recreate them again (and again). Or whatever the reason is, but you want those accounts gone for good. DirSync will NOT help you there, all deletes form current version of DirSync are “soft-deletes” therefore you have to connect with Powershell and remove your deleted accounts from the recycle bin. When you are dealing with more than handful of those deletes, you will probably want to have some automation to the process, unless you are enjoying typing too much.

I want to add this EXPLICIT DISCLAIMER to the post:
Neither the author of this script and article nor his employer, are responsible for any action(s), operation(s) or consequences of those action(s) and/or operation(s) or any loss of data, productivity, profits or other kind of  event(s). By using it you are taking full responsibility for whatever this script is about to do with your data, and all consequences of those modification. After all, we are talking about killing perfectly good user account. Please think twice before just running this script.
Thank you!

<#

. [COPYRIGHT]
. © 2011-2012 Dmitry Kazantsev, Microsoft. All rights reserved.
.
. [DISCLAIMER]
. This sample script is not supported under any Microsoft standard support
. program or service. The sample scripts are provided AS IS without warranty of
. any kind. Microsoft disclaims all implied warranties including, without
. limitation, any implied warranties of merchantability or of fitness for a
. particular purpose. The entire risk arising out of the use or performance of
. the sample scripts and documentation remains with you. In no event shall
. Microsoft, its authors, or anyone else involved in the creation, production,
. or delivery of the scripts be liable for any damages whatsoever (including,
. without limitation, damages for loss of business profits, business
. interruption, loss of business information, or other pecuniary loss) arising
. out of the use of or inability to use the sample scripts or documentation,
. even if Microsoft has been advised of the possibility of such damages.
#>


Import-Module MSOnline;
Write-Host ("Collecting admin credential");
$cred = Get-Credential;


Write-Host ("Connecting to your Office 365 tenent");
Connect-MsolService -Credential $cred;



Write-Host ("Enumerating users in the Recycle Bin");
$users = Get-MsolUser -ReturnDeletedUsers -All;


Write-Host ("Total number of accounts found: " + $users.Count);
[int]$index = 1;
[System.String]$message = "You are permanently deleting " + $users.Count + " user(s). Do you want to proceed? [Y] or [N]";
$responce = Read-Host -Prompt $message;


if ([System.String]::Equals("Y", $responce, [System.StringComparison]::OrdinalIgnoreCase))
{
Write-Host ("License to kill has been granted...")
foreach ($user in $users)
{
Write-Host ("Removing user " + $index + " of " + $users.Count + " | with UPN:'" + $user.UserPrincipalName + "' from the Recycle Bin");
#
# TODO: You will need to uncomment following line, to confirm that that you are dead-certain about killing all those innocent user accounts
#
#Remove-MsolUser –RemoveFromRecycleBin –UserPrincipalName $user.UserPrincipalName -Force -Verbose;
$index++;
}
}
else
{
Write-Host ("Ufff... I thought that you were kidding");
}

Once again. Dont hurt yourself accidently.

Dude, what is my UPN… or wait… is it an email address?

On several several several occasions I have been asked by confused customers about UPN renames and email address changes during initial Office 365 ADFS farm setup and directory clean-up effort. Today’s question produced a little story, which I wanted to share in attempt to demystify email and UPN correlation when ADFS is in the mix

Let’s start with defining constants

User Principal Name (UPN) is a single-value attribute [in AD attribute name is ‘userPrincipalName’] and to confuse you even further in ADUC UI is it not presented as a single-field attribute, but rather split into two parts with a drop-down to select the ‘right’ part, which is your AD domain name and possible additional suffixes, and string value that you can enter manually on the left. ADUC often “suggests” to use value of sAMAccountName that is listed in ADUC UI as “pre-Windows 2000 logon name”, yet you are free to type whatever you wish to type or leave that field/attribute blank all together.

Proxy Addresses is a multi-valued attribute [AD attribute name is ‘proxyAddresses’] . It is an extended attribute, which you will only have if you have extended AD schema while installing Exchange Server. Some version of ADUC UI can display it, some can’t, however it’s easy enough to use PowerShell or ADSI editor to see/edit the value of that and other attributes. Of course, you also have an option to use Exchange 2007/2010 console, which exposes many Exchange-related attributes as well.

Note
that proxyAddresses attribute contain more than just SMTP addresses and that formatting (including capitalization) is important for values of that attribute. For example capitalized prefix 'SMTP:' signifies that address is “primary”, and non-capitalize value 'smtp:' signifies that address is an additional address. You can only have one primary address, and as many secondary addresses as you wish. Since it is an Exchange attribute, Exchange Management Console is taking care of uniqueness verification when you set values for that attribute with the console. However, if and when you will modify that attribute directly in AD (outside of Exchange Management Console) you will need to ensure that uniqueness and formatting of that attribute is preserved. Failure to do so will likely to result in NDRs. Active Directory by itself will be happy to accept any values to store in the attribute, since it defined simply as multi-valued string. In other words, AD will not save you from being negligent.

Confusion of those attributes (along with ‘mail‘, ‘targetAddress‘ and sometimes mail alias aka ‘mailNickName‘ and ‘sAMAccountName‘) is relatively common occurrence, as the value of those attributes is often (but not necessarily) the same

UPN and Office 365 Federation

Organizations need to update their user’s UPN to be publicly “routable” in order to establish federated partnership with Office 365. There is no “adding” of a secondary value possible, since userPrinciplaName is a single-value attribute [attribute definition].

Simple PowerShell script can replace user’s UPNs in a few minutes, if/when desired.

Note:

Some of your existing applications could use UPN to tie-in to a user account in AD, which is a theoretical possibility; therefore changing UPN _could_ break that/those application(s), and therefore you should perform some analysis/testing of internal infrastructure to see whether UPN rename will work with all of your key applications without re-configuration of dependencies. If no dependencies identified the rename of UPN is fast and easy operation to perform. Quick Bing search will provide you with more PowerShell scripts on your hands that than you care to have. 🙂

Proxy Addresses

Aforementioned “ProxyAddresses” attribute could come into play if and when old UPN value was ALSO one of the routable email addresses. That is where confusion most often occurs.

Example

Bear with me, since it is a long story:

  • Let’s say that I am working for Contoso where my UPN is dmitry@contoso.com AND my primary email address is also happens to have the value of dmitry@contoso.com
  • At one fine day Contoso was purchased by Fabrikam (my heart goes out for those laid-off Contoso employees) and Fabrikam’s IT decided to go to the cloud and got themselves an Office 365 with ADFS deal
  • To “normalize” their joined AD infrastructure they’ve added @fabrikam.com” as one of the available suffixes to an existing Contoso’s AD and now they want to rename every account in Contoso’s AD to have UPN suffix @fabrikam.com. So they performed the actual rename of existing UPNs with the PowerShell script…
  • The Fabrikam’s half of the company needs no change – they will likely to have their UPN and their primary SMTP match. It will look something like username@fabrikam.com
  • The ex-Contoso half is not so lucky. I have my UPN renamed from dmitry@contoso.com to dmitry@fabrikam.com however my primary SMTP remained to be dmitry@contoso.com
  • Technically, there is nothing wrong with that scenario. As an ex-Contoso employee I still can login into my dmitry@contoso.com mailbox with my dmitry@fabrikam.com UPN; It just so happened that UPN value is _formatted_ the same way as an email address value; it doesn’t have to be equal to my email address. In fact, by itself UPN have nothing to do with my email at all. Yet, I have to use it to login into my new Office 365 mailbox via ADFS. Will I be confused about it? You bet! I’ll be screaming at those IT people who can’t stop changing things on me. Damn you IT department, why can’t you leave me alone?
  • So, naturally, Fabrikam’s IT will want to rename my ex-Contoso employee primary SMTPs to match to my new Fabrikam UPN value. Therefore my mailbox will become dmitry@fabrikam.com; Generally IT folks will provide an explanation like “you will be logging-in into Office 365 mailbox with your email address” which technically is a complete lie, since users are logging-in with their UPN value which just happen to be the same as their primary SMTP address/email.
  • Now we have arrived to “adding a value” part of the story:
  • Since I was giving my Contoso business cards to every person I’ve met at every cocktail party for the last 20 years with my email address being dmitry@contoso.com, this unfortunate acquisition by Fabrikam and consequential rename of my login information (UPN) to something else besides my old email address value, just so I can remember how to login into the new system… yes, all that jumbo-mambo IT folks was trying to explain to me — is simply not gonna work for me. I can’t afford to lose all those contacts and future invites to cocktail parties, nor you can expect me to remember how to login into this new cloud thing. Not after the cocktail party, anyways…
  • So I’ll be demanding that Fabrikam’s IT do something about incoming messages to my existing dmitry@contoso.com email address. Since they have renamed my primary SMTP to be dmitry@fabrikam.com, all new outgoing emails will be seen as “from” that address, however they [The IT folks]will need to add my ‘dmitry@contoso.com‘ email address as a secondary smtp: address into the proxyAddresses collection. By saving my old email address into proxyAddresses they will preserve routablity. That will allow me to continue to drop my old business cards with my old email address into those jars in the hotel lobbies with hopes to win an exuberant amount of points and have an awesome vacation somewhere where I don’t need to login into email account at all. Problem solved.

I hope that make some sense

Career Changes

After many years of working with Microsoft as a contractor (vendor) I have finally caved-in. I am very excited to announce that I’ve accepted a full time position with Microsoft Corporation. As of January 4, 2011 I have joined Office 365 Deployment Team. The hiring process in Microsoft deserves a separate blog entry – it’s a long winding trail (more like an obstacle course) where coming out on the other end is its own reward. The lesson that I’ve learned (over and over again) is: patience is a virtue.

So… what does is mean for this blog. Well, being inside of the “mother-ship” and being part of the “mother-ship” is a little deferent from each other. As a Microsoft employee I represent Microsoft’s official “party line”, therefore some topics could be covered in slightly deferent manner. Naturally, I am expecting my perspective(s) to change a little bit, as I will become more exposed to the inner-workings of the “machine”. Nevertheless, I don’t expect any drastic changes; after all I was hired for who I am. Wish me luck, it should be a cool ride.

Archiving Exchange Mailboxes to .PST files

Update:

Rather than deleting this old entry, I wanted to update it with a simple link to “Microsoft Exchange PST Capture 2.0”: http://aka.ms/pstcapture2

It might help more than an original technique described below

PST files

Somehow I always manage to hit problems that are poorly documented and I have to spend hours of installing/re-installing software, adding features and patches… and reading posts that are somewhat relevant but not 100% covering the issue.

So my task of de jure was to assist our IT Admins to off-load user mailboxes form local Exchange server to PSF files for archiving purposes. I’ve volunteered for it thinking that – hey, what can be difficult about that. So after several hours of trying to figure out what is that I need here is definitive path that worked for me.

  • Install “Windows 7 x86” (32 bit version is important detail) [I’ve virtualized this machine]
  • Install Outlook 2010 x86 [You’ll need MAPI protocol from it… ]
  • W7 comes with PowerShell (just make sure that you have it installed/enabled)
  • Install Remote Server Administration Tools for Windows 7
    [Exchange Management tools (which we’ll install later) will need it. It is not really documented all that well or throws an obvious error that tells you – hey you are missing a pre-requisite. That would be easy… Exchange installer goes through the install verifies something and tell you that everything is fine… and shortly thereafter fails with odd error “An error occurred. The error code was 3221684226. The message was The system cannot find the file specified ” Gotta love it!)
  • Enable Active Directory Domain Services Tools (that is an Add Remove Programs\Windows Features)
  • To avoid error “The log file directory ‘C:\Program Files\Microsoft\Exchange Server\Logging\MigrationLogs’ does not exist” you need to:
    a) Create that folder AND (if you still having the error)
    b) Create a registry key HKEY_LOCAL_MACHINE\Software\Microsoft\Exchange\Exchange Migration
  • Add your admin account to “Enterprise Adminis” group and to “Exchange Organization Administrators” group
  • Now you can download and install the “Microsoft Exchange Server 2007 Management Tools (32-Bit)” You’ll need to identify your version of Exchange with currently installed SP by looking at “About” dialog box of your exchange server and then looking here: http://support.microsoft.com/kb/158530
  • Now you are ready to off-load your users into PSTs with TWO PowerShell consecutive commands
    1) Firstly you need to grant yourself right to access the mailbox to avoid this misleading error:
    Export-Mailbox : Error was found for because: Error occurred in the step: Movingmessages. Failed to copy messages to the destination mailbox store with error: MAPI or an unspecified service provider. ID no: 00000000-0000-00000000, error code: -1056749164
    To grant yourself right you’ll need to execute following cmdlet:
    Add-mailboxpermission -identity -accessrights fullaccess -user
    2) And lastly you can execute this command:
    Export-Mailbox –Identity -PSTFolderPath
    Note: Make sure that your path for your destination PSF is pre-created…

I think this pretty much documents several several hours of my life.

Happy codding

AD account management and BPOS

Living with BPOS

Few weeks ago Schakra cut the umbilical cord of local Exchange server and fully moved into the cloud for all Exchange needs. The “cut” was rather anticlimactic. No email outage, no interruptions, no user screams… it was boring and trivial event. So what we have lacked in the “excitement” of migration, we have acquired on account management front. You would think that having less systems to manage will simplify life of an AD admin… not so fast. Once you’ve got accustom to Exchange management console you become dependent on those [pseudo-hidden] attributes such as msExchHideFromAddressLists, proxyAddresses, etc. With or without local Exchange box my admins demanded functionality to hide accounts, add additional addresses to user mailboxes, etc, etc. My first reaction was: “here is the list of attributes and ADSI Edit – go at it…” then I’ve started an investigation on how to “gracefully” expose the UI for those hidden treasures. Options were not so obvious, as one hoped: ADSI (or other LDAP) editor, upgrading AD to 2008 functional level and use of ADUC in that mode or the scripting. Of course having full-blown Identity Management system would help too.
Demographic data of BPOS user-base suggests that many of its users would lack the fully established Identity Management system, or the latest functional-level of AD, or the tools/comfort-level to edit raw AD data. So I’ve decided to attempt to automate a simple account creation process for all of those who are in post-migration stage of Office 365 deployment.

Choosing PowerShell

At first I was ready to write a command-line tool that would simply create a user account with pre-populated set of must-have attributes. PowerShell (being an interactive version of C# for the masses) is well situated for all sorts of administrative tasks; however my goal of providing familiar environment to my system admins will not be accomplished by writing a command-line utility. Having UI is a good thing for the comfort level. So I’ve decided to wrap a windows-forms application in PowerShell format. “Why oh why”, you would ask? PowerShell is command line environment after all. Writing windows form application defeats the purpose of the shell. Well… the answer is – not really. Even though it is not traditional approach to use the shell, having windows-forms should provide Windows admin with familiarity and therefore convenience. Yet, having PowerShell-based application provides ease of source maintenance and therefore gives end-user an ability to tweak application to his/her liking. So behold the PowerShell script/app to create your user accounts in AD.

Goals

Here are the goals that I’ve set for myself while writing this app

  • Create form-based application in PowerShell
  • Ensure that all ‘business logic’ for account creation is automated (OU location, group membership, etc.)
  • Ensure that there is as little typing as possible (Auto-concatenated display name, auto created alias, etc.)
  • To ease a pain for PowerShell adopters with creation of form-based application (One can re-use parts of my app to write something else)
  • Have no dependencies on AD-management snap-in (Use of ‘plain-Jane’ PowerShell would allow user to run it as-is without an additional dependencies and installs)

Non-Goals

Things that this ‘app’ is not…

  • Fool-Proofing (Although there is no end for error-proofing, I had to keep in mind that this is a ‘script’. PowerShell would throw plenty of nasty errors on the screen is something goes wrong.)
  • Full account management console (This is an app to create a user in AD, not edit a user or delete a user. I am sure it is more than possible to extend this to the full-blown 3rd party account management console… but why)

Business Logic

In my application I’ve used internal to Schakra assumptions. My system admin team is having set of internal rules for our internal account creation such as user location in AD is determined according to user’s employment type, account name and display names are calculated with particular formulas, group membership is pre-determined according to user’s employment status, etc. etc. All those things are easily modifiable and adjustable. Feel free to extend the application and adopt it for your internal use.

Source

Download script/app from Skydrive

CLS
Write-Host("Loading .NET assemblies...");

[System.Reflection.Assembly]::Load("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") | out-null
[System.Reflection.Assembly]::Load("System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") | out-null

function CreateDisplayName()
{
[System.String]$formattedDisplayName = [System.String]::Empty;
$formattedDisplayName = [System.String]::Format("{0} {1}", $textBoxFirst.Text.Trim(), $textBoxLast.Text.Trim());
$textBoxDisplayName.Text = $formattedDisplayName;
}

function CreateAlias()
{
if (0 -ne $textBoxLast.Text.Length)
{
[System.String]$formattedAlias = [System.String]::Empty;
$formattedAlias = [System.String]::Format("{0}{1}", $textBoxFirst.Text.Trim(), $textBoxLast.Text.Trim().Substring(0, 1));
$textBoxAlias.Text = $formattedAlias;
}
}

function Get-User
{
param
(
[Parameter( Mandatory=$true, Position=0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("alias")] [String] $UserName,

[Parameter( Mandatory=$true, Position=2, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("attribute")] [String] $AttributeName
)

[System.String] $filterSyntax = "(&(objectCategory=person)(objectClass=user)(" + $attributeName + "=" + $userName + "))";

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]"");
$searcher.filter = $filterSyntax;
$searcher.CacheResults = $true;
$searcher.SearchScope = "Subtree";
$searcher.PageSize = 1000;
[System.DirectoryServices.SearchResult] $result = $searcher.FindOne();

if ($null -ne $result)
{
[System.DirectoryServices.DirectoryEntry]$entry = $result.GetDirectoryEntry()
Write-Host("Manager DN: " + $result.Path.Substring(7).Trim());
return $result;
}

return $null;
}

function ConvertADSLargeInteger([object] $adsLargeInteger)
{
$highPart = $adsLargeInteger.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)
$lowPart = $adsLargeInteger.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)

$bytes = [System.BitConverter]::GetBytes($highPart)
$tmp = [System.Byte[]]@(0,0,0,0,0,0,0,0)
[System.Array]::Copy($bytes, 0, $tmp, 4, 4)
$highPart = [System.BitConverter]::ToInt64($tmp, 0)

$bytes = [System.BitConverter]::GetBytes($lowPart)
$lowPart = [System.BitConverter]::ToUInt32($bytes, 0)

return $lowPart + $highPart
}

function UpdateFields()
{
CreateDisplayName;
CreateAlias;
}

function Form-OnSubmit
{
$result = Form-Validate;
if ($true -eq $result)
{
Form-Submit;
}
}

function Manager-OnClick
{
$value = $textBoxManager.Text.Trim();

if ([System.String]::IsNullOrEmpty($value))
{
Write-Host("Manager's name is not specified");
return;
}

$manager = Get-User -UserName $value -AttributeName "sAMAccountName";

if ($null -eq $manager)
{
$labelManager.ForeColor = [System.Drawing.Color]::Black;
Write-Host("Could not resolve manager's account with specified sAMAccountName: " + $value);
}
else
{
$labelManager.ForeColor = [System.Drawing.Color]::DarkGreen;
return;
}

$manager = Get-User -UserName $value -AttributeName "displayName";

if ($null -eq $manager)
{
$labelManager.ForeColor = [System.Drawing.Color]::Black;
Write-Host("Could not resolve manager's account with specified displayName: " + $value);
}
else
{
$labelManager.ForeColor = [System.Drawing.Color]::DarkGreen;
return;
}
}

function Form-Submit
{
[System.String]$methodName = 'Form-Submit';
Write-Host("Entering '" + $methodName + "' function");

[System.String]$dominaController = "LDAP://si-dc:389/dc=schakra,dc=int";
Write-Host("Connecting to " + $dominaController);

[ADSI]$domain = [ADSI] $dominaController
[System.String]$destinationOU = [System.String]::Empty;
[System.String]$country = $comboBoxLocation.SelectedItem.ToString();
[System.String]$employeeType = $comboBoxEmployeeType.SelectedItem.ToString()

Write-Host ("Country: " + $country);
Write-Host ("Employee Type: " + $employeeType);

[System.Text.StringBuilder]$builder = New-Object System.Text.StringBuilder;

$builder.Append("LDAP://OU=");

if ($employeeType -eq "Full Time Employee" -or $employeeType -eq "Intern")
{
$builder.Append("Users,");
}
else
{
$builder.Append("Vendors,");
}

if ($country -eq "US")
{
$builder.Append("OU=Schakra-US,");
}
else
{
$builder.Append("OU=Schakra-India,");
}

$builder.Append("DC=schakra,DC=int");
Write-Host ("Destination OU: "+ $builder.ToString());
[ADSI]$usersOU = [ADSI] $builder.ToString();

[System.String]$mailSuffix = "@schakra.com";
[System.String]$domainName = "@schakra.local";
Write-Host ("CN: " + "cn=" + $textBoxDisplayName.Text);

[System.DirectoryServices.DirectoryEntry]$user = $usersOU.Create("user","cn=" + $textBoxDisplayName.Text);
Write-Host($user.GetType());

$user.put("givenName", $textBoxFirst.Text);
$user.put("sn", $textBoxLast.Text);
$user.put("title", $textBoxTitle.Text);
$user.put("mail", $textBoxAlias.Text + $mailSuffix);
$user.put("targetAddress", $textBoxAlias.Text + $mailSuffix);
$user.put("displayName", $textBoxDisplayName.Text);
$user.put("sAMAccountName", $textBoxAlias.Text);
$user.put("userPrincipalName", $textBoxAlias.Text + $domainName);
$user.put("description", $employeeType);
$user.put("employeeType", $employeeType);

$manager = Get-User -UserName $textBoxManager.Text -AttributeName "sAMAccountName";
if ($null -eq $manager)
{
$manager = Get-User -UserName $textBoxManager.Text -AttributeName "displayName";
}

if ($null -ne $manager)
{
$user.put("manager", $manager.Path.Substring(7));
}

if ($country -eq "US")
{
$user.put("c","US");
$user.put("co","United States");
$user.put("countryCode", 840);
$user.put("physicalDeliveryOfficeName", "US - Redmond");
}
else
{
$user.put("c","IN");
$user.put("co","India");
$user.put("countryCode", 356);
$user.put("physicalDeliveryOfficeName", "India - Hyderabad");

}

if ($checkBoxHideFromGal.Checked -eq $True)
{
Write-Host("Hiding from GAL");
$user.put("msExchHideFromAddressLists", $True);
}

if ($checkBoxAllowVpn.Checked -eq $True)
{
Write-Host("Allowing VPN access");
$user.put("msNPAllowDialin", $True)
}

$user.SetInfo()

if ($checkBoxEnabled.Checked -eq $True)
{
$user.psbase.Invoke("SetPassword","YourPasswordValue");
$user.psbase.InvokeSet('Accountdisabled',$false);

if ($checkBoxChangePasswordOnNextLogin.Checked -eq $True)
{
Write-Host("Resseting password on the next logon");
$user.pwdLastSet = 0;
}

$user.psbase.CommitChanges();
}

# Adding user to his/her 'default' distribution group
[System.Text.StringBuilder]$distributionGroupuilder = New-Object System.Text.StringBuilder;
$distributionGroupuilder.Append("LDAP://");

if ($country -eq "US")
{
$distributionGroupuilder.Append("CN=US-");
}
else
{
$distributionGroupuilder.Append("CN=India-");
}

switch ($employeeType)
{
"Intern"
{
$distributionGroupuilder.Append("Interns,");
}

"Full Time Employee"
{
$distributionGroupuilder.Append("FTE,");
}

"Contractor/Vendor"
{
$distributionGroupuilder.Append("Vendors,");
}
}

$distributionGroupuilder.Append("OU=Distribution,OU=Groups,DC=schakra,DC=int");

[System.String]$distributionGroupPath = $distributionGroupuilder.ToString();

Write-Host("Adding user to the group: " + $distributionGroupPath);

$distributionGroup = [ADSI]$distributionGroupPath;
$distributionGroup.Add("LDAP://" + $user.distinguishedName);

Form-Exit;
Write-Host("Exiting '" + $methodName + "' function");
}

function Form-Exit
{
[System.String]$methodName = 'Form-Exit';

Write-Host("Entering '" + $methodName + "' function");
Write-Host("Closing Windows Form...");
$form.Close() | out-null
Write-Host("Exiting '" + $methodName + "' function");
}

function Form-Build
{
[System.String]$methodName = 'Form-Build';
Write-Host("Entering '" + $methodName + "' function");

[System.Windows.Forms.Button]$buttonCancel = New-Object System.Windows.Forms.Button
[System.Windows.Forms.Button]$buttonOK = New-Object System.Windows.Forms.Button
[System.Windows.Forms.Button]$buttonManager = New-Object System.Windows.Forms.Button;

[System.Windows.Forms.GroupBox]$distributionGroupBoxOther = New-Object System.Windows.Forms.GroupBox;

[System.Windows.Forms.CheckBox]$checkBoxAllowVpn = New-Object System.Windows.Forms.CheckBox;
[System.Windows.Forms.CheckBox]$checkBoxChangePasswordOnNextLogin = New-Object System.Windows.Forms.CheckBox;
[System.Windows.Forms.CheckBox]$checkBoxEnabled = New-Object System.Windows.Forms.CheckBox;
[System.Windows.Forms.CheckBox]$checkBoxHideFromGal = New-Object System.Windows.Forms.CheckBox;

[System.Windows.Forms.ComboBox]$comboBoxLocation = New-Object System.Windows.Forms.ComboBox;
[System.Windows.Forms.ComboBox]$comboBoxEmployeeType = New-Object System.Windows.Forms.ComboBox;
[System.Windows.Forms.ComboBox]$comboBoxDepartment = New-Object System.Windows.Forms.ComboBox;

[System.Windows.Forms.Label]$labelFirst = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelMiddleInitial = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelLast = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelDisplayName = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelAlias = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelTitle = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelDepartment = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelLocation = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelEmployeeType = New-Object System.Windows.Forms.Label;
[System.Windows.Forms.Label]$labelManager = New-Object System.Windows.Forms.Label;

[System.Windows.Forms.TextBox]$textBoxFirst = New-Object System.Windows.Forms.TextBox;
[System.Windows.Forms.TextBox]$textBoxMiddleInitial = New-Object System.Windows.Forms.TextBox;
[System.Windows.Forms.TextBox]$textBoxLast = New-Object System.Windows.Forms.TextBox;
[System.Windows.Forms.TextBox]$textBoxDisplayName = New-Object System.Windows.Forms.TextBox;
[System.Windows.Forms.TextBox]$textBoxAlias = New-Object System.Windows.Forms.TextBox;
[System.Windows.Forms.TextBox]$textBoxTitle = New-Object System.Windows.Forms.TextBox;
[System.Windows.Forms.TextBox]$textBoxManager = New-Object System.Windows.Forms.TextBox;

$distributionGroupBoxOther.SuspendLayout | out-null

##
## labelFirst
##
$labelFirst.AutoSize = $true;
[System.Drawing.Point]$labelFirst.Location = New-Object System.Drawing.Point(11, 7);
$labelFirst.Name = "labelFirst";
[System.Drawing.Size]$labelFirst.Size = New-Object System.Drawing.Size(57, 13);
$labelFirst.Text = "First Name";

##
## textBoxFirst
##
[System.Drawing.Point]$textBoxFirst.Location = New-Object System.Drawing.Point(11, 23);
$textBoxFirst.Name = "textBoxFirst";
[System.Drawing.Size]$textBoxFirst.Size = New-Object System.Drawing.Size(122, 20);
$textBoxFirst.TabIndex = 1;
$textBoxFirst.add_TextChanged({UpdateFields})

##
## labelMiddleInitial
##
$labelMiddleInitial.AutoSize = $true;
[System.Drawing.Point]$labelMiddleInitial.Location = New-Object System.Drawing.Point(137, 7);
$labelMiddleInitial.Name = "labelMiddleInitial";
[System.Drawing.Size]$labelMiddleInitial.Size = New-Object System.Drawing.Size(25, 13);
$labelMiddleInitial.Text = "M.I."

##
## textBoxMiddleInitial
##
[System.Drawing.Point]$textBoxMiddleInitial.Location = New-Object System.Drawing.Point(138, 23);
$textBoxMiddleInitial.Name = "textBoxMiddleInitial";
[System.Drawing.Size]$textBoxMiddleInitial.Size = New-Object System.Drawing.Size(42, 20);
$textBoxMiddleInitial.TabIndex = 2;

##
## labelLast
##
$labelLast.AutoSize = $true;
[System.Drawing.Point]$labelLast.Location = New-Object System.Drawing.Point(183, 7);
$labelLast.Name = "labelLast";
[System.Drawing.Size]$labelLast.Size = New-Object System.Drawing.Size(58, 13);
$labelLast.Text = "Last Name";

##
## textBoxLast
##
[System.Drawing.Point]$textBoxLast.Location = New-Object System.Drawing.Point(186, 23);
$textBoxLast.Name = "textBoxLast";
[System.Drawing.Size]$textBoxLast.Size = New-Object System.Drawing.Size(119, 20);
$textBoxLast.TabIndex = 3;
$textBoxLast.add_TextChanged({UpdateFields})

##
## labelDisplayName
##
$labelDisplayName.AutoSize = $true;
[System.Drawing.Point]$labelDisplayName.Location = New-Object System.Drawing.Point(11, 46);
$labelDisplayName.Name = "labelDisplayName";
[System.Drawing.Size]$labelDisplayName.Size = New-Object System.Drawing.Size(72, 13);
$labelDisplayName.Text = "Display Name";

##
## textBoxDisplayName
##
[System.Drawing.Point]$textBoxDisplayName.Location = New-Object System.Drawing.Point(11, 62);
$textBoxDisplayName.Name = "textBoxDisplayName";
[System.Drawing.Size]$textBoxDisplayName.Size = New-Object System.Drawing.Size(294, 20);
$textBoxDisplayName.TabIndex = 4;

##
## labelAlias
##
$labelAlias.AutoSize = $true;
[System.Drawing.Point]$labelAlias.Location = New-Object System.Drawing.Point(11, 85);
$labelAlias.Name = "labelAlias";
[System.Drawing.Size]$labelAlias.Size = New-Object System.Drawing.Size(29, 13);
$labelAlias.Text = "Alias";

##
## textBoxAlias
##
[System.Drawing.Point]$textBoxAlias.Location = New-Object System.Drawing.Point(11, 101);
$textBoxAlias.Name = "textBoxAlias";
[System.Drawing.Size]$textBoxAlias.Size = New-Object System.Drawing.Size(119, 20);
$textBoxAlias.TabIndex = 5;

##
## labelTitle
##
$labelTitle.AutoSize = $true
[System.Drawing.Point]$labelTitle.Location = New-Object System.Drawing.Point(137, 85);
$labelTitle.Name = "labelTitle";
[System.Drawing.Size]$labelTitle.Size = New-Object System.Drawing.Size(27, 13);
$labelTitle.Text = "Title";

##
## textBoxTitle
##
[System.Drawing.Point]$textBoxTitle.Location = New-Object System.Drawing.Point(140, 101);
$textBoxTitle.Name = "textBoxTitle";
[System.Drawing.Size]$textBoxTitle.Size = New-Object System.Drawing.Size(165, 20);
$textBoxTitle.TabIndex = 6;

##
## labelDepartment
##
$labelDepartment.AutoSize = $true;
[System.Drawing.Point]$labelDepartment.Location = New-Object System.Drawing.Point(11, 124);
$labelDepartment.Name = "labelDelartment";
[System.Drawing.Size]$labelDepartment.Size = New-Object System.Drawing.Size(62, 13);
$labelDepartment.Text = "Department";

##
## comboBoxDepartment
##
$comboBoxDepartment.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
$comboBoxDepartment.FormattingEnabled = $true
[System.Drawing.Point]$comboBoxDepartment.Location = New-Object System.Drawing.Point(11, 140)
$comboBoxDepartment.Name = "comboBoxDepartment"
[System.Drawing.Size]$comboBoxDepartment.Size = New-Object System.Drawing.Size(119, 21)
$comboBoxDepartment.TabIndex = 7;

## Populating combo-box with values
[System.Array]$departmentItems = "Products","Services";
ForEach ($item in $departmentItems)
{
$comboBoxDepartment.Items.Add($item) | out-null
}

##
## labelLocation
##
$labelLocation.AutoSize = $true;
[System.Drawing.Point]$labelLocation.Location = New-Object System.Drawing.Point(137, 124);
$labelLocation.Name = "labelLocation";
[System.Drawing.Size]$labelLocation.Size = New-Object System.Drawing.Size(48, 13);
$labelLocation.Text = "Location";

##
## comboBoxLocation
##
$comboBoxLocation.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList;
$comboBoxLocation.FormattingEnabled = $true;
$comboBoxLocation.Location = New-Object System.Drawing.Point(140, 140);
$comboBoxLocation.Name = "comboBoxLocation";
$comboBoxLocation.Size = New-Object System.Drawing.Size(165, 21);
$comboBoxLocation.TabIndex = 8;

## Populating combo-box with values

[System.Array]$locationItems = "US", "India";
ForEach ($item in $locationItems )
{
$comboBoxLocation.Items.Add($item) | out-null
}

##
## labelEmployeeType
##
$labelEmployeeType.AutoSize = $true
[System.Drawing.Point]$labelEmployeeType.Location = New-Object System.Drawing.Point(11, 164)
$labelEmployeeType.Name = "labelEmployeeType"
[System.Drawing.Size]$labelEmployeeType.Size = New-Object System.Drawing.Size(80, 13)
$labelEmployeeType.Text = "Employee Type";

##
## comboBoxEmployeeType
##
$comboBoxEmployeeType.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList;
$comboBoxEmployeeType.FormattingEnabled = $true;
[System.Drawing.Point]$comboBoxEmployeeType.Location = New-Object System.Drawing.Point(11, 180);
$comboBoxEmployeeType.Name = "comboBoxEmployeeType";
[System.Drawing.Size]$comboBoxEmployeeType.Size = New-Object System.Drawing.Size(119, 21);
$comboBoxEmployeeType.TabIndex = 10;
[System.Array]$EmployeeTypeItems = "Full Time Employee","Contractor/Vendor", "Intern";
ForEach ($Item in $EmployeeTypeItems )
{
$comboBoxEmployeeType.Items.Add($Item) | out-null
}

##
## labelManager
##
$labelManager.AutoSize = $true;
$labelManager.Location = New-Object System.Drawing.Point(137, 164);
$labelManager.Name = "labelManager";
$labelManager.Size = New-Object System.Drawing.Size(49, 13);
$labelManager.Text = "Manager";

##
## textBoxManager
##
$textBoxManager.Location = New-Object System.Drawing.Point(140, 180);
$textBoxManager.Name = "textBoxManager";
$textBoxManager.Size = New-Object System.Drawing.Size(140, 20);
$textBoxManager.TabIndex = 11;

##
## buttonManager
##
$buttonManager.FlatStyle = [System.Windows.Forms.FlatStyle]::System;
$buttonManager.Location = New-Object System.Drawing.Point(284, 181);
$buttonManager.Margin = New-Object System.Windows.Forms.Padding(1);
$buttonManager.Name = "buttonManager";
$buttonManager.Size = New-Object System.Drawing.Size(21, 20);
$buttonManager.TabIndex = 12;
$buttonManager.Text = "...";
$buttonManager.TextAlign = [System.Drawing.ContentAlignment]::BottomLeft;
$buttonManager.UseVisualStyleBackColor = $true;
$buttonManager.Add_Click({Manager-OnClick});
##
## distributionGroupBoxOther
##
$distributionGroupBoxOther.Controls.Add($checkBoxAllowVpn);
$distributionGroupBoxOther.Controls.Add($checkBoxChangePasswordOnNextLogin);
$distributionGroupBoxOther.Controls.Add($checkBoxEnabled);
$distributionGroupBoxOther.Controls.Add($checkBoxHideFromGal);
[System.Drawing.Point]$distributionGroupBoxOther.Location = New-Object System.Drawing.Point(11, 215);
$distributionGroupBoxOther.Name = "distributionGroupBoxOther";
[System.Drawing.Size]$distributionGroupBoxOther.Size = New-Object System.Drawing.Size(294, 72);
$distributionGroupBoxOther.TabStop = $false;
$distributionGroupBoxOther.Text = "Additional Settings";

##
## checkBoxEnabled
##
$checkBoxEnabled.AutoSize = $true;
$checkBoxEnabled.Checked = $true;
$checkBoxEnabled.Name = "checkBoxEnabled";
[System.Drawing.Point]$checkBoxEnabled.Location = New-Object System.Drawing.Point(6, 19);
[System.Drawing.Size]$checkBoxEnabled.Size = New-Object System.Drawing.Size(65, 17);
$checkBoxEnabled.TabIndex = 13;
$checkBoxEnabled.Text = "Enabled";
$checkBoxEnabled.UseVisualStyleBackColor = $true;

##
## checkBoxAllowVpn
##
$checkBoxAllowVpn.AutoSize = $true;
$checkBoxAllowVpn.Checked = $true;
[System.Drawing.Point]$checkBoxAllowVpn.Location = New-Object System.Drawing.Point(6, 42);
$checkBoxAllowVpn.Name = "checkBoxAllowVpn";
[System.Drawing.Size]$checkBoxAllowVpn.Size = New-Object System.Drawing.Size(76, 17);
$checkBoxAllowVpn.TabIndex = 14;
$checkBoxAllowVpn.Text = "Allow VPN";
$checkBoxAllowVpn.UseVisualStyleBackColor = $true;

##
## checkBoxChangePasswordOnNextLogin
##
$checkBoxChangePasswordOnNextLogin.AutoSize = $true;
$checkBoxChangePasswordOnNextLogin.Checked = $true;
[System.Drawing.Point]$checkBoxChangePasswordOnNextLogin.Location = New-Object System.Drawing.Point(109, 19);
$checkBoxChangePasswordOnNextLogin.Name = "checkBoxChangePasswordOnNextLogin";
[System.Drawing.Size]$checkBoxChangePasswordOnNextLogin.Size = New-Object System.Drawing.Size(178, 17);
$checkBoxChangePasswordOnNextLogin.TabIndex = 15;
$checkBoxChangePasswordOnNextLogin.Text = "Change password on next logon";
$checkBoxChangePasswordOnNextLogin.UseVisualStyleBackColor = $true;

##
## checkBoxHideFromGal
##
$checkBoxHideFromGal.AutoSize = $true;
[System.Drawing.Point]$checkBoxHideFromGal.Location = New-Object System.Drawing.Point(109, 42);
$checkBoxHideFromGal.Name = "checkBoxHideFromGal";
[System.Drawing.Size]$checkBoxHideFromGal.Size = New-Object System.Drawing.Size(98, 17);
$checkBoxHideFromGal.TabIndex = 16;
$checkBoxHideFromGal.Text = "Hide From GAL";
$checkBoxHideFromGal.UseVisualStyleBackColor = $true;

##
## buttonCancel
##
[System.Drawing.Point]$buttonCancel.Location = New-Object System.Drawing.Point(233, 296);
$buttonCancel.Name = "buttonCancel";
[System.Drawing.Size]$buttonCancel.Size = New-Object System.Drawing.Size(75, 23);
$buttonCancel.TabIndex = 17
$buttonCancel.Text = "Cancel"
$buttonCancel.UseVisualStyleBackColor = $true;
$buttonCancel.Add_Click({Form-Exit});

##
## buttonOK
##
[System.Drawing.Point]$buttonOK.Location = New-Object System.Drawing.Point(151, 296)
$buttonOK.Name = "buttonOK"
[System.Drawing.Size]$buttonOK.Size = New-Object System.Drawing.Size(75, 23)
$buttonOK.TabIndex = 18
$buttonOK.Text = "OK"
$buttonOK.UseVisualStyleBackColor = $true;
$buttonOK.Add_Click({Form-OnSubmit});

Write-Host("Generating form");

#
#Form1
#
[System.Windows.Forms.Form]$Form = New-Object System.Windows.Forms.Form;
$Form.ClientSize = New-Object System.Drawing.Size(320, 330);
$Form.Controls.Add($comboBoxDepartment);
$Form.Controls.Add($labelDepartment);
$Form.Controls.Add($labelEmployeeType);
$Form.Controls.Add($comboBoxEmployeeType);
$Form.Controls.Add($labelLocation);
$Form.Controls.Add($comboBoxLocation);
$Form.Controls.Add($textBoxMiddleInitial);
$Form.Controls.Add($labelMiddleInitial);
$Form.Controls.Add($textBoxDisplayName);
$Form.Controls.Add($labelDisplayName);
$Form.Controls.Add($distributionGroupBoxOther);
$Form.Controls.Add($textBoxTitle);
$Form.Controls.Add($textBoxAlias);
$Form.Controls.Add($textBoxLast);
$Form.Controls.Add($textBoxFirst);
$Form.Controls.Add($labelTitle);
$Form.Controls.Add($labelAlias);
$Form.Controls.Add($labelLast);
$Form.Controls.Add($labelFirst);
$Form.Controls.Add($labelManager);
$Form.Controls.Add($textBoxManager);
$Form.Controls.Add($buttonManager);
$Form.Controls.Add($buttonOK);
$Form.Controls.Add($buttonCancel);

$Form.MaximizeBox = $false;
$Form.Name = "Form01";
$Form.ShowIcon = $false;
$Form.Text = "Create new Active Directory user";
$Form.ShowDialog() | Out-Null

Write-Host("Exiting '" + $methodName + "' function");
}

function Form-Validate
{
[System.String] $methodName = "Form-Validate";
Write-Host("Entering " + $methodName + " function");

[System.Boolean] $havingFirst = $false;
[System.Boolean] $havingLast = $false;
[System.Boolean] $havingDisplay = $false;
[System.Boolean] $havingAlias = $false;
[System.Boolean] $havingDepartment = $false;
[System.Boolean] $havingLocation = $false;
[System.Boolean] $havingEmployeeType = $false;

##
## FirstName
##
Try
{
if([System.String]::IsNullOrEmpty( $textBoxFirst.Text) )
{
$labelFirst.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'givenName' (First Name) is not specified");
$havingFirst = $false;
}
else
{
$labelFirst.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'givenName' (First Name) is OK");
$havingFirst = $true;
}

}
Catch [System.Exception]
{
$labelFirst.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'givenName' (First Name) is not specified");
$havingFirst = $false;
}
##
## LastName
##
Try
{
if([System.String]::IsNullOrEmpty( $textBoxLast.Text) )
{
$labelLast.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'sn' (Last/Family Name/Surname) is not specified");
$havingLast = $false;

}
else
{
$labelLast.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'sn' (Last/Family Name/Surname) is OK");
$havingLast = $true;
}

}
Catch [System.Exception]
{
$labelLast.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'sn' (Last/Family Name/Surname) is not specified");
$havingLast = $false;
}

##
## DisplayName
##
Try
{
if([System.String]::IsNullOrEmpty( $textBoxDisplayName.Text))
{
$labelDisplayName.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'DisplayName' is not specified");
$havingDisplay = $false;

}
else
{
$labelDisplayName.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'DisplayName' is OK");
$havingDisplay = $true;
}

}
Catch [System.Exception]
{
$labelDisplayName.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'DisplayName' is not specified");
$havingDisplay = $false;
}

##
## Alias
##
Try
{
if([System.String]::IsNullOrEmpty( $textBoxAlias.Text) )
{
$labelAlias.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'Alias' is not specified");
$havingAlias = $flase;

}
else
{
$labelAlias.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'Alias' is OK");
$havingAlias = $true;
}

}
Catch [System.Exception]
{
$labelAlias.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'Alias' is not specified");
$havingAlias = $false;
}

##
## Department
##
Try
{
if([System.String]::IsNullOrEmpty( $comboBoxDepartment.SelectedItem) )
{
$labelDepartment.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'Department' is not specified");
$havingDepartment = $false;
}
else
{
$labelDepartment.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'Department' is OK");
$havingDepartment = $true;
}
}
Catch [System.Exception]
{
$labelDepartment.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'Department' is not specified");
$havingDepartment = $false;
}

##
## Location
##
Try
{
if([System.String]::IsNullOrEmpty($comboBoxLocation.SelectedItem) )
{
$labelLocation.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'Location' is not specified");
$havingLocation = $false;
}
else
{
$labelLocation.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'Location' is OK");
$havingLocation = $true;
}
}
Catch [System.Exception]
{
$labelLocation.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'Location' is not specified");
$havingLocation = $false;
}

##
## EmployeeType
##
Try
{
if([System.String]::IsNullOrEmpty( $comboBoxEmployeeType.SelectedItem) )
{
$labelEmployeeType.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'EmployeeType' is not specified");
$havingEmployeeType = $false;

}
else
{
$labelEmployeeType.ForeColor = [System.Drawing.Color]::Black;
Write-Host("'EmployeeType' is OK");
$havingEmployeeType = $true;
}

}
Catch [System.Exception]
{
$labelEmployeeType.ForeColor = [System.Drawing.Color]::Red;
Write-Host("'EmployeeType' is not specified");
$havingEmployeeType = $false;
}

#Write-Host("Exiting " + $methodName + " function");
#Write-Host("havingFirst: " + $havingFirst);
#Write-Host("havingLast: " + $havingLast);
#Write-Host("havingDisplay: " + $havingDisplay);
#Write-Host("havingAlias: " + $havingAlias);
#Write-Host("havingDepartment: " + $havingDepartment);
#Write-Host("havingLocation:" + $havingLocation);
#Write-Host("havingEmployeeType: " + $havingEmployeeType);
return $havingFirst -and $havingLast -and $havingDisplay -and $havingAlias -and $havingDepartment -and $havingLocation -and $havingEmployeeType;
}

Form-Build;

Happy coding!

Metaverse Router 1.1

I have updated Metaverse Router code on CodePlex. One bug was fixed “XML Tag Capitalization”; minor but nasty little bug that was throwing exceptions during initialization of the provisioning modules. I have also updated the solution and project to Visual Studio 2010 with WIX 3.5 for an MSI building purposes.

Metaverse Router

Have you ever heard of Metaverse Router? If your answer is yes – you ARE a Sync Engine geek! (No offence here)
After years of sitting on this project I’ve finally got some time and will to clean it up, write an installer and make it publicly available.
So what is Metaverse Router?  If you have worked with MIIS/ILM and to some degree with FIM, before — you likely had a need to write some sort of provisioning code to generate new objects in connected directories. The truth is that regardless of how many directories you are generating objects for you’ll be working with the same DLL. It is a good and a bad thing. Good thing, it’s a single DLL, it’s easy to have all your code in one place and you can re-use some portions of it and apply to deferent situations.
The bad thing is that when you change something for one connected data-source you are changing code for ALL connected data-sources. Technically that qualifies your DLL to be re-tested from end-to-end and perform some regression testing on every data-source; will you do that or not is on your choice of how well you want to sleep that night and night after that.
So what is the solution? There is "well-unknown" router solution that MIIS/ILM product team supplied in the "developer reference"; technically you can cut and paste that solution and be happy with that.  What is gives you is an ability to create multiple provisioning DLLs for every connected data-source without having them all mixes into a monolithic block of code. The "developer reference" router will look at the files in the "extensions" folder and will look at their names. If file falls into specified naming convention — voila! — it will be executed.
The Metaverse Router that I’ve just published on CodePlex takes this idea to the next level. First of all I am looking at XML configuration file for my collection of provisioning DLLs. Second of all I don’t have to delete DLLs from "Extensions" folder to disable provisioning to a certain connected data-source (and therefore trigger a need for full re-synchronization), and third of all I can control execution index of my modules, which can be handy during specific configuration (Auxiliary MA implementation for example); Lastly, but not less important The Metaverse Router will allow you to turn on and off provisioning without actually changing sever configuration.
So if you are a Sync Engine geek – you’ve got a new toy for yourself:  Metaverse Router.
Happy Coding