Using conditional references to influence which assemblies are used for a specific project configuration or platform

by Tobias Hertkorn on November 22nd, 2011

Introduction

When using specific third party assemblies it is necessary to distinguish between platforms. Best example are e.g. log4net, sqlite, … which come in both x86 and x64 but not in the general Any Cpu flavor. A typical development scenario could be that an ASP.NET application is developed using the IIS Express and deployed on a 64bit IIS in production.
A different development scenario could be that a Silverlight application uses Design Time data in order to create a richer design time experience for the developer. In this situation a best practice is to put the classes containing the design time data into a separate assembly that is only referenced during development and is not deployed in production. Here one needs to distinguish between solution configuration Debug and Release.
In order to support these scenarios the project files allow for conditional references.
Unfortunately Visual Studio does not expose the conditional references feature in its UI. Therefore one has to edit the corresponding .csproj file by hand. This can be simply done using Visual Studio using the following steps:

  1. Right click the project one wants to edit
  2. Select "Unload Project" from the popup menu
  3. Right click the unloaded project
  4. Select "Edit "

Basic project file layout (Visual Studio 2010)

A project file (.csproj) is an XML file containing an XML header and a root element Project. The Project can contain multiple elements of different types, most notably the PropertyGroup, ItemGroup, Import and Target elements. In this post I will not describe the specific use of each of the elements, but will concentrate on conditional assemblies. Documentation for these elements can be found on msdn.
Assembly or project references are found in an ItemGroup using either Reference or ProjectReference elements.
A typical .csproj producing either an x86 or x64 executable looks like this:

XML:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  3.   <PropertyGroup>
  4.     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
  5.     <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
  6.     <ProductVersion>8.0.30703</ProductVersion>
  7.     <SchemaVersion>2.0</SchemaVersion>
  8.     <ProjectGuid>{5793FBD7-6122-484B-AD86-AA211162D0EB}</ProjectGuid>
  9.     <OutputType>Exe</OutputType>
  10.     <AppDesignerFolder>Properties</AppDesignerFolder>
  11.     <RootNamespace>Com.Hertkorn.DotNet.ConditionalAssemblyReferences</RootNamespace>
  12.     <AssemblyName>Com.Hertkorn.DotNet.ConditionalAssemblyReferences</AssemblyName>
  13.     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
  14.     <TargetFrameworkProfile>
  15.     </TargetFrameworkProfile>
  16.     <FileAlignment>512</FileAlignment>
  17.   </PropertyGroup>
  18.   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
  19.     <PlatformTarget>x86</PlatformTarget>
  20.     <DebugSymbols>true</DebugSymbols>
  21.     <DebugType>full</DebugType>
  22.     <Optimize>false</Optimize>
  23.     <OutputPath>bin\Debug\</OutputPath>
  24.     <DefineConstants>DEBUG;TRACE</DefineConstants>
  25.     <ErrorReport>prompt</ErrorReport>
  26.     <WarningLevel>4</WarningLevel>
  27.   </PropertyGroup>
  28.   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
  29.     <PlatformTarget>x86</PlatformTarget>
  30.     <DebugType>pdbonly</DebugType>
  31.     <Optimize>true</Optimize>
  32.     <OutputPath>bin\Release\</OutputPath>
  33.     <DefineConstants>TRACE</DefineConstants>
  34.     <ErrorReport>prompt</ErrorReport>
  35.     <WarningLevel>4</WarningLevel>
  36.   </PropertyGroup>
  37.   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
  38.     <DebugSymbols>true</DebugSymbols>
  39.     <OutputPath>bin\x64\Debug\</OutputPath>
  40.     <DefineConstants>DEBUG;TRACE</DefineConstants>
  41.     <DebugType>full</DebugType>
  42.     <PlatformTarget>x64</PlatformTarget>
  43.     <CodeAnalysisLogFile>bin\Debug\Com.Hertkorn.DotNet.ConditionalAssemblyReferences.exe.CodeAnalysisLog.xml</CodeAnalysisLogFile>
  44.     <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
  45.     <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
  46.     <ErrorReport>prompt</ErrorReport>
  47.     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
  48.     <CodeAnalysisRuleSetDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets</CodeAnalysisRuleSetDirectories>
  49.     <CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
  50.     <CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
  51.     <CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
  52.   </PropertyGroup>
  53.   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
  54.     <OutputPath>bin\x64\Release\</OutputPath>
  55.     <DefineConstants>TRACE</DefineConstants>
  56.     <Optimize>true</Optimize>
  57.     <DebugType>pdbonly</DebugType>
  58.     <PlatformTarget>x64</PlatformTarget>
  59.     <CodeAnalysisLogFile>bin\Release\Com.Hertkorn.DotNet.ConditionalAssemblyReferences.exe.CodeAnalysisLog.xml</CodeAnalysisLogFile>
  60.     <CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
  61.     <CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
  62.     <ErrorReport>prompt</ErrorReport>
  63.     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
  64.     <CodeAnalysisRuleSetDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets</CodeAnalysisRuleSetDirectories>
  65.     <CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>
  66.     <CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
  67.     <CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
  68.   </PropertyGroup>
  69.   <ItemGroup>
  70.     <Reference Include="CustomMarshalers" />
  71.     <Reference Include="System" />
  72.     <Reference Include="System.Core" />
  73.     <Reference Include="System.Xml.Linq" />
  74.     <Reference Include="System.Data.DataSetExtensions" />
  75.     <Reference Include="Microsoft.CSharp" />
  76.     <Reference Include="System.Data" />
  77.     <Reference Include="System.Xml" />
  78.   </ItemGroup>
  79.   <ItemGroup>
  80.     <Compile Include="Program.cs" />
  81.     <Compile Include="Properties\AssemblyInfo.cs" />
  82.   </ItemGroup>
  83.   <ItemGroup>
  84.     <None Include="app.config" />
  85.   </ItemGroup>
  86.   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  87.   <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
  88.        Other similar extension points exist, see Microsoft.Common.targets.
  89.   <Target Name="BeforeBuild">
  90.   </Target>
  91.   <Target Name="AfterBuild">
  92.   </Target>
  93.   -->
  94. </Project>

