Welcome to MSDN Blogs Sign in | Join | Help

One of the more challenging tasks facing ISVs on the Microsoft platform is navigation of all the technologies new to production and those due to arrive soon.  It's often not exactly clear how these technologies are intended to be used and how they're intended to be used together.

As it's a big part of my job to provide some clarity on this topic, I'd like to take this opportunity to give you a very high-level overview on how a few of our forthcoming products work together to provide a very powerful platform for ISVs to build on and extend.

The products I'm referring to are the Business Scorecard Manager, SQL Server Analysis Services, SQL Server Reporting Services, SQL Server Integration Services and Office 12.  While each of these products are useful in their own right, when combined they add up to somethng greater than the sum of their parts (hence the gestalt in the title of the post.)

For the first time, Microsoft is offering a platform for reporting and analysis that's as robust at the bottom as it is all the way up through the top of the stack.  ISVs will be able to extract, transform, and load data with SSIS, prepare it for delivery with SSRS, allow for end user driven exploration with SSAS, and present it in thin and rich clients with Excel 12 and SharePoint 12.

This is all made available in a consistent, developer-friendly set of APIs, which in turn will enable ISVs to build some tremendous Business Intelligence and Reporting applications that will knock the socks off their customers.  Even a snarky old crank like me was impressed by how nicely this all came together.

If you'd like to get a sense of where we're going with all this straight from the top, check out Jeff Raikes' web cast from the Business Scorecard Manager launch at http://www.microsoft.com/presspass/press/2005/oct05/10-23BiLaunchPR.mspx.  It well worth a listen if you're embarking on a effort to enhance the reporting and analysis capabilities of your products.

After reviewing my last post on this topic and reflecting a bit, I realized that my demo app sure sounds a lot like a customer relationship management application.  That prompted me to look into our customer relationship management product (MSCRM 3.0) and research how it might be applied.  I was impressed by what I found, and thus will be using MSCRM for the basis of much of what I described.

I won't repeat here the major features of a CRM application - there are many sources that do this much better that I could.  So, presuming that you're familar with the basic concepts and features offered to a CRM user, here's some of what MSCRM 3.0 offers you as a developer:

  • Robust base set of CRM entities (customers, contacts, leads, incidents, etc.)
  • Support for extension with your own entities
  • Web service API for accessing server functionality
  • Extensible rich clent nicely integrated with Outlook
  • Extensible web client pages
  • Integration points for custom business logic and reports
  • Many many more things

This certainly seems like a good base on which to build, so I'm going to define some of the custom entities I discussed in my previous post in MSCRM 3.0 and then snowflake out to a custom database if I start to push things a little to far for my comfort.  From there, I'll then attempt to use the MSCRM 3.0 web services and SQL Server database I've defined as a basis to explore WPF and Atlas.

In my next post on this topic, I'll describe my experiences with the MSCRM 3.0 SDK, and I'll be document my composite data model for reference.  Then, it's off to the races with WinFx.

Well, my fancy new 64-bit laptop completed its odyssey and finally arrived.  I was able to get build 5270 of the 64-bit version of Vista up and running in a partition on the hard drive, and will return to digging into WinFx.  It should be quite an interesting journey, as I'm just a guy trying to digest all this forthcoming change into something manageable.  I hope I'll be able to steer you clear of pitfalls I encounter.

More soon...

About a week ago, I was all excited to get started on a series of WinFx posts centered around a demo app I'm building.  My manager had generously approved my order for a new high-powered 64-bit laptop and I had been eagerly polling our internal ERP portal looking for a tracking number so I could monitor its delivery.  You can imagine my geek anticipation when I got a call Friday indicating that my new computer arrived.

 

"It's here."

"What's here?"

"Your new computer."

"YESSSSSSSS!!!!!!!!"

 

I downloaded the 64-bit build of Vista from the MSDN subscription site and rushed over to my recently arrived package to find...I had been shipped a 32-bit desktop.  What followed was about 3 days of ERP forensics which felt like the most boring possible episode of one of those CSI shows (no cool lab gear either.)  After sifting through cryptic email messages, marginally integrated websites, and lots of voice mail messages, we finally determined that I had received a desktop that was intended to be shipped to Denver (and the person in Denver received my laptop instead.)

