VBScript Consumption of C# enum

I have no idea how to do this.

Posted by jkhines with no comments

ASP.NET 2.0 Error Messages

December 22, 2005

I'm tackling my first migration of a web app from ASP.NET 1.1 to 2.0.  The traditional xcopy deployment works fine for 2.0 apps using the 1.1-style ASPX files and DLL code-behind.  But I'm trying out ASP.NET binary deployment mentioned in Fritz Onion's excellent article titled “Codebehind and Compilation in ASP.NET 2.0” in the January edition of MSDN magazine.
 
After converting my ASP.NET 1.1 web app using Visual Studio 2005, I chose “Build->Publish Web Site” on my development system and then xcopy (ftp, actually) deployed it to a remote system.  Browsing to the remote URL resulted in the following errors:


Error 1 : Parse Error Message: “It is an error to use a section registered as allowDefinition='MachineToApplication' beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.

Solution 1: Yep, you guessed it: ensure that the remote directory is enabled as an ASP.NET application in IIS.  If it already is, check that no subdirectory has a web.config with root-level settings in it.  I had this happen when I had nested applications - the solution is to compile the applications independently and nest them at deploy time.
 

Error 2: Parse Error Message: “Unrecognized configuration section 'xhtmlConformance'
 
Solution 2: Ensure the remote directory is enabled as an ASP.NET 2.0 application.  The .NET version can be set under the ASP.NET tab in IIS.  If you don't have an ASP.NET tab in your IIS admin MMC, you're in a different problem space.  I had to request that my ISP enable this for me.
 

Error 3: Browsing to default.aspx displays “This is a marker file generated by the precompilation tool, and should not be deleted!
 
Solution 3: Ensure you've uploaded the file PrecompiledApp.config into the root directory of your web application and that its contents look something like "PRECOMPILEDAPP updatable="false" version="2"".  Otherwise ASP.NET will assume you're using the old 1.1-style presentation/code-behind model and display the content of the ASPX marker files.
 

Error 4: Server Error; Exception Details: “System.Web.HttpException: The file '/VirtualPath/default.aspx' has not been pre-compiled, and cannot be requested.
 
Solution 4: Ensure you've uploaded all of the *.config files in the bin directory along with all of your DLLs..
 

Error 5: Applications beneath the root virtual directory break.
 
Solution 5: This is one I'm still working on.  Both / (the website root) and /code (this blog) are application-enabled.  Uploading the 2.0 version of the web.config for the root domain breaks all sub-applications.  I thought this blog would be isolated from its parent, but that's obviously not the case.  I'll have to fiddle with it some more.
 

 
Steps 1 - 4 eventually did get me a working, binary deployed ASP.NET 2.0 application, but any web applications underneath the root virtual directory stopped working.  Until I fix that, it's my hope that some of these error messages might help someone else in need.  Good luck!
Posted by jkhines with no comments
Filed under:

Visual Studio Debugging Tips

December 03, 2004

Min Kwan Park of Microsoft has the best article on the web detailing how to fix Visual Studio debugging problems.

The only error I haven't noticed on the page is due to having both .NET Framework 1.x and .NET Framework 2.x Beta installed on the same machine.  When Visual Studio creates a new ASP.NET web application, IIS will default to using the highest version of .NET, which, of course, Visual Studio is not expecting.

When you attempt to launch the debugger in this situation, you'll get a “Microsoft Development Environment” messagebox that simply says “Error while trying to run project.

The solution is to force IIS to use the proper version of .NET.  Go to Start->All Programs->Administrative Tools->Internet Information Services Manager.  Browse to your web application, right-click it, and select Properties.  Under the ASP.NET tab, change the ASP.NET version to use the production version of the .NET framework.

Posted by jkhines with no comments
Filed under:

ASP.NET HTTPException Timeout

December 03, 2004

Yesterday I had an ASP.NET web application which would fail after a few minutes with one of two errors:
a) An ASP.NET error message saying: “System.Web.HttpException: Request timed out”. (There was no stack trace information given, and it would fail at different points in the code).
b) An IE client error that said “Server Not Found” as if I'd typed in a bad URL.

Turns out they were both related to the fact that the default ASP.NET script execution stops after 90 seconds by default.  ASP.NET ignores the script timeout settings for ASP set via IIS, and looks at the machine.config and web.config instead.

I mistakenly assumed the ASP settings would roll over.  Once I added the following web.config entry to increase ASP.NET execution time to 15 minutes, things started to work:
<HTTPRUNTIME executionTimeout="900">

Posted by jkhines with no comments
Filed under:

Using RootDSE

November 24, 2004

I used to think that RootDSE was pretty magical. It would work on ASP pages without any kind of authentication and let me know which domain I was in. Which is true. Sort of.

When writing ASP pages that users from multiple Active Directory domains would see, I would use code like this:

Set rootDSE = GetObject("LDAP://SERVERNAME/RootDSE")
Response.Write rootDSE.Get("defaultNamingContext")

The output would look something like:
DC=MyDomain,DC=com

The first thing you need to know is that the domain displayed (MyDomain) has nothing to do with the user looking at the ASP page. It's the domain that SERVERNAME lives in. If you point the GetObject() call at a different server in a different domain, your defaultNamingContext will change.