Conditionals

In the above project one can already spot the mechanism used to distinguish between e.g. configurations and platforms.

XML:
  1. <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">

An XML node annotated with the Condition attribute is only included when parsing the file if the condition holds true. This mechanism can be applied to any element in the project file's XML structure.

Using conditions for assembly references

If one uses an assembly that is specifically designed for x86 or x64 one can use the conditional attribute to include the correct dll for the specified build platform. One of the best known dll of that type is SQLite:

Without the conditional a dll reference looks like this:

XML:
  1. <Reference Include="System.Data.SQLite">
  2.   <HintPath>..\libs\sqlite\System.Data.SQLite.dll</HintPath>
  3. </Reference>

To distinguish between the two states of 32bit and 64bit one simply doubles theReference element and applies mutually exclusive Condition attributes to both:

XML:
  1. <Reference Include="System.Data.SQLite" Condition="'$(PlatformTarget)' == 'x86'">
  2.   <HintPath>..\libs\sqlite\x86\System.Data.SQLite.dll</HintPath>
  3. </Reference>
  4. <Reference Include="System.Data.SQLite" Condition="'$(PlatformTarget)' == 'x64'">
  5.   <HintPath>..\libs\sqlite\x64\System.Data.SQLite.dll</HintPath>
  6. </Reference>

Note that the HintPath differs depending on the platform.

Using conditions for project references

If you have a project that should not get deployed as part of the final released product, why not use the Condition attribute to only include the project during debugging. One good example for these kinds of projects is Design-time data. Simply distinguish on the Configuration setting.