I'm told that In Texas, they have colorful metaphors that describe which end of the deal I got.

At any rate, I'm glad to report that the desktop runs 32-bit Vista very well, and even does the cool Aero glass and flip 3D without a hitch.  I'm also glad to report that it's now on its way to the Mile High City.  Hopefully, my laptop will be coming in the other direction soon (but I'm not holding my breath waiting...)

There is a relevant ISV-related point here though.

In the course of figuring out what happened to my order, it struck me how difficult and cumbersome it was to put all the information I needed together.  It would be great if this information was more organized and integrated, and it would be greater still if there was a compelling user experience on top of that.

Sounds like a great opportunity for some innovative ISVs.

 

0 Comments
Filed under:

Having just returned from the TechReady2 conference in rain-soaked Seattle, I realized that knowledge of all the great technologies I had just seen and heard about would quickly pass through my mind if I didn't do something to make them stick.  I also found myself wondering just how I could possibly make that happen with all the administrative tasks I had to keep up with as part of my new role.  And so, I thought it might be a good idea to build a simple application on WinFx that helps me handle my administrative tasks and at the same time illustrates the feature set and power of WinFx.  As I suspect many ISVs will be facing the same challenges, I also thought it might be useful to blog about it.

I call the application I'll be building "myisvs", and its purpose is quite simple.  I'd like to use it to track all the email, news, web, document, event, and contact information I manage for each ISV I work with.  Accordingly, I started by builiding a simple data model in SQL Server 2005 which tracks:

 

ISVs - base information about each ISV

Contacts - pointers over to contacts kept in Exchange for each ISV

Events - any happenings within the ISV that I'd like to keep track of (text + link + date/time)

Locations - address and lat/long (for plotting on a map)

Profile - blurbs of text about each ISV I hope to use to auto-generate Word 12 documents

Programs - internal Microsoft sponsored programs the ISV is participating in (Touchdown, Ascend, Front Runner, etc.)

Products - products ISVs offer in the market

Technologies - the technologies (products, frameworks, etc., Microsoft or otherwise) that the ISVs use in their products

 

 

I've also added a bunch of tables to link all these together in what I hope are interesting ways.

In my next post, I'll discuss the data model, and from there, I'll walk my way up the stack to a business layer and finally a user interface built on top of the data model.  I hope to touch all the major components of WinFx in the process and hope that I'll learn quite a bit along the way. 

 

 

In my last posting, I showed how to call the low-level API exposed by WLDAP32.DLL to authenticate via an LDAP bind.  The authentication function - ldap_simple_bind_s() - returns a 0 when the credentials supplied were successfully authenticated.  I left out what happens when the authentication function returns an error code.  It turns out that determining what caused your authentication call to fail can be a bit subtle – at least when the directory you’re binding is Active Directory.

 

PLEASE NOTE: I’m sharing this information because I don’t want any to have to figure this out by the means I did (quality time with ADSIEdit, a test domain controller loaded in a Virtual PC, and lots of VBScript fragments pulled from around the Internet.)

 

First, here’s what can go wrong when calling ldap_simple_bind_s() using a connection to an Active Directory LDAP server:

 

 

And here’s how to tell them apart:

 

  • Any error other than LDAP_INVALID_CREDENTIALS implies that it’s not any of the other cases I’ve listed
  • If ldap_simple_bind_s() returns LDAP_INVALID_CREDENTIALS
    • Test for the disabled account condition by checking if the userAccountControl attribute for that user has ACCOUNTDISABLE (0x0000002) bit set.  If it’s set, the account is disabled.
    • Test for the expired account condition by checking if the accountExpires attribute has an invalid value – this is a little bit tricky, as it’s a 64 bit integer field that may or may not be present (because account may or may not have an expiration date.)  If it’s present, you’ll need to convert it to a date/time value and compare it to the current system date/time.  It gets even more subtle than that, but that’s the subject of a future posting…
    • Test for the locked out account condition by checking the lockoutTime attribute.  If it comes back with a non-zero value, the account is locked out.
    • Test for the must change password condition by checking the pwdLastSet attribute.  If it comes back with a 0 value, the password must be reset at next login.  There’s a flag in the userAccountControl attribute that looks like it corresponds to this condition, but in my experience, I’ve found that this is the only reliable way to tell.
    • Test for the expired password condition by checking the pwdLastSet attribute against domain policy.  This also involves date arithmetic with 64 bit integer attributes, which I’ll cover in a future posting.
    • If all those tests fail, you can safely presume that you’ve been given a bad password.

 

