Using NuGet with TFS

Amongst other things, I use TFS and NuGet. When I move to a new machine, TFS will give me the code but the packages will be missing.

I tried the solution context menu command to “Enable NuGet Package Restore” and checked it in but when I updated the solution files on the other machine it made a complete pig’s ear of things and half of the projects in the solution wouldn’t load because of missing files. So I backed that out…

ASIDE: why is it so hard to back out a big check-in, in TFS? Surely, one of the major reasons for source control is to save your bacon? So, why is it so hard to back out a change?  Why can’t I simply point at a check-in and go “Revert to this point”.  Yes, it should ask me if I’m sure.  Possibly, it should point out how dangerous and possibly stupid this is.  But it should be that easy, not like this. [Not that I’m complaining about that post – saved me lots of fun.]

… and had a poke around t’Internet. As shown here, it’s easy to get the packages.

First, to download the packages, you need to get nuget.exe, which you can find here.

Now, my solutions are often quite large and I wanted an easier way that running that for every project folder. A bit of fiddling with PowerShell (which is also always harder than I think it should be) and I arrived at this one-liner which iterates through your solution folder and downloads missing packages:

PS C:\Users\Public\...{SolutionDir}\.nuget> gci -include "packages.config" -recurse ..\ | % { .\nuget install $_.FullName -o Packages }

nuget.exe is in the .nuget folder where Enable NuGet Package Restore put it.

If you try it, you should see something like this:

All packages listed in packages.config are already installed.
Successfully installed ‘CommonServiceLocator 1.0’.
Successfully installed ‘Prism.UnityExtensions 4.1.0.0’.
Successfully installed ‘Unity 2.1.505.0’.
Successfully installed ‘Prism 4.1.0.0’.
Successfully installed ‘SharpSerializer 2.18’.
All packages listed in packages.config are already installed.
Successfully installed ‘NUnit 2.6.0.12054’.
Successfully installed ‘NSubstitute 1.4.2.0’.
All packages listed in packages.config are already installed.
All packages listed in packages.config are already installed.
All packages listed in packages.config are already installed.

I haven’t tested it extensively so, as with everything here, use it advisedly.

Microsoft JScript runtime error: Sys.ArgumentException: An element with id ‘Form1’ could not be found.

I’ve been trying to add some Ajax to a Web Forms page.  It’s the first time I’ve used Ajax since dabbling with it when it was lauched.  I’ve had a problem:

“Microsoft JScript runtime error: Sys.ArgumentException: An element with id ‘Form1’ could not be found.
Parameter name: elementOrElementId”

I just found the solution here.  I had added a script using:

  <script src="Scripts/silverlight-detector.js" type="text/javascript" />

and when I changed it to:

  <script src="Scripts/silverlight-detector.js" type="text/javascript"></script>

it worked.  I know I’ll make this mistake again because I don’t like empty tags (uuuuuugly) and always write empty ones in the shortened form.  Since it was far from the first hit on the search, I’ve reproduced it here so I can find it again, quickly.

Poor UI Design – iTunes

I hate iTunes.  It seems an odd thing to say about a piece of software.  But I do.  My iPhone cost me a fortune.  As far as I’m concerned, it should be an appliance.  It should be as simple to operate as a stereo.  On the whole, the phone is.  But using iTunes is a painful experience, every time.

Now, I’ll accept that some of this is a Windows-bias.  I object to having a Mac-styled app thrust upon me.  But here are two concrete, and indefensible, examples of the garbage that it iTunes.

Background

For no reason that I am aware of, iTunes ‘lost’ its configuration.  There was no error message: one day, it was downloading podcasts and the next it wasn’t.  This has happened before.  I tried, in vain, to copy the podcast definitions from the iPhone to iTunes but I couldn’t see how.  So, I started to set them up again.  Now, I tend to listen to podcasts in batches.  Some of the ones on my phone weren’t still downloadable.  So, I had to listen to all of the old podcasts before I could re-sync because iTunes overwrites everything in this situation.  Grrrrr!

That week, I downloaded a couple more apps.  I’d downloaded a few and they’d just overflowed the existing layout and appeared on a new pane, as they do.  So I went to iTunes to organise them.  It had lost the layouts as well.  When I clicked the Sync button on the apps page the old layouts which had been visible in iTunes (but sort-of greyed-out) were replaced with apps spread over twelve panes and random folders, sometimes two or three to a pane.  It took me over an hour to copy the existing layouts from the phone to iTunes, just so i could re-sync them back and add the new apps to the old folders.  Grrrrr!

Example 1