XML:
  1. <ProjectReference
  2.       Include="..\Com.Hertkorn.DotNet.ConditionalAssemblyReferences.Design\Com.Hertkorn.DotNet.ConditionalAssemblyReferences.Design.csproj"
  3.       Condition="'$(Configuration)' == 'Debug'">
  4.   <Project>{C7EAA6CA-1AF6-4D72-929B-D7D1177868D2}</Project>
  5.   <Name>Com.Hertkorn.DotNet.ConditionalAssemblyReferences.Design</Name>
  6. </ProjectReference>

Hint

Keep in mind that the Visual Studio UI does not reflect conditional references within the "References" folder of a project. Therefore all references are visible in any given configuration or platform selection. The compiler and Intellisense on the other hand are aware of conditional references, honoring the correct settings both with visual feedback and error notification during builds.

Summary

Today we looked at the possibility to customize which assemblies or projects are referenced in each individual configuration or platform selection. The customization cannot be done using the UI, but is accomplished by manually editing the project file.

November 22nd, 2011 8:52 pm | Comments (0)

An updated look at Duck Typing in C# 4.0

by Tobias Hertkorn on January 5th, 2011

Download the source: blogducktypingreloaded.zip

It's time to do an update on my blog post from the end of 2008, when I did take a first look at the dynamic capabilities in .NET 4.0. From the beta to the final version of 4.0 they did unfortunatelly change the API used for dynamic dispatching and dynamic objects. Instead of MetaObject and IDynamicObject all we need in the updated version of the API is System.Dynamics.DynamicObject. It takes the place of IDynamicObject and MetaObject is gone for good.

This makes it really pleasently easy to implement the same behaviour as I did years ago:

C#:
  1. public class Duck : DynamicObject
  2. {
  3.     private Dictionary<string, string> dictionary = new Dictionary<string, string>();
  4.  
  5.     public override bool TryGetMember(GetMemberBinder binder, out object result)
  6.     {
  7.         string resultString;
  8.         var didWork = TryGetMember(binder.Name, out resultString);
  9.         result = resultString;
  10.  
  11.         return didWork;
  12.     }
  13.  
  14.     private bool TryGetMember(string name, out string result)
  15.     {
  16.         return dictionary.TryGetValue(name, out result);
  17.     }
  18.  
  19.     public override bool TrySetMember(SetMemberBinder binder, object value)
  20.     {
  21.         return TrySetMember(binder.Name, value);
  22.     }
  23.  
  24.     private bool TrySetMember(string name, object value)
  25.     {
  26.         try
  27.         {
  28.             dictionary[name] = value == null ? null : value.ToString();
  29.  
  30.             return true;
  31.         }
  32.         catch
  33.         {
  34.             return false;
  35.         }
  36.     }
  37.  
  38.     public override bool TryConvert(ConvertBinder binder, out object result)
  39.     {
  40.         try
  41.         {
  42.             result = Generator.GenerateProxy(binder.Type, this);
  43.             return true;
  44.         }
  45.         catch
  46.         {
  47.             result = null;
  48.             return false;
  49.         }
  50.     }
  51. }

This is a dynamic Duck that can store any value when given via Property and will return the same value as a string when asked for it via the dynamic keyword.

C#:
  1. dynamic d = new Duck();
  2. d.Test = i;
  3. dynamic a = d.Test;
  4.  
  5. Console.WriteLine(a);
  6. Console.WriteLine(a.GetType().FullName + " (Should be System.String)");

When you download the sample you can see that the code also includes the ability to do a cast from the dynamic Duck to any given interface, in this case an IQuack. So now the totally dynamic property bag is usable via a strongly typed interface!

Download the source: blogducktypingreloaded.zip

January 5th, 2011 10:29 pm | Comments (0)

Creating an improved ASP.NET MVC 3 Html.ImageActionLink

by Tobias Hertkorn on December 5th, 2010

The old version of Html.ImageActionLink had a serious flaw - I had to resolve to replacing parts of the generated link after the fact in order to get non html encoded information in there. That obviously creates, though not really probable in this specific case, the problem of unwanted replacement is possible.