The second, and arguably more important, thing to note is that getting output from rootDSE doesn't mean that you can start reading from MyDomain. rootDSE will work on ASP pages using Anonymous authentication and running as IUSR_MyServer, but GetObject() calls on users in the domain will fail if your Active Directory doesn't allow anonymous reads. You'll either need to supply credentials via the objADsNamespeace.OpenDSObject() function or configure IIS to run as a domain user.

Yep, the well-known "double hop" issue. Although opening up anonymous read access to Active Directory opens up all kinds of security holes, it does make coding easier. Which do you think is more important?

Posted by jkhines with no comments
Filed under:

Query AD for telephoneNumber in VBScript

October 13, 2004

OK -- I couldn't resist this one. I noticed a Google search hitting my page for the title topic. Since nobody ever leaves feedback, there's no way to know if any of the info here answers the question. So here you go.

In general, VBS makes it a piece of cake to retrieve information from users, groups, and computers. But actually finding the path (ADsPath) to the object is the hard part.

So to find the ADsPath, you'll want to query the global catalog of your forest for the account name (sAMAccountName). You'll also want to change the pertinant info to match your domain.

01: Dim objConn
02: Dim objRS
03: Dim objUser
04: Dim strUsername
05: Dim strTelephoneNumber
06:  
07: strUsername = "jkhines"
08:  
09: Set objConn = CreateObject("ADODB.Connection")
10: objConn.Provider = "ADsDSOObject"
11: objConn.Open
12:  
13: Set objRS = objConn.Execute(";" &_
14:   "((objectCategory=person)(sAMAccountName=" & strUsername & "));" &_
15:   "adsPath;Subtree")
16:  
17: Set objUser = GetObject(objRS("adsPath")) ' or objRS.Fields(0) - same thing
18: strTelephoneNumber = oUser.telephoneNumber
19:  
20: Set objUser = Nothing
21: Set objRS = Nothing
22: Set objConn = Nothing

 