Anyway, on to the first example.  I realised later that iTunes had also lost all of my music.  And books.  And films.  In fact, everything.  So I tried to work out how to get them back.  I knew the music would be in folders so I had a browse round, found a likely candidate and tried File > Add Folder to Library… etc.

It started asking me this:

iTunes dialog box which reads: The app "Books" already exists in your iTunes library.  Do you want to replace it with the one you are moving?

It asked me it a lot.  In fact, 238 times, one for each app.  Actually, I’m guessing: I didn’t count.  I just clicked.  For a long time.  Who thinks this is the right way to do this?

But this isn’t really the problem.  Read the dialog again.  ‘Replace’ or ‘Don’t Replace’.  Which should I pick?  What are the consequences of picking either?  Why doesn’t it tell me?  There is a similar dialog which warns be I’m about to replace a newer version which suggests that in this case the versions are the same or the replacement is newer.  But it doesn’t tell me which of those.  I picked Don’t Replace.   I still don’t know if I did the right thing.

I don’t really understand why the dialogs appeared at all.  But I don’t want to: it’s an appliance.  Except that it’s not.

Example 2

Here’s the second example.  At the end of the process described above, I clicked on TV Programmes in Library.  This appeared:

Again, what are the consequences of clicking either?  If I click Don’t Apply, will I lose everything I’ve just done or will there be an opportunity to apply it later?

I clicked Apply.  It’s still syncing.  [I actually typed sinking there, which is how I feel now!]

And a dialog box appeared mid-sentence; clearly, I typed a relevant letter and it’s closed so I have no idea what I’ve done there.  Mind you, I can’t blame iTunes for that: responsiblility for windows being able to take the keyboard focus sits with MS – they have a lot for which to answer with that design decision.

Making Wrong Code Look Wrong – Joel on Software

It’s an old post but it was new to me: Making Wrong Code Look Wrong – Joel on Software where Joel talks about the differences between Apps Hungarian and System Hungarian prefixes and argues in favour of the former.

I’ve been a user of System Hungarian (in my vbScript days) without realising that there were two dialects, or that I was using the ‘wrong’ one.

The idea behind Apps Hungarian is definitely food for thought…

Fun with IIS 7.5 and Kerberos

I’ve recently had some fun setting up load-balanced IIS 7.5 (Server 2008 R2) servers to use Kerberos with the Default Web Site running under an AppPool which uses a domain account.

Now that my monitors have been replaced, I’ve got my new keyboard and have glued my mouse buttons back on, I thought I’d share some resources that helped.

First, Ken Schaefer’s series on Kerberos itself, to remind me how it works:

Then Brian Murphy-Booth (a.k.a. Brian Booth) ‘s excellent tool DelegConfig:

The last one of those has four sections: Installing IIS; Installing the web application; Configuring Network Service as the AppPool Identity; and Configuring a domain based user account as the AppPool Identity.  This last section held the key for me: I’d remembered everything else from a previous smashing-my-face-into-the-desk exercise except unchecking “Enable Kernel-mode authentication” in the Advanced Settings on the Windows Authentication entry of the Authentication feature.  Talk about hidden away…

So, now it all works.  Today.  If I’m feeling brave, I’ll restart everything tomorrow morning and see if it’s still working. 🙂

EDIT

I realised that the instructions in Addendum: Making the DelegConfig website work on IIS 7 refer to the Classic .NET AppPool.  My servers are built with an unattended build process so my starting point was an R2 server with IIS installed and the .NET Framework 4.0.  I created my own App Pool called DefaultWebSiteAppPool [there’s a time for originality and there’s a time for sticking to the bleedin’ obvious] with .NET Framework version v4.0.30319 and Managed Pipeline Mode: Integrated with the identity set to my domain account.

 

How to query the Active Directory Schema using LinqToLdap

When creating ClassMaps for earlier articles, I’ve needed info from the AD Schema.  Earlier, I thought, “Why not do this using LinqToLdap?”  So I did.  In this article, I show you what’s needed so you can do the same.  I build on earlier articles, Creating a directory objects assembly for LinqToLdap, in particular.

Overview

First, I added a couple of properties to Top.cs: adminDescription and adminDisplayName. I’d already created a new subclass of Top, TopX, to hold attributes for other directory object classes I haven’t blogged about, yet. I’m not keen on the name, so it may change. Feel free to offer alternatives.

Then, I created new directory object class files called ClassSchema.cs and AttributeSchema.cs with matching ClassMap files, ClassSchemaMap.cs and AttributeSchemaMap.cs.