So I learnt about UrlHelper and generate the whole MvcHtmlString in one go, without falling back on replacing stuff.

C#:
  1. public static MvcHtmlString ImageActionLink(
  2.     this HtmlHelper helper,
  3.     string imageUrl,
  4.     string altText,
  5.     string actionName,
  6.     string controllerName,
  7.     object routeValues,
  8.     object linkHtmlAttributes,
  9.     object imgHtmlAttributes)
  10. {
  11.     var linkAttributes = AnonymousObjectToKeyValue(linkHtmlAttributes);
  12.     var imgAttributes = AnonymousObjectToKeyValue(imgHtmlAttributes);
  13.  
  14.     var imgBuilder = new TagBuilder("img");
  15.  
  16.     imgBuilder.MergeAttribute("src", imageUrl);
  17.     imgBuilder.MergeAttribute("alt", altText);
  18.     imgBuilder.MergeAttributes(imgAttributes, true);
  19.  
  20.     var urlHelper = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);
  21.  
  22.     var linkBuilder = new TagBuilder("a");
  23.  
  24.     linkBuilder.MergeAttribute("href", urlHelper.Action(actionName, controllerName, routeValues));
  25.     linkBuilder.MergeAttributes(linkAttributes, true);
  26.  
  27.     var text = linkBuilder.ToString(TagRenderMode.StartTag);
  28.     text += imgBuilder.ToString(TagRenderMode.SelfClosing);
  29.     text += linkBuilder.ToString(TagRenderMode.EndTag);
  30.  
  31.     return MvcHtmlString.Create(text);
  32. }
  33.  
  34. private static Dictionary<string, object> AnonymousObjectToKeyValue(object anonymousObject)
  35. {
  36.     var dictionary = new Dictionary<string, object>();
  37.  
  38.     if (anonymousObject != null)
  39.     {
  40.         foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(anonymousObject))
  41.         {
  42.             dictionary.Add(propertyDescriptor.Name, propertyDescriptor.GetValue(anonymousObject));
  43.         }
  44.     }
  45.  
  46.     return dictionary;
  47. }

The interface of the method is exactly the same so I can still use this version to create an image that is an actionlink in razor like this:

XML:
  1. @Html.ImageActionLink(
  2.    Url.Content("~/Content/Images/" + url),
  3.    alt,
  4.    "Details",
  5.    "Calender",
  6.    new { area = "", day = Model.Date.Day, month = Model.Date.Month, year = Model.Date.Year },
  7.    new { @class = "SelectDay", onClick = string.Format("DoCallbackBookingDetails({0},{1},{2}); return false;", Model.Date.Day, Model.Date.Month, Model.Date.Year) },
  8.    new { style = "border: 0px;" })

December 5th, 2010 10:20 am | Comments (10)

Creating an ASP.NET MVC 3 Html.ActionLink that is an Image

by Tobias Hertkorn on December 5th, 2010

[UPDATE: There is an improved version of Html.ActionLink: Creating an improved ASP.NET MVC 3 Html.ImageActionLink]

Have I told you that I really enjoy ASP.NET MVC 3 Razor? Damn, that's nice to work with. I am doing a small, fun project for a friend of mine and I stumbled across the situation that I wanted to have an image as an ActionLink. And since the @Html.ActionLink syntax is really sexy, I wanted to write an Extension method that does the same with an image.

The solution is this quick hack:

[UPDATE: There is an improved version without the hacky replace: Creating an improved ASP.NET MVC 3 Html.ImageActionLink]