As you’ve probably surmised, I had write an application that had a critical need to tell the difference between all this conditions and was surprised at how hard it was to do so.  Hopefully, this will help someone else charged with the same task (or hopefully, no one else will ever have to do this.)  At any rate, that’s how to tell why your LDAP bind to Active Directory didn’t work.

 

Somewhere, the system administrators of the world are laughing at me.  J

jmp

I'd like to take an opportunity to introduce my ISV Architecure/Developer Advisor colleagues - starting first with the east coast first.

Alex Onik is my counterpart based in Boston, Massachusetts.  In addition to the Microsoft platform, he has significant background on J2EE and object-oriented design patterns.  He's promised to help demystify all the patternazzi-speak I have trouble with, and I'm holding him to it.

John Shipway is my counterpart based in Charlotte, North Carolina.  He has significant experience within the Healthcare and Life Sciences vertical, and likes to collect samples of buzzword-laden corporatese.

Michael Earls is the most prolific and artistic blogger of our team.  You can check out all the great work he's done with the Acrylic beta on his Cerebral Kitchen site (including a scarily lifelike avatar.) [:)]

And of course I'd be remiss if I didn't refer you to my manager, Larry Gregory.  Larry's based in Connecticut, but does lots of travel.  He and I have had some international misadventures (in which he kept me off the streets of Zurich by throwing me a really tough Notes API + MQ Series integration problem to solve on a really early version of BizTalk Server.

