Archive for the ‘ BPOS ’ Category

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

Advertisements

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!

Automation of User Enablement in BPOS

If you have been working with BPOS you might take a notice of one peculiar design decision made by Microsoft team. Once user is created by Sync Tool the account is creates in inactive state. For administrator to activate the user (s)he heave to log-in and assign an appropriate license to an account, after which user is actually enabled/activated.

Enable/Disable vs. Activate/Deactivate

This was a bit confusing; to me, but with help from Microsoft support folks (internal contacts are everything!) I’ve figure out the deference between Activation and Enablement in BPOS

Activation is a process of assigning the license to the user account

Enablement is an ability of user to access the account

Activation can only be done only one time; Once license is assigned the mail flow (if that is the case for your user(s)) will begin; By disabling a user account an administrator can restrict user access to the BPOS resources, however mail-flow is not affected by that action.

So what else could we do? Automate!

Well, naturally, as an IdM guy, I was looking into automation of this process. Why would I ask an administrator to log-in and activate an account when we are dealing with automated process? So… several hours of swearing under my breath, I’ve written an Extensible Management Agent to perform one-time-activations of user accounts upon creation. Now I can assign an appropriate license to a user without asking an admin to log-in and do that manually.

End-state scenario looks like this:

  1. User account is created in AD
  2. User account is synchronized into BPOS (in our case I am doing less than 2 min provisioning cycle. Who’s got time to wait for default sync-cycle loop timing)
  3. User account is activated by assigning an appropriate license to it and instantly availabe to user (Deferent license can be assigned based on user’s OU [location] or attribute of your choice in AD)

Voila! Look ma, no hands!

BPOS PCNS Extension

Lately I have been involved in a lot of internal Microsoft BPOS activity. For people who have not heard of BPOS it is: Business Productivity Online Suite. Basically it is Microsoft servers such as Exchange, SharePoint, Communication Server, etc. that are hosted by Microsoft in Microsoft’s data-center and sold to business as a service vs. as a software/product. No need for hardware, no need for upgrades and maintenance.
Schakra has embraced ‘the cloud’ and bravely moved all our internal mailboxes to BPOS. As a Microsoft partner that offers BPOS deployments to customers this was a necessary move. Now we can experience what our customers are experiencing and gain valuable first-hand expertise.
The very first thing that I have noticed after the migration was completed is that now I’ve got two passwords to worry about. One for my local AD and another for BPOS cloud resources. BPOS comes with rich SSO client with attempts to manage your credentials and re-configures your rich applications such as Outlook and Communicator, however when you are going to web resource – you are on your own. You got to type your login and password assigned to you. Out IT guys were not exactly a happy bunch, when users began to ask to reset local and cloud passwords. Technically almost all time they have saved on not managing local exchange server they were losing on ad-hoc password resets. We have plenty of users that are working remotely, some are VPNing, some are joined to client’s domains… so as you can imagine adding another variable to password management is no an ideal place to be in.
Being an IdM guy I could not live with that. My researched indicated that there is no products that would employ standard PCNS (Password Change Notification Service) that would synchronize on-premise AD passwords with the cloud BPOS. What else could I do, but to write one!
For several days Schakra’s internal population is happily using BPOS PCNS extension; we would be happy to help any BPOS customer with your password synchronization issues.
BPOS PCNS extension installs onto your existing BPOS directory synchronization box and does not require any custom code on your domain controllers, nor a web-service of any kind or a separate physical or virtual host. It simply augments your existing BPOS installation and synchronizes your AD passwords to BPOS passwords 1 to 1