If you already know the ADsPath to the object (that's the path in the form of LDAP://CN=Hines\, John,OU=Users,DC=MyDomain,DC=com) then you simply start at line 17. Lucky you.

In order to find what user attributes exit, use adsiedit. Personally, I use an arcane program called adsvw.exe.

Posted by jkhines with no comments
Filed under:

Variant Conversion Functions

August 17, 2004

I was converting binary SIDs to hex SIDs in VBScript for the millionth time when I stumbled across this ancient Microsoft article concerning Variant Conversion Functions (Q250344).

The COM component lets me skip doing this in VBS, which is extremely slow. It also has quite a few other functions for converting VBS's ubiquitous Variant type to other useful forms.

The Microsoft code snippet is pretty simple, even if the component's method name isn't:

Set objGroup = GetObject("LDAP://MyAdsPath")
objGroup.GetInfoEx Array("tokengroups"), 0
objSids = objGroup.Get("tokengroups")

Set oConvert = CreateObject("ADs.ArrayConvert")
For Each objSid in objSids
  strHexSid = oConvert.CvOctetStr2vHexStr(objSid)
  set objParentGroup = GetObject("LDAP://
Posted by jkhines with no comments
Filed under:

Access DFS from .NET posted

June 29, 2004

[UPDATE 09 Feb 2005: I added the sample code from this post to the NetDfsAdd definition at pinvoke.net]

[UPDATE 29 Dec 2004: I added some C# code for utilizing NetDfsAdd at the end of this post]

As an IT developer, I'm subject to the downtime schedule of my customers.  This means that I'm often informed (usually around holidays) of an upcoming change that will affect some of my many automation tasks.  This 4th of July is no exception.

I have less than two days to modify, test, and deploy four production scripts to account for several important server changes and a new dependency on Microsoft's Distributed File System (DFS).  My biggest challenge is taking legacy VBScript code and adding the ability to create DFS links.  The issue with using VBScript is that there's no p/invoke ability to use the DFS APIs in netapi32.dll, and no native COM wrapper for DFS.  I can't wrap netapi32 myself due to a dispersed install base, hatred of COM, and lack of time.

I'm resigned to shelling out (via the Exec method in WSH 5.6) to dfscmd.exe.  While it's frustrating to not have the time to dig a bit deeper, the legacy scripts must get moved over quickly and continue to work reliably over time.

How would I like to see it done?  I'd like to write an XML Web Service in C# that would p/invoke NetDfsAdd to create the link.  I'd configure Kerberos delegation and enable impersonation so that anyone in the enterprise who already has native permissions would be able to use the web service from any OS.  I'd add MS SQL logging of all transactions, including errors, so that I could track usage and trend success rate over time.  And I'd do this for every little task like this until I don't have to write code anymore.

Here is some C# code which might work if you wish to call NetDfsAdd from .NET:

[DllImport("Netapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
static extern int NetDfsAdd(
 [MarshalAs(UnmanagedType.LPWStr)]
 string DfsEntryPath,
 [MarshalAs(UnmanagedType.LPWStr)]
 string ServerName,
 [MarshalAs(UnmanagedType.LPWStr)]
 string PathName,
 [MarshalAs(UnmanagedType.LPWStr)]
 string Comment,
 int Flags);
 
void TestMethod(void) {
 // DFS_ADD_VOLUME has a value of 1
 NetDfsAdd("\\DfsRoot\Path\Dir", "FileServer", "Share", "Comment", 1);
}
Posted by jkhines with 1 comment(s)
Filed under:

Good Web Design

May 10, 2004

Ask some ASP/ASP.NET web developers what constitutes good web design and how to judge it, and you'll likely receive as many answers (or more) than the people you question.  For me, the answer is simple:
When good design goes into a web app, useful functionality and good markup come out.

“Useful functionality” is in the eye of the beholder.  And since it's subjective it's difficult to measure.

Thankfully, good markup is easy to measure.  After all the times I've done a View->Source only to be scared witless by chaotic lines of open-ended markup, I wish Microsoft would bake-in the W3C Markup Validation Service into every single one of its web-enabled tools.  Now that Visual Studio is hardwiring CSS into ASP.NET web apps, the W3C CSS Validation Service would be another welcome addition.

In the meantime, you can manually validate your web app output using these tools.

Lastly, if you're interested in creating good web sites that look the same in all modern browsers (without hacks), I'd recommend Jeffrey Zeldman's book Designing with Web Standards.  And Douglas Bowman deserves a mention in this Microsoft-oriented blog for his guerilla re-write of Microsoft's web site.

Posted by jkhines with no comments
Filed under:

Microsoft Certification for MCSD.Net

Microsoft Exam 70-315
June 18, 2004

I'm glad to note that I passed Microsoft certification exam 70-315 today with a score of 824. Prometric gave out a score sheet after the exam with the total points and bar graphs that guage competency in areas like "Creating User Services" and "Consuming and Manipulating Data". This was my first Microsoft exam.

In order to prepare for 70-315 I read the MS Press 70-315 study guide for a general overview of what MS wants you to know about ASP.NET.  To brush up, I bought the Exam Cram 2 70-315 guide on a lark and ended up scouring four chapters a day right up until the test. It's far, far superior to the MS book and its questions are in MS exam format. I wouldn't have passed by reviewing the MS book alone. Preparing for the test with the MS book's exam and the Exam Cram practice tests were a good combination.  By the time you take the test you should be scoring at least 90% on all of the practice tests.

As far as the exam goes, it places a huge emphasis on ADO.NET. I think the Exam Cram book really nailed the key areas: Know all of the ADO.NET classes for SQL, the right way to use them, and when to use the right one. Know how to invoke Transactions and work with XML.  Know the web.config and how to register controls.  And if you're weak on any of the areas, simply build a test web app and work your way through it. As of last week, a lot of the broader areas, such as Custom Controls, Custom Events, and Globalization, were all completely new to me, so I'm pretty happy with the results.

One nitpick: My testing machine was a museum piece whose monitor had a refresh rate of 60Hz.  At this rate, the monitor looks like it's constantly flashing, and I thought I was going to be sick for the first half hour.  The staff at the test center said there was nothing they could do.  Doesn't MS have some kind of minimum hardware spec for these systems?

Problems with Prometric
Saturday, August 14, 2004

After a horrible testing experience yesterday, I felt I had to document some issues with Prometric, a vendor of Microsoft exams.  Hopefully other people can avoid these issues by avoiding Prometric.

I used Prometric's web site to schedule exam 70-300 for August 6th.  No problem.  On August 4th, I used Prometric's web site to push the test out one week to August 13th.  Yep, Friday the 13th.  I should've known.

I showed up at the testing center on the 13th to the greeting: “You're not on our schedule today.“  Apparently Prometric's web application broke in the middle of the reschedule.  While Prometric's web site showed that my test was rescheduled, the testing center was never notified.  In fact, they still had me down for the 6th listed as a “No Show”.  Even worse, the testing center was booked had no openings until the following week.

To review: I scheduled the test.  I rescheduled it and got a confirmation email.  I studied a lot.  I took time off work and showed up early.  And I couldn't take the test.

So here's what the testing center (New Horizons in Beaverton, Oregon) did for me.  They called their helpdesk and changed their business hours so that it looked like they opened at 6am.  Then they scheduled me for the fake 6am slot and set up a computer for me to take the test.  After more wrangling with Prometric, they got Prometric to agree to reschedule me for 6am and download the 70-300 exam to the new computer.

But the Prometric download wasn't working.

To review: I got an opening at the testing center.  I got a test machine to take the exam on.  And I couldn't take the test.

After an hour and 40 minutes of dealing with all of these issues, I left.  I certainly wasn't in the frame of mind to take an exam.  I went home and dealt with Prometric registration staff (again).  The conclusion?  They'll call me back on Tuesday.  The only thing they were able to tell me for certain is that the two “No Shows” (on for 8/6 and one for 8/13 at 6am) can be taken of my record.

Advice for Prometric: If you're a company that sells exams on how to write web apps, you should hire people who take them.

Microsoft Exam 70-300
September 03, 2004

I squeaked by Microsoft Exam 70-300 today with a score of 700 (with 700 required to pass). I absolutely would not have passed if I hadn't found this post by Jason Haley.

I had heard that this exam was easy and that it could be passed with half a day of studying. I found the exam to be on the hard side, as there's no hands-on technical work you can do to understand architecture. While experience helps, this is not an easy exam.

I'll focus on three things: studying for the exam, passing the Transcender practice exams, and taking the actual exam.

Studying for the exam
I focused on the Exam Cram 2 70-300 study guide and Chapter 1 of the MS Press book. You have to drop all of your own architecture habits and use the information given in these books only.

Take the Exam Cram 2 practice exams last but only as a knowledge guage. They are not in the same format as the real exam. Like Jason says, only use them as a guideline - if you get over a 900 on both Exam Cram 2 practice exams the first time you take them, schedule the real thing. Otherwise, get the Transcender product.

Passing the Transcender exams
If you're like me, you understand what it takes to get a product designed, built, and deployed. But if you've never taken a case study exam, this knowledge is essentially worthless. This is where the Transcender practice exams help immensely. Their format for reading a case study and navigating/answering questions is just like the real exam.

Warning: The Transcender exams consist of three case studies and a fixed set of questions. Once you take a case study, you've seen all of the questions. You can randomize the order, but you will never see new questions. This means you should save at least one case study until near your exam. Don't run through all of the case studies and assume the questions will be replaced with new ones like the MS Press book exams.

Once you're easily passing the Transcender exams (including that one you saved for last) you'll be ready for the real thing. Arm yourself with a good note-taking plan, confidence with case studies, and enhanced reading comprehension skills.

Taking the real exam
Point one: Don't use the number of questions as a guide for managing your time (plus my exam intro gave the wrong number of overall questions). Everything I've read and experienced indicates that there are three case study questions. Break down your 120 minutes accordingly, knowing that at least one case study is likely to be extremely complex. Know your own average times for taking notes and answering questions.

Point two: WATCH THE CLOCK. I had some wierdness on my 'remaining time' clock not adding up to two hours for all of the case studies. If I had to do it again, I would have ignored the in-program clock and allocated 40 minutes for each case study. The clock really was my enemy on this exam.

Point three: Be sure that you're completely happy with each case study before you move on. Since I was too rushed for time, I ended all of my case studies thinking I could go back and review them. Once you click 'Next' from the last question on the last case study, you're done. I didn't get a chance to review my last case study at all.

Point four: I feel that 70-300 is really about relationships. If you can read a case study and draw a detailed Entity Releationship Diagram, 60% of the remaining questions will be answered. I recommend getting the list of tables from the questions and laying them out as you read the case study. Then you can draw your ERD and answer questions about ORM, cardinality, query performance, and logic flow.

My nervous rush to get through all of the case studies almost cost me a passing grade for this exam. If you're methodical and treat it like another run through the practice exams, you'll be just fine. == Don’t Use Prometric for Microsoft Exams posted @ Monday, April 18, 2005 1:19 PM

Last Autumn I posted "Problems With Prometric" detailing some harrowing testing experiences I had with Thomson Prometric, a vendor of Microsoft certification exams.  The summary is that Prometric screwed up my exam schedule so badly due to technical glitches that Prometric eventually agreed to give me an exam for free.

Today I called Prometric's customer service (1-800-775-3926, option 2) about my ticket, #1977354.  I'm ready to take my next exam (70-316), and wanted to schedule the free exam I was due.  Guess what?  After being on hold for 30 minutes, I was told that nothing in my ticket (owned by David) mentioned a free exam.  Prometric refused to give me the free exam that David had promised me verbally over the phone.

So let's review my experiences with Prometric: Technical glitches that drop me off the schedule, break the reschedule, and break the download of the actual exam.  Hold times of 20 to 30 minutes for each and every call, and TERRIBLE customer service.  And last but not least, not living up to their promises.  Studying for exams is hard enough without all of these distractions!

You're looking at a Pearson Vue customer for life.

Microsoft Exam 70-316
May 06, 2005

Today I passed Microsoft Exam 70-316 with a score of 940.  I have two exams left before I obtain MCSD certification.

It's been eight months since my last exam and I had never jumped into the GUI world before.  So in that long period of time I really wanted to understand Windows Forms, and did a lot more than you need to do in order to pass the exam.
1. I went through the MS Press 70-316 study guide to get a feel for Microsoft's emphasis.  It wasn't very useful.  I found the practice test to be helpful in guaging my general knowledge, but I didn't need to it to pass the exam.
2. I went through Chris Sell's Windows Forms Programming in C# while writing a complex, multithreaded Windows application.  The book is an introductory-level overview of Windows Forms and is a good replacement for the Petzold or Prosise books.
3. I went through the Exam Cram 2 70-316 study guide.  Of the three Exam Cram books I've studied, this was the most pertinent and did the best job of pointing out my weak spots.
4. Lastly, I took the best instructor-led class I've had, class 2555A led by Chris Tavares.  Chris answered all the questions I had from Sells' book and more.  I've taken loads of training before, and I'm still telling people about how great Chris' class was.

As for the exam itself, well, my version was really, really easy.  I can't really detail what wasn't on the exam for giving away what it contains.  But just getting a simple multithreaded Windows application working with help, validation, and configuration covered much more than this exam.  By emphasizing ADO.NET so prominently, Microsoft are omitting many other important areas of programming.  I could have passed this exam with just a basic understanding of the technology.

If you want to get through the exam, just review the Sells and Exam Cram books.  Personally, I'd like to follow the above pattern for all of the MCSD exams, but I don't have 40 months of my life to dedicate to it.  I'm hoping to punch out Web Services and my elective as fast as possible and move on.  Especially now that I understand that MCSD really means you've been introduced to the technology, and aren't necessarily a master of it.

Microsoft Exam 70-320
August 12, 2005

Today I passed Microsoft Exam 70-320 with a score of 952.  I have one elective exam until the MCSD certification is mine.

I have to start by saying I was suprised to score so highly. Although I've been using Web Services in production since early 2003, I hadn't done much with the server components that are also on the exam. I felt I was weak on the COM+ features implemented by serviced components. The epiphany I had while preparing this time was "It doesn't matter whether the textbook answer is right if you're wrong." In other words, the best approach to Microsoft exams is to first eliminate wrong answers, regardless of the question.

To prepare, I went through Keith Ballinger's book .NET Web Services. I thought it was too focused on internals the first time I went through it, and really enjoyed it as I reviewed it. My conclusion is that it's a great book for anyone who's going to write, deploy, and (most especially) debug web services. Just understand that it's a single component of this exam. I then did three chapters a week of the Exam Cram 2 70-320 study guide and did a week of review.

I powered through the practice tests, but was really worried for the first half of the real thing. I was weak on serviced components and their performance and deployment issues, and reviewed about 40% of the questions before I finished. I still have mixed emotions seeing so much database and XML focus on these exams, but seeing them for the third time helped my overall score. My web service experience really paid off. I don't have the mastery of 70-320 that I had of 70-316, but knowing how to eliminate the wrong questions got me a higher score on this exam.

If you want to get through the exam, just review the Exam Cram book. I'm full of so many opinions about the entire MCSD process, but I'm saving them for a single post on improving the process. So far my MCSD experience has been like taking five multiple choice tests on the grammar of French, Greek, Russian, German, and English. It's a good overview and a great way to stay current, but you have to use it daily in order to be fluent.

Microsoft Exam 70-229
November 10, 2005

Today I passed Microsoft Exam 70-229 with a score of 908. This ends my five exams for the MCSD.NET certification with an average score of 865.

I started studying for this exam on October 3rd and took it on November 10th. To prepare, I did the bare minimum: I went through a chapter per day of the Exam Cram 2 70-229 study guide, got sick for two weeks, and did a week of book review and a week with the Transcender practice test.

The Transcender exams are really what got me through 70-229. I felt that the Exam Cram 2 questions were too easy, and I was right. By reviewing the concepts behind the Transcender questions I was extremely well prepared to take the exam. For once I wasn't frustrated at the amount of database information on an MCSD exam, I was just wondering why a software engineer would ever need to know when to use clustered or nonclustered indexes. Fortunately I've used MS-SQL for years and had taken 70-300, which is 80% database design. This really cut down on the amount of new material I had to review

The exam itself was one of the more focused of the MCSD exams because it didn't need to mix MS product recommendations with technical questions. I was surprised that I didn't have a single question designing complex table relationships. However most of the questions are based on a good understanding of foreign keys, indexes, views, and constraints. The exam posed when the best time was to use each technology rather than doing a deep-dive into how each technology works.

If you want to get through the exam, you need to be fluent in T-SQL and have experience with the Enterprise Tools. SQL Books Online is really the best reference out there. From there you can pick any study book and test out each concept as it's introduced. I'm happy to say I'll probably use some of this knowledge in the future, but not as a DBA.

Thoughts on MCSD Certification
November 10, 2005

After more than a year of effort and two weeks after Microsoft announced a new Microsoft certification track, I passed the final exam required for my qualification as a Microsoft Certified Solution Developer for Microsoft .NET. I wanted to consolidate many of the thoughts I've had through the entire process as advice for others who would have considered this certification.

It's easy to pick where to begin - my biggest lesson was what MCSD.NET really means. I used to think that an MCSD would be an elite developer who could explain non-deterministic finalization to me, or how the CLR uses garbage collection statistics to help the OS determine a process' virtual memory size. This is not the case. MCSD means that the developer has been exposed to broad .NET concepts: the concept of how .NET works, how desktop, web, and distributed apps should work, and good ways to run a database or ensure security. If you want to be an expert in a particular part of .NET, it's better to subscribe to the expert blogs, keep up on the trade journals, and dedicate time to actually writing code.

This brings me to my second point - the certification so far too broad. I don't feel like I've developed a depth of expertise in any specific technology, including basic language syntax. The features that make .NET apps cool are ignored or lightly touched on in the certification. Things like advanced IIS role-based security, desktop app multithreading techniques, cross-OS web service interoperability barely scratch the surface of my memory from the exams. ADO.NET is emphasized too much and tested too frequently. The core MCAD exams share 25% of their questions on ADO.NET alone. You won't get specialized knowledge by getting certified. If you want to be a master of a small part of .NET, identify what that part is and learn everything you can about it.

My last point has to do with the exams themselves. While it would be difficult to walk in off the street and pass an exam, they're still too easy. If Microsoft wants to make the exams a challenge, multiple choice has got to go. There must be some real coding done on the exams. There should be more of a Computer Science emphasis than a "what product to use when" emphasis. Even on the multiple choice, a base level of knowledge means test takers can eliminate obviously incorrect answers to guess at the right solution. And lastly, the exams are mirrored too closely by the practice exams available. By the end of the track I've mastered how to take Microsoft certification exams as much as the material itself. Now I understand how some people pass five tests in a couple of months. Microsoft needs to do better to keep the exams relevant. Otherwise, what does a certification really mean?

I'm going to end on a positive note. A year ago I had only touched the surface of .NET and felt like my programming skills were slipping into obsolesence. Getting certified motivated me to get a baseline understanding of .NET to the point where I'm a reference for friends. I've written web services, web apps, console apps, desktop apps, Windows services, stored procedures, user-defined functions, and remote objects. I'm starting to feel like a software engineer again. If you're coming from nothing, certification is a great way to get up to speed on the minimum you need to know. Don't be afraid to do it - there's an entire industry out there to help you succeed. Once you've gotten your MCSD.NET or Enterprise MCPD, you'll be ready to choose your favorite area and really get to know what .NET is all about.

Good luck!

Posted by jkhines with no comments
Filed under:

Working with Windows Group Membership

May 06, 2004

[UPDATE 14 May 2004: If you plan on adding members to AD groups from .NET 1.0 or 1.1, be aware of the following bug: http://support.microsoft.com/default.aspx?scid=kb;en-us;818031]

I've had to determine user group membership in quite a different number of ways; local groups, domain groups, domain groups with nesting. Sometimes I've known what group I was looking for, sometimes not. Microsoft does provide code to do all of this -- the downside is that it can be extremely slow (like IADsGroup.IsMember in an Active Directory Domain). This is an attempt to list out my best known methods and great web resources for working with group membership.

1. Group Membership Scripts

First off, Hilltop Lab has one of the most well presented sites on ADSI scripting for Windows NT 5.x environments I've found on the web. All of the code we care about is in the section labeled Group Membership Tests. The list there is more complete than anything I've found on the MS site.

The only thing I'd add is that always enumerating the IADsUser.Groups attribute can be slow, especially in interpreted VBScript and also if the user is a member of many groups. I've found the fastest way to check for a single group membership is the following:

Dim oUser
Dim strGroups
oUser = GetObject("LDAP://CN=Hines\, John,DC=MyDomain,DC=com")
strGroups = Join(oUser.memberOf)
If (InStr(strGroups, "CN=MyGroup,") <> 0) Then
' user is a member! for me, this test is ~10x faster than enumeration
End If

 

If you're looking to display the groups later, you can Join( ) with any special character that's invalid in a group ADsPath and then Replace( ) it with a vbCrLf.

2. Finding Nested Group Membership: VERY USEFUL

The key to enumerating nested groups is the"tokenGroups"; attribute, which must be explicitly copied down from the domain once you've bound to the user object. Microsoft has an example of enumerating tokenGroups in VB Script in Q301916 (http://support.microsoft.com/default.aspx?scid=kb;en-us;Q301916). I haven't done any tests comparing the tokenGroups method to walking up the IADsGroup.managedBy tree, but it's more intuitive and non-recursive.

3. Finding Group Membership in ASP.NET

The fastest way to determine group membership in ASP.NET is through the user of the Security Principal objects.

using System.Security.Principal;

WindowsPrincipal user = (HttpContext.Current.User as WindowsPrincipal);
if (user.IsInRole(@"MyDomain\MyGroup")) {
// user is a member!
}

If you're curious about more ways to work with role-based security in ASP.NET, I'd recommend Implementing Role-Based Security with ASP.NET and this advanced article about Scalable ASP.NET Expanded User Properties Model.

4. Finding the Groups a User can Manage

Lastly, once you know what groups your user is in, you may then want to know what groups they have permission to manage. For direct management you'll want to check the IADsUser.managedObjects property. For direct and inherited management it's preferable to submit an ADO query and have the domain find out for you.

The algorithm I use constructs an ADO query filter that essentially says “Return every group that is directly managed by my user or any group my user is a member of”. Be aware this misses things like permission settings on OUs, but it's the best method I've found so far.

using System;
using System.Text;
using System.DirectoryServices;
using System.Collections;

string username = "MyDomain\jkhines";

//
// perform an AD search for the user object
// assume username is set to MyDomain\MyUser
//
DirectorySearcher ds = new DirectorySearcher();
ds.SearchRoot = new DirectoryEntry("LDAP://DC=MyDomain,DC=com");
ds.SearchScope = SearchScope.Subtree;
ds.Filter = String.Format("(&(objectCategory=person)(sAMAccountName={0}))",
username.Split('\\')[1]);
ds.PropertiesToLoad.Add("adsPath");
SearchResult userSearch = ds.FindOne();
string userAdsPath = userSearch.Properties["adsPath"][0].ToString();

//
// bind to the user object and get the tokenGroups
//
DirectoryEntry user = new DirectoryEntry(userAdsPath);
user.RefreshCache(new String[] {"tokenGroups"});

//
// start construction of a query filter of all the groups the user belongs to
//
StringBuilder filter = new StringBuilder();
filter.AppendFormat("(|(&(objectCategory=group)(managedBy={0}))",
user.Properties["distinguishedName"][0]);

DirectoryEntry group;
byte[] currentSID;
StringBuilder hexString = new StringBuilder();
IEnumerator groupList = user.Properties["tokenGroups"].GetEnumerator();

while (groupList.MoveNext()) {
//
// convert the byte[] SID to a friendly SID string
//
currentSID = (byte[])groupList.Current;
foreach (byte inByte in currentSID) {
hexString.AppendFormat("{0:X2}", inByte);
}

//
// bind to the group to get the distinguishedName
//
try {
group = new DirectoryEntry(String.Format("LDAP://",
hexString.ToString()));

filter.AppendFormat("(&(objectCategory=group)(managedBy={0}))",
group.Properties["distinguishedName"][0]);
}
catch(Exception err) {
// ignore errors if this is a local group
}
hexString.Remove(0, hexString.Length);
}
filter.Append(")");

//
// set new searcher filter
//
ds.Filter = filter.ToString();

//
// execute new query and display results
//
foreach(SearchResult groupSearch in ds.FindAll()) {
// use groupSearch.Properties["adsPath"][0];
}

Thursday, July 15, 2004 1:59 PM

Today I stumbled on posts by Joe Kaplan, an MVP for ADSI, showing an unsupported but great method for listing nested Windows group membership without requiring an enumeration through the tokengroups attribute.

Visual Basic code can be found in the original articles here and here.

Joe posts the following warning: “Do not use this in production code as reflecting on private members is NOT a good idea and the implementation may change in a future version of the Framework!”  Here's my stab at a  C# version of Joe's .NET Framework 1.0-1.1 code:

using System;
using System.Reflection;
using System.Security.Principal;

class Class1 {
 static void Main(string[] args) {
  string[] roles = GetRoles(WindowsIdentity.GetCurrent());

  foreach (string role in roles) {
   Console.WriteLine(role);
  }
 }

 static string[] GetRoles(WindowsIdentity identity) {
  Type idType = identity.GetType();
  object result = idType.InvokeMember("_GetRoles",
   BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
   null, identity, new object[] { identity.Token }, null);
  return (string[]) result;
 }
}

However I'm surprised that MS didn't publicly expose _GetRoles.  The supported method of enumerating through tokengroups SIDs requires binding to a Domain Controller in order to fetch the friendly name, and it doesn't cover custom roles.  Why not just make _GetRoles a public method?

As of Whidbey Beta1 (.NET Framework v40607) it looks like MS may have done just that.  While I'm having issues with the beta, here's an example from the Help:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Web.Security"%>
<SCRIPT runat="server">

string[] rolesArray;

public void Page_Load() {
RolePrincipal r = (RolePrincipal)User;
rolesArray = r.GetRoles();
UserRolesGrid.DataSource = rolesArray;
UserRolesGrid.DataBind();

Heading.Text = "Roles for " + User.Identity.Name;
}
. . .

Past Comments


group code
hi john, i have been trying your code to get the ldap group. i cannot get it to work. below.


if the userid is m-----y and the domain is cabrini.edu then how do you fill in the string? should this be able to run alone on an .asp page?
11/19/2004 8:23 AM | mike b  re: Working with Windows Group MembershipUse the ADSI Edit utility to determine your ADsPath for the GetObject call. This url has a brief walkthrough of ADSI Edit:
http://www.w2kcfg.net/adsi_edit.htm

The code will run from an ASP page if your Active Directory configuration allows anonymous read access. Here are a few articles on accessing AD from ASP:
1. http://www.serverwatch.com/tutorials/article.php/1476961
2. http://www.serverwatch.com/tutorials/article.php/1478231
3. http://www.serverwatch.com/tutorials/article.php/1482281
11/19/2004 8:24 AM | john re: Working with Windows Group Membership [Note: edited for length -- jkh]

This issue all started because the code I am using currently which uses winnt sometimes just stops working and they have to reboot the webserver. Here is the line that stops working
Set oADsObject = oADsNamespace.OpenDSObject(strADsPath, tempstr, strPassword, 0)

I have tried many scripts and some work and some do not. For example--this works on a asp page on the server.
set rootdse = getobject("LDAP://SERVERNAME/Rootdse") note-I must insert server name otherwise I get nothing.
response.write "subschemasubentry" & rootdse.get("subschemasubentry")&"

so I know the asp page can get info without needing a user name to sign onto the ldap server. But I cannot get thru the line starting with oUser.Faculty&Staff is a group

Dim oUser
Dim strGroups
oUser=GetObject("LDAP://SERVERNAME/CN=Users,CN=MYUSERNAME,dc=MYDOMAIN"
)
strGroups = Join(oUser.memberOf)
If (InStr(strGroups, "CN=Faculty&Staff,") <> 0) Then
response.write "hello"
End If

The server is either w2000 or w2003. Is it really possible for this code to work? It seems to simple to be true.
11/24/2004 5:57 PM | mike b re: Working with Windows Group Membership It looks like you're running into either a bad GetObject call or a security context issue.

Firstly, you have to use 'Set' on your GetObject call and fix the LDAP Path:
01: Set oUser = GetObject("LDAP://SERVERNAME/CN=MYUSERNAME,OU=Users,dc=MYDOMAIN")
Use ADSI Edit to ensure you have the right path, some businesses have a CN that differs from USERNAME.

If changing that one line doesn't just magically work (and it will if your Active Directory allows anonymous queries), then you have a security context issue and need to pass credentials to the Domain Controller via your code or IIS.

Doing it from code is pretty simple:
02: Set objADsNamespace = GetObject("LDAP:")
03: Set objUser = objADsNamespace.OpenDSObject(strADsPath, strUser, strPassword, 0)
04: Response.Write Join(objUser.memberOf)
05:
06: Set objUser = Nothing
07: Set objADsNamespace = Nothing

Note that the strADsPath in line 03 is the same string that was passed to GetObject() on line 01, and that strUser is in the form of "DOMAIN\USERNAME". I numbered the lines just for convenience.

You can also configure IIS (under Properties->Directory Security->Edit) to run as a domain account or use Basic Authentication (under SSL) to work around this.
11/24/2004 6:00 PM | john re: Working with Windows Group MembershipHi,

My Name is Roberto T.

I am a bit lost on the following snippet of code from above:

group = new DirectoryEntry(String.Format("LDAP://<SID={0}gt;",
hexString.ToString()));

"LDAP:// ...."

what am I supposed to enter here. and how do I use adsi to help find the right path for this. I guess this is the final component to resolve the sid to the actual group name.

thanks for all your help...
12/5/2004 10:03 AM | rtorres1228@yahoo.com re: Working with Windows Group Membership Roberto,
Firstly, the gt; is a formatting error from the .TEXT interface, it's meant to be the closing angle bracket (>). I'll fix the article.

Second, you're right that it's the final component for getting the friendly group name. Check out http://support.microsoft.com/default.aspx?scid=kb;en-us;Q301916 - it's a simpler example for using the SID binding.

You're not going to enter a SID value in here by hand. Under the example hexString is the value that's being passed, and hexString just holds one SID from the user's tokenGroups attribute.
12/5/2004 12:34 PM | john re: Working with Windows Group Membership [Note: edited for length -- jkh]

John I tried the code you sent me and it works. I managed to determine which group a person is in also.

But our system has some exchange users and some not. Because of that, exchange users have a path like
strADsPath = "LDAP://SERVERNAME/CN=Lastname\,
Firstname,CN=Users,dc=MyDomain"

So the code only seems to work if
"LDAP://SERVERNAME/CN=theirusername,CN=Users,dc= MyDomain"
12/5/2004 1:12 PM | mike b re: Working with Windows Group Membership Mike,
At this point I only have time to point you at a couple
links.

1. How To Use ADO to Access Objects Through an ADSI LDAP Provider
http://support.microsoft.com/default.aspx?scid=kb;en-us;187529

and the Microsoft Script Center for Active Directory
http://www.microsoft.com/technet/scriptcenter/scripts/ad/default.mspx

Between these two, you should be able to get what you need
12/5/2004 1:13 PM | johnre: Working with Windows Group Membership John,

I appreciate your prompt response. You are very generous with your time.

However, I looked at the link you had listed. It is done in Visual Basic. My limitted expertise is in C#. If you can point me to another example done in the later I would be much appreciative. Also I think my problem stems with my lack of familiarity with ADSI programming. When you try to get a "DirectoyEnty" using the hexString as a parameter to "LDAP://<SID={0}" don't you need to prefix this with
"LDAP://distiguished-name of some node/<SID={0}". Forgive me if I am using the wrong terms here. I find it hard to see how it would now where to look?

thanks
12/5/2004 3:50 PM | rtorres1228 re: Working with Windows Group Membership I meant "LDAP://<SID={0}>,..."

How does it know where this is will mapped. To say the least It is now working for me. I get an unkown error when I try it.

thanks

12/5/2004 3:52 PM | rtorres1228

re: Working with Windows Group Membership

I mean it is not working...

12/5/2004 3:52 PM | rtorres1228

re: Working with Windows Group Membership

John,

I found out where the problem was. I found this article:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ad/ad/binding_to_an_object_using_a_sid.asp

where it explains:

"LDAP://servername/<SID=XXXXX>

In this example, servername is the name of the directory server
and XXXXX is the string representation of the hexadecimal value of
the SID. The servername is optional. The SID string is specified in
a form where each character in the string is the hexadecimal representation
of each byte of the SID."

The machine I am development from is not a member of the AD domain. Once I typed the name of the DC in place of Servername I was working.

Thanks for your help!

(:-)

12/5/2004 4:43 PM | rtorres1228

Posted by jkhines with no comments
Filed under:
More Posts