It's my pleasure to work for Larry and to work with such a great team, and if you'd like to join us (and are living in the Philadelphia/DC metro area), you might want to apply via this job posting (http://members.microsoft.com/careers/search/details.aspx?JobID=44cab0ab-2e91-4c0d-95fb-57b7f24252b9)

 

jmp

 

Hi,

While I'm up too late, I should mention that Ron Jacobs has posted a podcast on Identity Management up on Channel 9 Wednesday.  You can check it out at http://channel9.msdn.com/shows/ARCast_with_Ron_Jacobs.

jmp

Well, its a new year, and therefore its an appropriate time to begin my journey into the world of LDAP and Identity Management.  I'd like to start by discussing some of the recent history of LDAP on .NET (dating no further back than .NET 1.1), and then I'll move ahead into an exploration of .NET 2.0's System.DirectoryServices.Protocols namespace.  The classes in this namespace are new to me, and are probably new to you too.  I'll likely stumble a few times along the way (please hold me to account if you find any errors), but I hope I'll illustrate how these classes provide you with secure, scalable and high-performance means of accessing LDAP directories.

"Why not just use System.DirectoryServices"?

Let me start by saying, yes, you absolutely can access LDAP directories via System.DirectoryServices.  On .NET 1.x, these classes provided a nice managed wrapper over the COM-based ADSI libraries, and were well-documented and well-supported for writing applications that accessed directories.  This worked great for applications that didn't need to support a large number of distinct identities accessed on a larger number of distinct threads.  Unfortunately, if you dropped code that into an assembly loaded by IIS and authenticated a large number (>1000s) of users through it, you'd find that your app's responsiveness would drop quite a bit.

After a lot of head scratching and running some controlled experiments with some very bright colleagues of mine, we discovered why.  We were running out of available TCP sockets, and we discovered that the more distinct identities we randomized through the app, the quicker we'd get into trouble.  To cut right to it, it turns out that the reason this happened was by design, namely, ADSI (and thus System.DirectoryServices) weren't intended to authenticate 1000s of identities in the middle-tier.  Unfortunately, enterprise developers sometimes have to connect LDAP directories other than Active Directory, and sometimes they have a large number of users to bind to those LDAP directories.  So how did we resolve it?

WLDAP32.DLL to the rescue

In reviewing a few memory dumps, we discovered a library that was loaded under System.DirectoryServices and ADSI on every thread with a hung up socket.  That library was WLDAP32.DLL, and it was the key to our getting ripping fast LDAP binds going on .NET 1.x.  Unfortunately, WLDAP32.DLL isn't very well-known or well-documented - at least not via P/Invoke from C#.  There isn't even a list of declarations for it up on http://www.pinvoke.net.  That lead to many more hours of head scratching, and some creative use of a C++ shim DLL to figure out how to get my marshaling to/from unmanaged memory right.  I don't envy anyone attempting to do this, and I sincerely hope that if you're ever faced with this issue, you'll upgrade to .NET 2.0 and bypass the whole thing.  But this does illustrate what's happening at the lower API levels when you authenticate users via LDAP binds.  I'm including this here with the hope that it will shed some light on a topic I found to be rather murky prior to going through this process.

Init, bind, and bind again

To connect to an LDAP directory via WLDAP32.DLL, you need to call the ldap_init() or ldap_sslinit() function.  I prefer ldap_sslinit() here because I can make it behave just like ldap_init() by changing the value of a single argument.  Depending on the value of said argument, the ldap_sslinit() function will establish a clear-text LDAP connection or a TLS protected LDAP connection (by default on ports 389 and 636 respectively.)  Their managed code declarations look like this:

[ComVisible(false), SuppressUnmanagedCodeSecurityAttribute()]
internal class WLdap32
{
  /// <summary>
  /// The ldap_init function initializes a session with an LDAP server.
  /// </summary>
  /// <see cref="
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/ldap_init.asp?frame=true"/>
  [DllImport("wldap32.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "ldap_initW", SetLastError = true, CharSet = CharSet.Unicode)]
  public static extern IntPtr ldap_init(string hostName, uint portNumber);

  /// <summary>
  /// The ldap_sslinit function initializes a Secure Sockets Layer (SSL) session with an LDAP server.
  /// </summary>
  /// <param name="hostName"></param>
  /// <param name="portNumber"></param>
  /// <param name="secure"></param>
  /// <returns></returns>
  ///
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/ldap_sslinit.asp?frame=true
  [DllImport("wldap32.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "ldap_sslinitW", SetLastError = true, CharSet = CharSet.Unicode)]
  public static extern IntPtr ldap_sslinit(string hostName, uint portNumber, int secure);
}

Both these functions return an IntPtr, which you'll need to hang on to for every subsequent call you make until you close the connection.  Once you have such an IntPtr, authenticating a user is a matter of binding the LDAP directory.  There are many ways to do this, but for enterprise environments where only port 389 or 636 connections are allowed, the ldap_simple_bind_s() function is the simplest to implement and the easiest to understand.  Its manged code declaration looks like this:

[ComVisible(false), SuppressUnmanagedCodeSecurityAttribute()]
internal class WLdap32
{
  //
 other declarations, then...

  /// <summary>
  /// The ldap_simple_bind_s function synchronously authenticates a client to server, using a plaintext password.
  /// </summary>
  /// <param name="ldapHandle"></param>
  /// <param name="distinguishedName"></param>
  /// <param name="password">Password of user in Active Directory</param>
  /// <returns></returns>
  ///
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ldap/ldap/ldap_simple_bind_s.asp
  [DllImport("wldap32.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "ldap_simple_bind_sW", CharSet = CharSet.Unicode)]
  public static extern int ldap_simple_bind_s([In] IntPtr ldapHandle, string distinguishedName, string password);
}

A couple things to note here:

  • As the <summary> indicates, ldap_simple_bind_s() sends passwords to the server in plaintext.  I do not recommend using it over anything other than a secure LDAP connection (or over one with a transport secured via other means.)
  • The second argument is the distinguishedName of the user (i.e., not the username.)  The username you type into the Windows Login dialog corresponds to Active Directory's sAMAccountName attribute - you'll need to distinguishedName associated with that sAMAccountName in order to authenticate via ldap_simple_bind_s(), and you should also note that you often can't simply derive the distingusihedName from the username (at least not on Windows.)

Have I dissauded you from attempting this yet?

Boy I sure hope so, but if you really insist on trying, the code you'll need to call these functions wil look something like this:

// standard console stuff and namespace declarations, then...
void AuthenticateViaLdap(string userDn, string password)
{
  // read "Writing Secure Code", then *never* write code like this,
  // but for sake of illustration...
  string host   = "somehost.dns.name";
  int    port   = 636; // the default LDAPS port
  int    secure = Convert.ToInt32(true);
  IntPtr pCnx   = WLdap32.ldap_sslinit(host, port, secure);
  int    result = WLdap32.ldap_simple_bind_s(pCnx, userDn, password);
  // hope you get back result == 0
}

As you can guess, a result of 0 indicates that the user was successfully authenticated.  In my next post I'll explore what happens when you get a non-zero result back from this function and things you might want to do about that.

Happy New Year!

jmp

 

 

I guess one of the telltale signs of the advancing creep of age is the passage of one's pop culture heroes into obscurity - another sign is the tendency to begin or end one's sentences with the phrase "back in the day."  So rather than attempt to forestall the inevitable, I'm going to wallow in it for just a little while.

Back in the day, when REM was still on IRS records and still not quite popular, they released a disc called "Document" which contained what I think was their first hit single ("The One I Love") - it also contained an obscure song called "Exhuming McCarthy."  This was the era when Borland C ruled the day, as unlike Microsoft C, it featured a nifty, productive IDE and didn't require a forklift to deliver.  Right around this time, the Microsoft C team was getting into trouble, as the Borland C team kept eating their lunch - getting better and better and pulling further and further ahead with each subsequent release.

Things started to change with the release of Visual C++ 1.0, and the credit for the turnaround was heaped upon legendary project manager Jim McCarthy.  His insights into the workings of software development teams and the heads of software developers were lucid, profound, and hysterically funny.  He became very popular on the lecture circuit, and was widely quoted by people who never read a word he wrote or heard a word he had to say back in the day - the telltale signs he had become a pop culture hero.

Jim left Microsoft a while ago, but was kind enough to distill his collected wisdom into a short, easy-to-read book called "Dynamics of Software Development" (ISBN 1556158238, <200 pages in length.)  I think everyone involved in software development should read this book at least several dozen times, and there was a time when everyone I knew involved in software development did or claimed to do just that (yes - back in the day.)  Needless to say, I felt pretty old when I mentioned this book and its author to a bunch of younger developers who had never heard of either.

To my knowledge, Jim McCarthy the person isn't in need of an exhumation (although he hasn't posted to his blog recently), but clearly, a whole bunch of folks involved in software development could benefit from exhuming his ideas and reading what he had to write back in day.  They still resonate 10+ years after its initial publication.

When I first tried to get my mind around LDAP via the ADSI libraries in Windows NT 4.0, I quickly convinced myself that "L" in LDAP (which of course stands for "lightweight") was an ironic joke.  Then I heard about X.500, and not a programming day has gone by since in which I haven't silently thanked the University of Michigan for getting us all off the hook for implementing DAP (go Wolverines!)

Of course, that didn't make the task at hand any easier.  Learning LDAP via ADSI was a daunting task for me - likewise for just about every other developer I've worked with.  I think this is because at its core, ADSI is a library designed to support administrative tools (consoles, scripts, etc.) and thus was *not* designed with middle-tier component development in mind.  If you attempt to use ADSI in the middle-tier (or .NET 1.1's System.DirectoryServices classes, which provide a managed wrapper over ADSI) and have a need to authenticate a large number of identities, you'll quickly find out why.

Unfortunately, there are situations in which only LDAP will do in the middle-tier, and unfortunately, there were limited options available on the .NET 1.1 platform.  To achieve high-scale and high-performance there, I ended up writing directly to WLDAP32.DLL, which is the library ADSI uses to make LDAP calls.  Techniques for calling WLDAP32.DLL from the world of managed code are poorly documented and unpleasant in the extreme (and aren't even listed on http://www.pinvoke.net), but once I got them working, they proved to be ripping fast and rock solid.  Still, I don't envy anyone attempting to go this route.

And thankfully with .NET 2.0, you don't have to.

The System.DirectoryServices.Protocols namespace now includes proper LDAP classes suitable for use in the middle-tier which provide all the functionality present in WLDAP32.DLL.  When it absolutely, postively has to be LDAP in the middle tier, this is a godsend for .NET developers.  Because after all, in its essential form, LDAP really is pretty simple and lightweight.  Directory objects have attributes, and attributes have values, and that sort of hierarchical relationship should be second nature in the era of XML.

Maybe I should start silently thanking the .NET Framework team every programming day (go Seahawks?)

Hi,

Here's a strategy hint for dealing with software vendors (and I work for a very large one.)  As a customer, if a vendor is asking you to make a small investment in an upgrade (hardware and/or software) to support their latest release, by all means take them up on their suggestion!  If said vendor senses your reluctance to upgrade an offers to help (with human and capital resources), by all means TAKE THEM UP ON THEIR OFFER!  I was in a rather amusing meeting in which the following was turned over several times:

 

vendor: please upgrade your hardware - we know that the extra horsepower is necessary for our new app

customer: we're fine - another expert told us so

vendor: you're fine on the current version - not the new version - please upgrade

customer: we're fine - another expert told us so

 

This is the same logic applied by Spinal Tap guitarist Nigel Tufnel when asked about how he got the monster volume ouf of his amp rig (quote courtesy IMdb - http://www.imdb.com/title/tt0088258/quotes):

Nigel Tufnel: The numbers all go to eleven. Look, right across the board, eleven, eleven, eleven and...
Marty DiBergi: Oh, I see. And most amps go up to ten?
Nigel Tufnel: Exactly.
Marty DiBergi: Does that mean it's louder? Is it any louder?
Nigel Tufnel: Well, it's one louder, isn't it? It's not ten. You see, most blokes, you know, will be playing at ten. You're on ten here, all the way up, all the way up, all the way up, you're on ten on your guitar. Where can you go from there? Where?
Marty DiBergi: I don't know.
Nigel Tufnel: Nowhere. Exactly. What we do is, if we need that extra push over the cliff, you know what we do?
Marty DiBergi: Put it up to eleven.
Nigel Tufnel: Eleven. Exactly. One louder.
Marty DiBergi: Why don't you just make ten louder and make ten be the top number and make that a little louder?
Nigel Tufnel: [pause] These go to eleven.

Yesterday, I got to play the Marty DiBergi part.  [:)]

 

To get out of that kind of situation, remind the customer that he can apply the hammer to the vendor if the suggested configuration doesn't work - and then send them a copy of This is Spinal Tap.

jmp

3 Comments
Filed under:

Hi,

My name is John Pelak, and I'm a Development Evangelist serving the Independent Software Vendor community in the New York Metropolitan Area.  I've been working with the Microsoft technology stack for the last 15 years, and while I won't attempt to pass myself off as an expert on any one topic, I hope that sharing my experiences in getting large-scale, high-performance solutions up, running, secured, and reliable will be helpful to ISVs facing the same challenges.

My technology focus is on SQL Server and Active Directory, and I hope that I'll be able to show how you can leverage .NET to make the most of this foundation in the products and solutions you build on them.  I'd also like to take some time here to show how the Visual Studio Team System can be used to make your organization more effiective in managing the development life cycle for your products and solutions.

Rather than repeating information that's already available from other sources, I'll use this space to provide my own insights (which I hope add value) and links to information you might find useful (such as my colleague's blogs.)

More soon...

jmp

More Posts « Previous page
 
Page view tracker