C#:
  1. public static MvcHtmlString ImageActionLink(this HtmlHelper helper,
  2.     string imageUrl,
  3.     string altText,
  4.     string actionName,
  5.     string controllerName,
  6.     object routeValues,
  7.     object linkHtmlAttributes,
  8.     object imgHtmlAttributes)
  9. {
  10.     var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(imgHtmlAttributes);
  11.  
  12.     var builder = new TagBuilder("img");
  13.     builder.MergeAttribute("src", imageUrl);
  14.     builder.MergeAttribute("alt", altText);
  15.  
  16.     foreach (var key in attributes.Keys)
  17.     {
  18.         var value = attributes[key];
  19.         string valueAsString = null;
  20.         if (value != null)
  21.         {
  22.             valueAsString = value.ToString();
  23.         }
  24.         builder.MergeAttribute(key, valueAsString);
  25.     }
  26.  
  27.     var link = helper.ActionLink("[placeholder]", actionName, controllerName, routeValues, linkHtmlAttributes);
  28.     var text = link.ToHtmlString();
  29.     text = text.Replace("[placeholder]", builder.ToString(TagRenderMode.SelfClosing));
  30.  
  31.     return MvcHtmlString.Create(text);
  32. }

We return a MvcHtmlString, so we can indicate that this sting is not supposed to be html encoded after we return it to the templating engine. Plus we use the handy AnonymousObjectToHtmlAttributes helper method in order to enable us to pass html attributes to the img tag using anonymous classes, just as we are used to by now.

Unfortunatelly we can not tell the helper.ActionLink call to not html encode what we pass as linkText - therefore we fall back on using a place holder that we replace after the fact.

So now we can do a

XML:
  1. @Html.ImageActionLink(
  2.    Url.Content("~/Content/Images/" + url),
  3.    alt,
  4.    "Details",
  5.    "Calender",
  6.    new { area = "", day = Model.Date.Day, month = Model.Date.Month, year = Model.Date.Year },
  7.    new { @class = "SelectDay", onClick = string.Format("DoCallbackBookingDetails({0},{1},{2}); return false;", Model.Date.Day, Model.Date.Month, Model.Date.Year) },
  8.    new { style = "border: 0px;" })

right inside Razor. Beautiful!

[UPDATE: There is an improved helper to create an image that has an actionlink url a href: Creating an improved ASP.NET MVC 3 Html.ImageActionLink]

December 5th, 2010 12:28 am | Comments (2)

Active Directory Tip #9 – How to escape the Path property

by Tobias Hertkorn on May 17th, 2010

Did you get the dreaded 0x80005000 COMException "Unknown error"? Maybe you should check, if your AD entry contains a forward slash '/' in its DN. Because there is a subtile difference between a correctly escaped distinguished name and a correctly escaped ldap path.

Observe:

PLAIN:
  1. Unescaped:                                 CN=Test\ Me \\ huhu \/ cool / \\/ // \\// \\\/// \\\\////! /////,OU=AdTests,DC=test,DC=domain,DC=local
  2. Escaped DN:                                CN=Test\\ Me \\\\ huhu \\/ cool / \\\\/ // \\\\// \\\\\\/// \\\\\\\\////! /////,OU=AdTests,DC=test,DC=domain,DC=local
  3. Escaped Path: LDAP://test.domain.local:389/CN=Test\\ Me \\\\ huhu \\\/ cool \/ \\\\\/ \/\/ \\\\\/\/ \\\\\\\/\/\/ \\\\\\\\\/\/\/\/! \/\/\/\/\/,OU=AdTests,DC=test,DC=domain,DC=local

Notice how forward slashes need to be escaped in addition to how distinguished names get escaped. This is a very subtile thing that is often overlooked. So when converting between DNs and Paths you have to use the following methods:

C#:
  1. private static readonly string LDAP_PREFIX = @"LDAP://test.domain.local:389/";
  2.  
  3. private static string ConvertDnToPath(string dn)
  4. {
  5.   var exploded = dn.ToCharArray();
  6.   StringBuilder sb = new StringBuilder();
  7.  
  8.   foreach (var @char in exploded)
  9.   {
  10.     if (@char == '/')
  11.     {
  12.       sb.Append('\\');
  13.     }
  14.     sb.Append(@char);
  15.   }
  16.  
  17.   return LDAP_PREFIX + sb.ToString();
  18. }
  19.  
  20.  
  21.  
  22. private static string ConvertPathToDn(string path)
  23. {
  24.   path = path.Substring(path.IndexOf('/', "LDAP://".Length));
  25.   // remove leading /
  26.   while (path.StartsWith("/"))
  27.   {
  28.     path = path.Substring(1);
  29.   }
  30.  
  31.  
  32.   if (!path.Contains('/'))
  33.   {
  34.     return path;
  35.   }
  36.  
  37.   StringBuilder sb = new StringBuilder(path);
  38.  
  39.   bool slashMode = false;
  40.   int backslashCount = 0;
  41.   for (int i = path.Length - 1; i>= 0; i--)
  42.   {
  43.     if (path[i] == '\\' && slashMode)
  44.     {
  45.       backslashCount++;
  46.     }
  47.     else if (slashMode)
  48.     {
  49.       if ((backslashCount % 2) != 0)
  50.       {
  51.         sb.Remove(i + 1, 1);
  52.       }
  53.       backslashCount = 0;
  54.       slashMode = false;
  55.     }
  56.  
  57.     if (path[i] == '/')
  58.     {
  59.       backslashCount = 0;
  60.       slashMode = true;
  61.     }
  62.   }
  63.  
  64.   return sb.ToString();
  65. }

May 17th, 2010 9:00 pm | Comments (0)

Active Directory Tip #8 – Set Password of user

by Tobias Hertkorn on May 14th, 2010

The password of a person entry is not a regular property with direct access for reading and writing. Instead it can't be read just indirectly written to via the setPassword method.

C#:
  1. string password = "secret";
  2. directoryEntry.Invoke("setPassword", password);
  3. directoryEntry.RefreshCache();

The caller must have the User-Force-Change-Password Extended Right to set the password with this method.

May 14th, 2010 8:21 pm | Comments (0)

Active Directory Tip #7 – Handling a Bit Property (e.g. userAccountControl – Account is disabled)

by Tobias Hertkorn on May 12th, 2010

Bit properties like User-Account-Control Attribute in the Active Directory are represented by regular integers. Therefore all the usual bit operation may be performed on those fields.

C#:
  1. public static bool GetBitAttribute(DirectoryEntry directoryEntry, string attributeName, int bitmask)
  2. {
  3.   int value = (int)DirectoryEntryHelper.GetAdObjectProperty(directoryEntry, attributeName);
  4.   bool result = (bitmask & value) == bitmask;
  5.   return result;
  6. }
  7.  
  8. public static void SetBitAttribute(DirectoryEntry directoryEntry, string attributeName, int bitmask, bool attributeValue)
  9. {
  10.   PropertyValueCollection property = directoryEntry.Properties[attributeName];
  11.   int value = (int)property.Value;
  12.   if (attributeValue)
  13.   {
  14.     value |= bitmask;
  15.   }
  16.   else
  17.   {
  18.     value &= ~bitmask;
  19.   }
  20.  
  21.   property.Value = value;
  22. }

With these helper functions deactivating a user would be accomplished by using

C#:
  1. SetBitAttribute(directoryEntry, "userAccountControl", ADS_UF_ACCOUNTDISABLE, true);

Just for convenience, here is the table form User-Account-Control Attribute converted to C# syntax:

C#:
  1. /// <summary>
  2. /// The logon script is executed.
  3. /// </summary>
  4. int ADS_UF_SCRIPT = 0x00000001;
  5. /// <summary>
  6. /// The user account is disabled.
  7. /// </summary>
  8. int ADS_UF_ACCOUNTDISABLE = 0x00000002;
  9. /// <summary>
  10. /// The home directory is required.
  11. /// </summary>
  12. int ADS_UF_HOMEDIR_REQUIRED = 0x00000008;
  13. /// <summary>
  14. /// The account is currently locked out.
  15. /// </summary>
  16. int ADS_UF_LOCKOUT = 0x00000010;
  17. /// <summary>
  18. /// No password is required.
  19. /// </summary>
  20. int ADS_UF_PASSWD_NOTREQD = 0x00000020;
  21. /// <summary>
  22. /// The user cannot change the password.
  23. /// </summary>
  24. int ADS_UF_PASSWD_CANT_CHANGE = 0x00000040;
  25. /// <summary>
  26. /// The user can send an encrypted password.
  27. /// </summary>
  28. int ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000080;
  29. /// <summary>
  30. /// This is an account for users whose primary account is in
  31. /// another domain. This account provides user access to this
  32. /// domain, but not to any domain that trusts this domain.
  33. /// Also known as a local user account.
  34. /// </summary>
  35. int ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0x00000100;
  36. /// <summary>
  37. /// This is a default account type that represents a typical user.
  38. /// </summary>
  39. int ADS_UF_NORMAL_ACCOUNT = 0x00000200;
  40. /// <summary>
  41. /// This is a permit to trust account for a system domain that
  42. /// trusts other domains.
  43. /// </summary>
  44. int ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0x00000800;
  45. /// <summary>
  46. /// This is a computer account for a computer that is a member
  47. /// of this domain.
  48. /// </summary>
  49. int ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x00001000;
  50. /// <summary>
  51. /// This is a computer account for a system backup domain controller
  52. /// that is a member of this domain.
  53. /// </summary>
  54. int ADS_UF_SERVER_TRUST_ACCOUNT = 0x00002000;
  55. /// <summary>
  56. /// The password for this account will never expire.
  57. /// </summary>
  58. int ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000;
  59. /// <summary>
  60. /// This is an MNS logon account.
  61. /// </summary>
  62. int ADS_UF_MNS_LOGON_ACCOUNT = 0x00020000;
  63. /// <summary>
  64. /// The user must log on using a smart card.
  65. /// </summary>
  66. int ADS_UF_SMARTCARD_REQUIRED = 0x00040000;
  67. /// <summary>
  68. /// The service account (user or computer account), under which a
  69. /// service runs, is trusted for Kerberos delegation. Any such service
  70. /// can impersonate a client requesting the service.
  71. /// </summary>
  72. int ADS_UF_TRUSTED_FOR_DELEGATION = 0x00080000;
  73. /// <summary>
  74. /// The security context of the user will not be delegated to a service
  75. /// even if the service account is set as trusted for Kerberos delegation.
  76. /// </summary>
  77. int ADS_UF_NOT_DELEGATED = 0x00100000;
  78. /// <summary>
  79. /// Restrict this principal to use only Data Encryption Standard (DES)
  80. /// encryption types for keys.
  81. /// </summary>
  82. int ADS_UF_USE_DES_KEY_ONLY = 0x00200000;
  83. /// <summary>
  84. /// This account does not require Kerberos pre-authentication for logon.
  85. /// </summary>
  86. int ADS_UF_DONT_REQUIRE_PREAUTH = 0x00400000;
  87. /// <summary>
  88. /// The user password has expired. This flag is created by the system
  89. /// using data from the Pwd-Last-Set attribute and the domain policy.
  90. /// </summary>
  91. int ADS_UF_PASSWORD_EXPIRED = 0x00800000;
  92. /// <summary>
  93. /// The account is enabled for delegation. This is a security-sensitive
  94. /// setting; accounts with this option enabled should be strictly
  95. /// controlled. This setting enables a service running under the account
  96. /// to assume a client identity and authenticate as that user to other
  97. /// remote servers on the network.
  98. /// </summary>
  99. int ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x01000000;

May 12th, 2010 8:14 pm | Comments (0)

Active Directory Tip #6 – Set Account never expires

by Tobias Hertkorn on May 10th, 2010

The account never expires checkbox in the MMC is a virtual option. Instead of being a separate property it is set using the accountExpires property. Setting it to the equivalent of 0 will result in an account that never expires.

See Account-Expires Attribute for more details.