Finally, I used them in a series of queries.  I’ve included all of the files below.  Here are some examples of queries I’ve run.

The first example retrieves a particular classSchema entry, user, and displays selected properties.

    static void Main(string[] args)
    {
        var config = new LdapConfiguration()
            .AddMapping(new ilminator.LinqToLdap.DirectoryObjects.ClassMaps.AttributeSchemaMap())
            .AddMapping(new ilminator.LinqToLdap.DirectoryObjects.ClassMaps.ClassSchemaMap());

        var factory = config.ConfigureFactory("dc1");    // Insert your DC name or domain DNS name here

        var context = new DirectoryContext();

        // ----- Start of section to replace -----

        var query = context.Query<ClassSchema>()
            .Where(c => c.lDAPDisplayName.Equals("user"));

        foreach (var item in query)
        {
            Console.WriteLine("lDAPDisplayName = {0}", item.lDAPDisplayName);
            DisplaySingleValuedAttribute(item.subClassOf, "subClassOf");
            DisplayMultiValuedAttribute(item.systemAuxiliaryClass, "systemAuxiliaryClass");
            DisplayMultiValuedAttribute(item.auxiliaryClass, "auxiliaryClass");
            DisplayMultiValuedAttribute(item.systemMustContain, "systemMustContain");
            DisplayMultiValuedAttribute(item.mustContain, "mustContain");
            DisplayMultiValuedAttribute(item.systemMayContain, "systemMayContain");
            DisplayMultiValuedAttribute(item.mayContain, "mayContain");
        }

        // ----- End of section to replace -----

        Console.WriteLine("\r\nPress a key to continue...");
        Console.ReadKey(true);
    }

    private static void DisplaySingleValuedAttribute(Object attribute, string attributeName)
    {
        if (attribute != null)
        {
            Console.WriteLine("{0,-30}: {1}", attributeName, attribute);
        }
    }

    private static void DisplayMultiValuedAttribute(Collection<String> attributeValues, string attributeName)
    {
        if (attributeValues != null)
        {
            var list = new List<String>(attributeValues);
            list.Sort();

            Console.WriteLine("{0}:", attributeName);
            foreach (var item in list)
            {
                Console.WriteLine("    {0}", item);
            }
        }
    }

The second example retrieves all classSchema entries which have mailRecipient as an auxiliaryClass or systemAuxiliaryClass and displays their lDAPDisplayNames.

    // ----- Start of section to replace -----

    var query = context.Query<ClassSchema>()
        .Where(c => (c.auxiliaryClass.Contains("mailRecipient") || c.systemAuxiliaryClass.Contains("mailRecipient")));

    foreach (var item in query)
    {
        Console.WriteLine(item.lDAPDisplayName);
    }
            
    // ----- End of section to replace -----

The third example retrieves all classSchema entries which have mail as an attribute displays their lDAPDisplayNames.

    // ----- Start of section to replace -----

    var query = context.Query<ClassSchema>()
        .Where(c => (c.mustContain.Contains("mail")
            || c.systemMustContain.Contains("mail")
            || c.mayContain.Contains("mail")
            || c.systemMayContain.Contains("mail")));

    foreach (var item in query)
    {
        Console.WriteLine(item.lDAPDisplayName);
    }
            
    // ----- End of section to replace -----

The fourth example retrieves and displays all of the mapped attributes of all attributeSchema entries.

    // ----- Start of section to replace -----

    DisplayAllAttributes(context);
            
    // ----- End of section to replace -----

and

    private static void DisplayAllAttributes(DirectoryContext context)
    {
        var query = context.Query<AttributeSchema>();

        var attributes = from a in query
                            select a;

        foreach (var attribute in attributes)
        {
            DisplaySchemaAttribute(attribute);
            Console.WriteLine();
        }
    }

    private static void DisplaySchemaAttribute(AttributeSchema attribute)
    {
        DisplaySingleValuedAttribute(attribute.lDAPDisplayName, "lDAPDisplayName");
        DisplaySingleValuedAttribute(attribute.commonName, "commonName");
        DisplaySingleValuedAttribute(attribute.adminDescription, "adminDescription");
        DisplaySingleValuedAttribute(attribute.adminDescription, "adminDescription");
        DisplaySingleValuedAttribute(attribute.attributeID, "attributeID");
        DisplaySingleValuedAttribute(attribute.attributeSyntax, "attributeSyntax");
        DisplaySingleValuedAttribute(attribute.classDisplayName, "classDisplayName");
        DisplayMultiValuedAttribute(attribute.description, "description");
        DisplaySingleValuedAttribute(attribute.displayName, "displayName");
        DisplaySingleValuedAttribute(attribute.distinguishedName, "distinguishedName");
        DisplaySingleValuedAttribute(attribute.isDefunct, "isDefunct");
        DisplaySingleValuedAttribute(attribute.isMemberOfPartialAttributeSet, "isMemberOfPartialAttributeSet");
        DisplaySingleValuedAttribute(attribute.isSingleValued, "isSingleValued");
        DisplaySingleValuedAttribute(attribute.linkID, "linkID");
        DisplaySingleValuedAttribute(attribute.name, "name");
        DisplaySingleValuedAttribute(attribute.objectGuid, "objectGuid");
        DisplaySingleValuedAttribute(attribute.oMObjectClass, "oMObjectClass");
        DisplaySingleValuedAttribute(attribute.oMSyntax, "oMSyntax");
        DisplaySingleValuedAttribute(attribute.rangeLower, "rangeLower");
        DisplaySingleValuedAttribute(attribute.rangeUpper, "rangeUpper");
        DisplaySingleValuedAttribute(attribute.searchFlags, "searchFlags");
        DisplaySingleValuedAttribute(attribute.systemOnly, "systemOnly");
    }

The fifth example retrieves and displays all of the mapped attributes of all classSchema entries.

    // ----- Start of section to replace -----

    DisplayAllClasses(context);
            
    // ----- End of section to replace -----

and


    private static void DisplayAllClasses(DirectoryContext context)
    {
        var schemaQuery = context.Query<ClassSchema>();

        var schemaclasses = from a in schemaQuery
                            select a;

        foreach (var schemaClass in schemaclasses)
        {
            DisplaySchemaClass(schemaClass);
            Console.WriteLine();
        }
    }

    private static void DisplaySchemaClass(ClassSchema schemaClass)
    {
        DisplaySingleValuedAttribute(schemaClass.lDAPDisplayName, "lDAPDisplayName");
        DisplaySingleValuedAttribute(schemaClass.commonName, "commonName");
        DisplaySingleValuedAttribute(schemaClass.adminDescription, "adminDescription");
        DisplaySingleValuedAttribute(schemaClass.adminDisplayName, "adminDisplayName");
        DisplayMultiValuedAttribute(schemaClass.auxiliaryClass, "auxiliaryClass");
        DisplaySingleValuedAttribute(schemaClass.classDisplayName, "classDisplayName");
        DisplaySingleValuedAttribute(schemaClass.defaultHidingValue, "defaultHidingValue");
        DisplaySingleValuedAttribute(schemaClass.defaultObjectCategory, "defaultObjectCategory");
        DisplayMultiValuedAttribute(schemaClass.description, "description");
        DisplaySingleValuedAttribute(schemaClass.displayName, "displayName");
        DisplaySingleValuedAttribute(schemaClass.distinguishedName, "distinguishedName");
        DisplaySingleValuedAttribute(schemaClass.governsID, "governsID");
        DisplaySingleValuedAttribute(schemaClass.isDefunct, "isDefunct");
        DisplayMultiValuedAttribute(schemaClass.mayContain, "mayContain");
        DisplayMultiValuedAttribute(schemaClass.mustContain, "mustContain");
        DisplaySingleValuedAttribute(schemaClass.name, "name");
        DisplaySingleValuedAttribute(schemaClass.objectClassCategory, "objectClassCategory");
        DisplaySingleValuedAttribute(schemaClass.objectGuid, "objectGuid");
        DisplayMultiValuedAttribute(schemaClass.possSuperiors, "possSuperiors");
        DisplaySingleValuedAttribute(schemaClass.rDNAttID, "rDNAttID");
        DisplaySingleValuedAttribute(schemaClass.schemaIDGUID, "schemaIDGUID");
        DisplaySingleValuedAttribute(schemaClass.showInAdvancedViewOnly, "showInAdvancedViewOnly");
        DisplaySingleValuedAttribute(schemaClass.subClassOf, "subClassOf");
        DisplayMultiValuedAttribute(schemaClass.systemAuxiliaryClass, "systemAuxiliaryCLass");
        DisplayMultiValuedAttribute(schemaClass.systemMayContain, "systemMayContain");
        DisplayMultiValuedAttribute(schemaClass.systemMustContain, "systemMustContain");
        DisplaySingleValuedAttribute(schemaClass.systemOnly, "systemOnly");
        DisplayMultiValuedAttribute(schemaClass.systemPossSuperiors, "systemPossSuperiors");
    }

Top and TopX