C#:
  1. /// <summary>
  2. /// You still have to call CommitChanges() after using this method.
  3. /// </summary>
  4. private void SetAccountNeverExpires(DirectoryEntry directoryEntry)
  5. {
  6.   long accountExpires = 0;
  7.    
  8.   var accountExpiresOriginalValue = directoryEntry.Properties["accountExpires"].Value;
  9.  
  10.   Type type = accountExpiresOriginalValue.GetType();
  11.  
  12.   int highPart = (int)(accountExpires>> 32);
  13.   int lowPart = (int)(0xFFFFFFFF & accountExpires);
  14.  
  15.   type.InvokeMember("HighPart", BindingFlags.SetProperty, null, accountExpiresOriginalValue, new object[] { highPart });
  16.   type.InvokeMember("LowPart", BindingFlags.SetProperty, null, accountExpiresOriginalValue, new object[] { lowPart });
  17.  
  18.   SetAdObjectAttribute(directoryEntry, "accountExpires", accountExpiresOriginalValue);
  19. }

May 10th, 2010 9:02 pm | Comments (0)

Active Directory Tip #5 – Set Large Integer (e.g. Account expires date)

by Tobias Hertkorn on May 10th, 2010

Setting a large integer property is just as easy.

C#:
  1. /// <summary>
  2. /// You still have to call CommitChanges() after using this method.
  3. /// </summary>
  4. private void SetAccountExpiresDate(DirectoryEntry directoryEntry, DateTime expiresOn)
  5. {
  6.   long accountExpires = expiresOn.ToFileTimeUtc();
  7.    
  8.   var accountExpiresOriginalValue = directoryEntry.Properties["accountExpires"].Value;
  9.  
  10.   Type type = accountExpiresOriginalValue.GetType();
  11.  
  12.   int highPart = (int)(accountExpires>> 32);
  13.   int lowPart = (int)(0xFFFFFFFF & accountExpires);
  14.  
  15.   type.InvokeMember("HighPart", BindingFlags.SetProperty, null, accountExpiresOriginalValue, new object[] { highPart });
  16.   type.InvokeMember("LowPart", BindingFlags.SetProperty, null, accountExpiresOriginalValue, new object[] { lowPart });
  17.  
  18.   SetAdObjectAttribute(directoryEntry, "accountExpires", accountExpiresOriginalValue);
  19. }

May 10th, 2010 2:00 pm | Comments (0)

Active Directory Tip #4 – Get Large Integer (e.g. Account expires date)

by Tobias Hertkorn on May 4th, 2010

Certain properties, like usnChanged or accountExpires, are in an IADsLargeInteger format. One way to read out the long representation of the value of such a property is by reflection.

C#:
  1. private static long ConvertLargeIntegerToLong(object largeInteger)
  2. {
  3.   Type type = largeInteger.GetType();
  4.  
  5.   int highPart = (int)type.InvokeMember("HighPart", BindingFlags.GetProperty, null, largeInteger, null);
  6.   int lowPart = (int)type.InvokeMember("LowPart", BindingFlags.GetProperty | BindingFlags.Public, null, largeInteger, null);
  7.  
  8.   return (long)highPart <<32 | (uint)lowPart;
  9. }

That value may be converted to a DateTime value using the static DateTime.FromFileTimeUtc method.

C#:
  1. object accountExpires = DirectoryEntryHelper.GetAdObjectProperty(directoryEntry, "accountExpires");
  2. var asLong = ConvertLargeIntegerToLong(accountExpires);
  3.  
  4. if (asLong == long.MaxValue || asLong <= 0 || DateTime.MaxValue.ToFileTime() <= asLong)
  5. {
  6.   return DateTime.MaxValue;
  7. }
  8. else
  9. {
  10.   return DateTime.FromFileTimeUtc(asLong);
  11. }

May 4th, 2010 10:58 pm | Comments (1)
Tobi + C# = T# - Blogged blogoscoop