This is what Top looks like, now. Note that I’ve added a namespace, AbstractClasses, and moved Top into it. The ‘Abstract’ part refers to classes that are abstract in the project rather than in Active Directory. I’ve also added an abstract class, TopX. It contains commonName which is actually part of top but is optional. It’s absent from organizationalUnit so it doesn’t make sense to have it in Top. It also contains description, in it’s multi-valued incarnation. See the class summaries for more info.

using System;
using System.Collections.ObjectModel;
using LinqToLdap.Mapping;

namespace ilminator.LinqToLdap.DirectoryObjects.AbstractClasses
{
    /// <summary>
    /// Classes should not normally inherit Top but should instead choose one of
    /// existing subclasses: <see cref="TopX"/>, <see cref="SecPrinMailRecipEtc"/> 
    /// or <see cref="OrganizationalUnitObject"/> as these have additional 
    /// attributes common to other classes.
    /// </summary>
    public abstract class Top : DirectoryObjectBase
    {
        /// <summary>
        /// The description displayed on admin screens.
        /// [From: http://msdn.microsoft.com/en-us/library/ms675213.aspx]
        /// RangeLower: 0
        /// RangeUpper: 1024
        /// </summary>
        public String adminDescription { get; set; }
        /// <summary>
        /// The name to be displayed on admin screens.
        /// [From: http://msdn.microsoft.com/en-us/library/ms675214.aspx]
        /// RangeLower: 1
        /// RangeUpper: 256
        /// </summary>
        public String adminDisplayName { get; set; }

        // description cannot be implemented here because it has different uses in 
        // Active Directory.  For SAM-managed objects, it is used as if it were
        // defined as single-valued.  For other objects, it is used as defined, 
        // which is as a multi-valued object.  For this reason, I think it makes
        // sense to implement it differently for SAM- and non-SAM-managed objects.
        // See the definitions of SecPrinMailRecipEtc and OrganizationalUnit for
        // examples.
        // Ref: http://msdn.microsoft.com/en-us/library/windows/desktop/ms675492.aspx.

        private string _displayName;
        /// <summary>
        /// Display-Name.  The display name for an object. This is usually 
        /// the combination of the users first name, middle initial, and last name.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms675514.aspx]
        /// </summary>
        public String displayName
        {
            get { return this._displayName; }
            set
            {
                if (value != this._displayName)
                {
                    this._displayName = value;
                    AttributeChanged<Top, String>(a => a.displayName);
                }
            }
        }

        /// <summary>
        /// Obj-Dist-Name.
        /// </summary>
        public String distinguishedName { get; set; }
        /// <summary>
        /// RDN.  The Relative Distinguished Name of an object.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms678697.aspx]
        /// </summary>
        public String name { get; set; }
        /// <summary>
        /// Object-Guid.  The unique identifier for an object.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms679021.aspx]
        /// </summary>
        public Guid objectGuid { get; set; }
        /// <summary>
        /// USN-Changed.  USN value assigned by the local directory for the 
        /// latest change, including creation.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680871.aspx]
        /// </summary>
        public Int64 uSNChanged { get; set; }
        /// <summary>
        /// USN-Created.  USN-Changed value assigned at object creation.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680874.aspx]
        /// </summary>
        public Int64 uSNCreated { get; set; }
        /// <summary>
        /// When-Changed.  The date when this object was last changed. This 
        /// value is not replicated and exists in the global catalog.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680921.aspx"]
        /// </summary>
        public DateTime whenChanged { get; set; }
        /// <summary>
        /// When-Created.  The date when this object was created. This value
        /// is replicated and is in the global catalog.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680924.aspx"]
        /// </summary>
        public DateTime whenCreated { get; set; }

        private string _wWWHomePage;
        /// <summary>
        /// WWW-HomePage.  The primary webpage.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680927.aspx]
        /// </summary>
        public String wWWHomePage
        {
            get { return this._wWWHomePage; }
            set
            {
                if (value != this._wWWHomePage)
                {
                    this._wWWHomePage = value;
                    AttributeChanged<Top, String>(a => a.wWWHomePage);
                }
            }
        }
    }
}

and TopX:

using System;
using System.Collections.ObjectModel;

namespace ilminator.LinqToLdap.DirectoryObjects.AbstractClasses
{
    /// <summary>
    /// Top, with additional attributes.  Non-SAM classes should
    /// normally use this class as their parent class.
    /// </summary>
    public abstract class TopX : Top
    {
        public String commonName { get; set; }  // Top (but optional)

        private Collection<string> _description;     // top (see note on Top)
        /// <summary>
        /// Description.  Contains the description to display for an object. In 
        /// Active Directory, this value is restricted as single-valued for 
        /// backward compatibility in some cases but is allowed to be multi-valued
        /// in others. See 'Remarks' on the follow link.
        /// [From: http://msdn.microsoft.com/en-us/library/windows/desktop/ms675492.aspx]
        /// </summary>
        public Collection<String> description
        {
            get { return this._description; }
            set
            {
                if (value != this._description)
                {
                    this._description = value;
                    AttributeChanged<TopX, Collection<String>>(a => a.description);
                }
            }
        }
    }
}

ClassSchema and ClassSchemaMap

First, ClassSchema. Note that I’ve created a namespace, SchemaClasses, to hold the directory object classes for attributes and classes.

using System;
using System.Collections.ObjectModel;

namespace ilminator.LinqToLdap.DirectoryObjects.SchemaClasses
{
    public class ClassSchema : ilminator.LinqToLdap.DirectoryObjects.AbstractClasses.TopX
    {
        public Collection<String> auxiliaryClass { get; set; }
        public String classDisplayName { get; set; }
        public Boolean defaultHidingValue { get; set; }
        public String defaultObjectCategory { get; set; }
        public String governsID { get; set; }
        public Nullable<Boolean> isDefunct { get; set; }
        public String lDAPDisplayName { get; set; }
        public Collection<String> mayContain { get; set; }
        public Collection<String> mustContain { get; set; }
        public Int32 objectClassCategory { get; set; }
        public Collection<String> possSuperiors { get; set; }
        public String rDNAttID { get; set; }
        public Guid schemaIDGUID { get; set; }
        public Boolean showInAdvancedViewOnly { get; set; }
        public String subClassOf { get; set; }
        public Collection<String> systemAuxiliaryClass { get; set; }
        public Collection<String> systemMayContain { get; set; }
        public Collection<String> systemMustContain { get; set; }
        public Boolean systemOnly { get; set; }
        public Collection<String> systemPossSuperiors { get; set; }
    }
}

then, ClassSchemaMap

using System;
using LinqToLdap.Mapping;

namespace ilminator.LinqToLdap.DirectoryObjects.ClassMaps
{
    public class ClassSchemaMap : ClassMap<ilminator.LinqToLdap.DirectoryObjects.SchemaClasses.ClassSchema>
    {
        public ClassSchemaMap()
        {
            NamingContext("CN=Schema,CN=Configuration,DC=big,DC=wooden,DC=badger");   //   Replace with your defaultNamingContext
            ObjectClass("classSchema");
            ObjectCategory("classSchema");

            // From ClassSchema
            Map(x => x.auxiliaryClass).StoreGenerated();
            Map(x => x.classDisplayName).StoreGenerated();
            Map(x => x.defaultHidingValue).StoreGenerated();
            Map(x => x.defaultObjectCategory).StoreGenerated();
            Map(x => x.governsID).StoreGenerated();
            Map(x => x.isDefunct).StoreGenerated();
            Map(x => x.lDAPDisplayName).StoreGenerated();
            Map(x => x.mayContain).StoreGenerated();
            Map(x => x.mustContain).StoreGenerated();
            Map(x => x.objectClassCategory).StoreGenerated();
            Map(x => x.possSuperiors).StoreGenerated();
            Map(x => x.rDNAttID).StoreGenerated();
            Map(x => x.schemaIDGUID).StoreGenerated();
            Map(x => x.showInAdvancedViewOnly).StoreGenerated();
            Map(x => x.subClassOf).StoreGenerated();
            Map(x => x.systemAuxiliaryClass).StoreGenerated();
            Map(x => x.systemMayContain).StoreGenerated();
            Map(x => x.systemMustContain).StoreGenerated();
            Map(x => x.systemOnly).StoreGenerated();
            Map(x => x.systemPossSuperiors).StoreGenerated();

            // From TopX
            Map(x => x.commonName).Named("cn").StoreGenerated();
            Map(x => x.description).StoreGenerated();

            // From Top
            DistinguishedName(x => x.distinguishedName);

            Map(x => x.adminDescription).StoreGenerated();
            Map(x => x.adminDisplayName).StoreGenerated();
            Map(x => x.displayName).StoreGenerated();
            Map(x => x.name).StoreGenerated();
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.uSNChanged).StoreGenerated();
            Map(x => x.uSNCreated).StoreGenerated();
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
            Map(x => x.wWWHomePage).StoreGenerated();
        }
    }
}

AttributeSchema and AttributeSchemaMap

Now, AttributeSchema.

using System;

namespace ilminator.LinqToLdap.DirectoryObjects.SchemaClasses
{
    public class AttributeSchema : ilminator.LinqToLdap.DirectoryObjects.AbstractClasses.TopX
    {
        public AttributeSchema() { }

        public String attributeID { get; set; }
        public String attributeSyntax { get; set; }
        public String classDisplayName { get; set; }
        public Nullable<Boolean> isDefunct { get; set; }
        public Nullable<Boolean> isMemberOfPartialAttributeSet { get; set; }
        public Boolean isSingleValued { get; set; }
        public String lDAPDisplayName { get; set; }
        public Nullable<Int32> linkID { get; set; }
        public Byte[] oMObjectClass { get; set; }
        public Int32 oMSyntax { get; set; }
        public Nullable<Int32> rangeLower { get; set; }
        public Nullable<Int32> rangeUpper { get; set; }
        public Int32 searchFlags { get; set; }
        public Boolean systemOnly { get; set; }
    }
}

then, AttributeSchemaMap:

using System;
using LinqToLdap.Mapping;

namespace ilminator.LinqToLdap.DirectoryObjects.ClassMaps
{
    public class AttributeSchemaMap : ClassMap<ilminator.LinqToLdap.DirectoryObjects.SchemaClasses.AttributeSchema>
    {
        public AttributeSchemaMap()
        {
            NamingContext("CN=Schema,CN=Configuration,DC=big,DC=wooden,DC=badger");   //   Replace with your defaultNamingContext
            ObjectClass("attributeSchema");
            ObjectCategory("attributeSchema");

            // From AttributeSchema

            Map(x => x.attributeID).StoreGenerated();
            Map(x => x.attributeSyntax).StoreGenerated();
            Map(x => x.classDisplayName).StoreGenerated();
            Map(x => x.isDefunct).StoreGenerated();
            Map(x => x.isMemberOfPartialAttributeSet).StoreGenerated();
            Map(x => x.isSingleValued).StoreGenerated();
            Map(x => x.lDAPDisplayName).StoreGenerated();
            Map(x => x.linkID).StoreGenerated();
            Map(x => x.oMObjectClass).StoreGenerated();
            Map(x => x.oMSyntax).StoreGenerated();
            Map(x => x.rangeLower).StoreGenerated();
            Map(x => x.rangeUpper).StoreGenerated();
            Map(x => x.searchFlags).StoreGenerated();
            Map(x => x.systemOnly).StoreGenerated();

            // From TopX
            Map(x => x.commonName).Named("cn").StoreGenerated();
            Map(x => x.description).StoreGenerated();

            // From Top
            DistinguishedName(x => x.distinguishedName);

            Map(x => x.adminDescription).StoreGenerated();
            Map(x => x.adminDisplayName).StoreGenerated();
            Map(x => x.displayName).StoreGenerated();
            Map(x => x.name).StoreGenerated();
            Map(x => x.objectGuid).StoreGenerated();
            Map(x => x.uSNChanged).StoreGenerated();
            Map(x => x.uSNCreated).StoreGenerated();
            Map(x => x.whenChanged).StoreGenerated();
            Map(x => x.whenCreated).StoreGenerated();
            Map(x => x.wWWHomePage).StoreGenerated();
        }
    }
}

Note that the NamingContexts on both ClassMaps have been set to the forest schemaNamingContext.

Summary

It only took me a few minutes to set these up and now I can query the schema the same way I run other queries.  I hope you find it as useful as I have.

Retrieving move than 500 objects with LinqToLdap

By default, LinqToLdap sets the page size to be 500 objects, meaning standard queries will only return the first 500 objects.

This article builds on the article OrganizationalUnit CRUD and uses the same OrganizationalUnitObject and OrganizationalUnitObjectMap classes.

Before we start, it’s probably a good idea to make sure you’ve got a large number of OUs to play with. If you haven’t, you’ll have to adapt the code to work with objects you have got. Or wait until I write about users and groups. ADU&C will show all that there are: I suggest you go to the top of your domain and use the Find dialog to get a count. I had about 8,000.

So, what do we get if we don’t do anything special? Here’s the Program.cs to start with:

using System;
using System.Linq;
using LinqToLdap;

namespace BlogOuTest1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
                .AddMapping(new OrganizationalUnitObjectMap());
            config.ConfigureFactory("dc1");

            var counter = 0;
            var context = new DirectoryContext();

            // ----- Start of section to replace -----
            var allOUsQuery = context.Query();

            var ous = from o in allOUsQuery
                      select o;

            foreach (var ou in ous)
            {
                counter++;
                Console.WriteLine("Ou: {0}",
                    ou.organizationalUnitName);
            }
            // ----- End of section to replace -----

            Console.WriteLine("Counter = {0}", counter);
            Console.WriteLine("\r\nPress a key to continue...");
            Console.ReadKey(true);
        }
    }
}

With this, I get a list of OUs and then

Counter = 500

Which was as expected because the default page size in LinqToLdap is 500. So, how do we get more?

Since the Active Directory default maximum page size, on the server, is 1000 we could increase the page size used by LinqToLdap. If you replace the config definition with the following, you’ll get 1000 objects returned:

    var config = new LdapConfiguration()
        .AddMapping(new OrganizationalUnitObjectMap())
        .MaxPageSizeIs(1000);

It’s better, but still not what we’re after. To get the rest, we’re going to have to get the server to page through the data. So, how do we do that in LinqToLdap? First, I’m going to revert to the original (LinqToLdap) default page size of 500 by removing the MaxPageSizeIs(1000) line.

Now, you have two choices. If you just want to retrieve all objects, you can use InPagesOf(n); if you want to process the objects as you retrieve them, you can use ToPage().

InPagesOf() – retrieving the objects in one pass

You can use a query like this to retrieve all of the objects in one pass:

using System;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Linq;
using LinqToLdap;
using LinqToLdap.Collections;

namespace BlogOuTest3
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration()
                .AddMapping(new OrganizationalUnitObjectMap());
            config.ConfigureFactory("dc1");

            var context = new DirectoryContext();

            // ----- Start of section to replace -----

            var ous = context.Query<OrganizationalUnitObject>()
                .Select(u => u.organizationalUnitName)
                .InPagesOf(10);

            var counter = 0;
            foreach (var ou in ous)
            {
                counter++;
                Console.WriteLine("Ou: {0}",ou);
            }

            // ----- End of section to replace -----

            Console.WriteLine("Counter = {0}", counter);

            Console.WriteLine("\r\nPress a key to continue...");
            Console.ReadKey(true);
        }
    }
}

I’ve chosen a silly page size for this one, so it’ll page even if you’ve only a handful of OUs. There is a scenario where this won’t work and it’s one that had me scratching my head for quite a while. So far, I’ve been chosing the root container (the domainDNS container, otherwise known as the defaultNamingContext) of my domain as the NamingContext on the ClassMaps, for example:

NamingContext("DC=big,DC=wooden,DC=badger");

But for searches to work against the root container, you need another search control:

SearchOptionsControl(SearchOption.DomainScope)

So the query statement should look like this:

var ous = context.Query<OrganizationalUnitObject>()
    .WithControls(new[] { new SearchOptionsControl(SearchOption.DomainScope) })
    .Select(u => u.organizationalUnitName)
    .InPagesOf(10);

I eventually found this here. It doesn’t say why and I still haven’t worked that out. I’ve run individual searches against every OU and container in the root container and they all work without the control, so it’s something to do with searching the root container itself. I’ll update if I ever sort it out.

ToPage() – processing objects in pages

You can use ToPage() in a query to process the objects in pages as you retrieve them. Here’s an example. First, I’ve added a method to the Program class:

   private static int ProcessPage(int counter, ILdapPage<string> page)
    {
        if (page.Count > 0)
        {
            foreach (var ou in page.ToList())
            {
                counter++;
                Console.WriteLine("Ou: {0}", ou);
            }
            System.Threading.Thread.Sleep(500);
        }

        return counter;
    }

All it does is display the results and increment a counter, then sleep for half a sec so you can see that it’s happening page by page.

Then, I’ve replaced the section between the comments, in the code sample above, with the following:

    // ----- Start of section to replace -----

    var counter = 0;

    ILdapPage<string> page = context.Query<OrganizationalUnitObject>()
        .WithControls(new[] { new SearchOptionsControl(SearchOption.DomainScope) })
        .Select(o => o.distinguishedName)
        .ToPage(10);

    counter = ProcessPage(counter, page);

    while (page.HasNextPage)
    {
        var pageSize = page.PageSize;
        var nextPage = page.NextPage;

        page = context.Query<OrganizationalUnitObject>()
            .WithControls(new[] { new SearchOptionsControl(SearchOption.DomainScope) })
            .Select(o => o.distinguishedName)
            .ToPage(pageSize, nextPage);

        counter = ProcessPage(counter, page);
    }

    // ----- End of section to replace -----

When you run the code you should see the results appear a page at a time.