Search code examples
directoryservicesaccount-managementuserprincipal

Looking for a Full S.DS.AM Sample with many AD extensions already written


System.DirectoryServices.AccountManagement can be extended to support additional properties for reading and writing AD properties.

Is anyone aware of a full/complete sample implementation that works for AD, Exchange 2003 or 2010?


Solution

  • There isn't anything online that I know of, but you are welcome to my collection (which I've included).

    One thing you'll probably notice about my code is that I've almost completely replaced the standard get/set operations with my own code which writes directly to the underlying DirectoryEntry. This is because the built in set operation is not designed to handle data types which are arrays of arrays (such as the jpegPhoto attribute which is an array of byte arrays, with each byte array representing a picture).

    First is a bunch of extension methods which I use for my various get/set operations.

        ''' <summary>
        ''' Checks if an attribute is available on the underlying object.
        ''' </summary>
        <Extension()> _
        Public Function IsAttributeDefined(ByVal prin As Principal, ByVal attribute As String) As Boolean
            'since some attributes may not exist in all schemas check to see if it exists first
            Dim uo As DirectoryEntry = DirectCast(prin.GetUnderlyingObject(), DirectoryEntry)
    
            'check for property, if it's not found return an empty array
            Return uo.Properties.Contains(attribute)
        End Function
    
    #Region "Get Helpers"
        ''' <summary>
        ''' This function is the foundation for retrieving data
        ''' </summary>
        <Extension()> _
        Public Function ExtensionGetAttributeObject(ByVal prin As Principal, ByVal attribute As String) As Object()
            'check if the attribute exists on this object
            If IsAttributeDefined(prin, attribute) Then
                'if property exists then return the data
                Dim dirObj As DirectoryEntry = prin.GetUnderlyingObject()
                Dim val As Object() = (From c As Object In dirObj.Properties(attribute) Select c).ToArray()
    
                Return val
            Else
                'return an empty array if the attribute is not defined
                Return New Object(-1) {}
            End If
        End Function
    
        ''' <summary>
        ''' This is the primary function for retrieving attributes that contain only one value
        ''' </summary>
        <Extension()> _
        Public Function ExtensionGetSingleValue(ByVal prin As Principal, ByVal attribute As String) As Object
            'get the object
            Dim attributeValues() As Object = ExtensionGetAttributeObject(prin, attribute)
    
            'if the item length = 1 then return the first value, else don't
            If attributeValues.Length = 1 Then
                Return attributeValues(0)
            Else
                Return Nothing
            End If
        End Function
    
        ''' <summary>
        ''' Returns the string value of an attribute
        ''' </summary>
        ''' <remarks>(null if no value found)</remarks>
        <Extension()> _
        Public Function ExtensionGetSingleString(ByVal prin As Principal, ByVal attribute As String) As String
            Dim o As Object = ExtensionGetSingleValue(prin, attribute)
            If o IsNot Nothing Then
                Return o.ToString()
            Else
                Return String.Empty
            End If
        End Function
    
        ''' <summary>
        ''' Returns all of the strings contained in a multi-value attribute
        ''' </summary>
        <Extension()> _
        Public Function ExtensionGetMultipleString(ByVal prin As Principal, ByVal attribute As String) As String()
            'get the object array for this attribute
            Dim attributeValues() As Object = ExtensionGetAttributeObject(prin, attribute)
    
            'create a string array of the same length as the object array
            Dim array As String() = New String(attributeValues.Length - 1) {}
    
            'and copy over all items, converting them to strings as we go
            For i As Integer = 0 To attributeValues.Length - 1
                array(i) = attributeValues(i).ToString()
            Next
    
            'return the string array
            Return array
        End Function
    
        ''' <summary>
        ''' Returns the date value of an attribute 
        ''' </summary>
        ''' <remarks>(null if no value found)</remarks>
        <Extension()> _
        Public Function ExtensionGetSingleDate(ByVal prin As Principal, ByVal attribute As String) As String
            Dim o As Object = ExtensionGetSingleValue(prin, attribute)
            If o IsNot Nothing Then
                Dim dt As DateTime = Convert.ToDateTime(o)
                Return dt
            Else
                Return Nothing
            End If
        End Function
    
        ''' <summary>
        ''' Returns the principle represented by a column containing a single distinguished name
        ''' </summary>
        <Extension()> _
        Public Function ExtensionGetSingleDistinguishedName(ByVal prin As Principal, ByVal attribute As String) As Principal
            'get the distinguished name of the object as a string
            Dim dn As String = ExtensionGetSingleString(prin, attribute)
    
            'check for null
            If String.IsNullOrEmpty(dn) Then
                Return Nothing
            End If
    
            'get the principal represented by the DN
            Dim prinF As Principal = Principal.FindByIdentity(prin.Context, dn)
    
            'if it exists then prepare to return it
            If prinF IsNot Nothing Then
                'if the object is a userprincipal then get the user detailed principal for it.
                If TypeOf prinF Is UserPrincipal Then
                    prinF = UserDetailedPrinciple.FindByIdentity(prin.Context, prinF.Name)
                End If
    
                'return the principal
                Return prinF
            End If
    
            'if all else fails return nothing
            Return Nothing
        End Function
    
        <Extension()> _
        Public Function ExtensionGetMultipleDistinguishedNames(ByVal prinParent As Principal, ByVal attribute As String) As Principal()
            'get the distinguished name of the object as a string
            Dim dn() As String = ExtensionGetMultipleString(prinParent, attribute)
    
            'array to hold list of principles
            Dim al As New List(Of Principal)()
    
            For Each d As String In dn
                'get the principal represented by the DN
                Dim prin As Principal = Principal.FindByIdentity(prinParent.Context, d)
    
                'if it exists then prepare to return it
                If prin IsNot Nothing Then
                    'if the object is a userprincipal then get the user detailed principal for it.
                    If TypeOf prin Is UserPrincipal Then
                        prin = UserDetailedPrinciple.FindByIdentity(prin.Context, prin.Name)
                    ElseIf TypeOf prin Is GroupPrincipal Then
                        prin = GroupPrincipal.FindByIdentity(prin.Context, prin.Name)
                    End If
    
                    'return the principal
                    al.Add(prin)
                End If
            Next
    
            'return list of principles
            Return al.ToArray()
        End Function
    
        ''' <summary>
        ''' Gets the bytes contained in an Octet String
        ''' </summary>
        <Extension()> _
        Public Function ExtentsionGetBytes(ByVal prin As Principal, ByVal attribute As String) As Byte()
            'get the data
            Dim o As Object = ExtensionGetSingleValue(prin, attribute)
            'check for nulls
            If o Is Nothing Then
                Return Nothing
            End If
    
            'get the byte array
            Dim byteArray() As Byte = DirectCast(o, Byte())
    
            'return the data
            Return byteArray
        End Function
    
        ''' <summary>
        ''' Gets the image contained in an Octet String type attribute
        ''' </summary>
        <Extension()> _
        Public Function ExtensionGetImage(ByVal prin As Principal, ByVal attribute As String) As Image
            'get bytes for attribute
            Dim bytearray() As Byte = ExtentsionGetBytes(prin, attribute)
    
            'if none returned return nothing
            If bytearray Is Nothing Then
                Return Nothing
            End If
    
            'read the bytes into a memory stream
            Dim ms As New MemoryStream(bytearray)
    
            'convert the memory stream to a bitmap and return it
            Return New Bitmap(ms)
        End Function
    
        <Extension()> _
        Public Function ExtensionGetImages(ByVal prin As Principal, ByVal attribute As String) As Image()
            'get all values in attribute
            Dim vals() As Object = ExtensionGetAttributeObject(prin, attribute)
    
            'array to hold images to be returned
            Dim al As New List(Of Image)()
    
            For Each o As Object In vals
                'get bytes
                Dim bytearray() As Byte = DirectCast(o, Byte())
    
                'if no data skip entry
                If bytearray Is Nothing Then
                    Continue For
                End If
    
                'read the bytes into a memory stream
                Dim ms As New MemoryStream(bytearray)
    
                'convert the memory stream to a bitmap and add to the array
                al.Add(New Bitmap(ms))
            Next
            'return the list of images as an array.
            Return al.ToArray()
        End Function
    #End Region
    
    #Region "Set Helpers"
        Private Sub ExtensionSetDE(ByVal de As DirectoryEntry, ByVal attribute As String, ByVal value As Object)
            'check value, if it's null then don't add (null means clear only)
            If value IsNot Nothing Then
                de.Properties(attribute).Add(value)
            End If
        End Sub
    
        <Extension()> _
        Public Sub ExtensionSetValue(ByVal prin As Principal, ByVal attribute As String, ByVal value As Object)
            Dim uo As DirectoryEntry = prin.GetUnderlyingObject()
            uo.Properties(attribute).Clear()
            ExtensionSetDE(uo, attribute, value)
        End Sub
    
        <Extension()> _
        Public Sub ExtensionSetStringValue(ByVal prin As Principal, ByVal attribute As String, ByVal value As String)
            If String.IsNullOrEmpty(value) Then
                value = Nothing
            End If
    
            ExtensionSetValue(prin, attribute, value)
        End Sub
    
        <Extension()> _
        Public Sub ExtensionSetMultipleValueDirect(ByVal prin As Principal, ByVal attribute As String, ByVal values() As Object)
            'Normal ExtensionSet does not support saving array type values (octet string)
            ' so we set it directly on the underlying object
            Dim uo As DirectoryEntry = prin.GetUnderlyingObject()
            uo.Properties(attribute).Clear()
    
            If values IsNot Nothing Then
                For Each v As Object In values
                    ExtensionSetDE(uo, attribute, v)
                Next
            End If
        End Sub
    
        <Extension()> _
        Public Sub ExtensionSetImage(ByVal prin As Principal, ByVal attribute As String, ByVal img As Image)
            'set data to attribute
            ExtensionSetValue(prin, attribute, img.SaveImageToByteArray())
        End Sub
    
        <Extension()> _
        Public Sub ExtensionSetImages(ByVal prin As Principal, ByVal attribute As String, ByVal img() As Image)
            'array list to hold the values temporarily
            Dim al As New ArrayList()
    
            'convert each image into a byte array
            For Each i As Image In img
                al.Add(i.SaveImageToByteArray())
            Next
    
            'set image array as value on attribute
            ExtensionSetMultipleValueDirect(prin, attribute, al.ToArray())
        End Sub
    
        <Extension()> _
        Public Function SaveImageToByteArray(ByVal img As Image) As Byte()
            'create a memory strea
            Dim ms As New MemoryStream()
            'write the image to the stream
            img.Save(ms, Imaging.ImageFormat.Jpeg)
    
            'save data to a byte array
            Dim bytes() As Byte = ms.ToArray()
    
            Return bytes
        End Function
    
        <Extension()> _
        Public Sub ExtensionSetMultipleDistinguishedNames(ByVal prin As Principal, ByVal attribute As String, ByVal dns() As Principal)
            'convert user principles into distinguished names
            Dim sc As New ArrayList()
            For Each u As UserDetailedPrinciple In dns
                sc.Add(u.DistinguishedName)
            Next
    
            ExtensionSetMultipleValueDirect(prin, attribute, sc.ToArray())
        End Sub
    
        ''' <summary>
        ''' Helps set the Thumbnail photo by resizing main photo and also saving original (possibly resized to 300xvariable)
        ''' to JpegPhoto.
        ''' </summary>
        ''' <param name="imgO">The iamge to use as the users thumbnail photo</param>
        ''' <remarks>You still NEED to call .Save() after calling this sub
        ''' as this sub does not call save().
        ''' </remarks>
        <Extension()> _
        Public Sub SetUserPhoto(ByVal prin As Principal, ByVal imgO As Image)
            'resize the image for thumbnail
            Dim imgN As Bitmap = ResizeImage(imgO, 100)
    
            'check if we need to resize for medium sized image (300px high max
            Dim imgM As Bitmap
            If imgO.Height > 300 Then
                imgM = ResizeImage(imgO, 300)
            Else
                imgM = imgO
            End If
    
            'save small image to the users profile
            ExtensionSetImage(prin, "thumbnailPhoto", imgN)
            'save original to the jpegPhoto attribute
            ExtensionSetImages(prin, "jpegPhoto", New Image() {imgM})
        End Sub
    
        Private Function ResizeImage(ByVal imgO As Bitmap, ByVal Height As Integer) As Bitmap
            'if the image is smaller/equal to the requested height return original
            If imgO.Height <= Height Then
                Return imgO
            End If
    
            'images are fixedHeightxVariable, so we need to calculate the variable portion
            Dim width As Integer = (Convert.ToDecimal(imgO.Width) / Convert.ToDecimal(imgO.Height)) * Height
    
            'resize the image
            Dim imgN As New Bitmap(width, Height)
            Dim g As Graphics = Graphics.FromImage(imgN)
            g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
            g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
    
            'draw in resized form
            g.DrawImage(imgO, 0, 0, width, Height)
    
            'return resized image
            Return imgN
        End Function
    
        <Extension()> _
        Public Function Rename(ByVal prin As Principal, ByVal NewName As String) As Principal
            'escape commas
            NewName = NewName.Replace(",", "\,")
    
            'get directory object for move
            Dim de As DirectoryEntry = prin.GetUnderlyingObject()
    
            'move
            de.Rename(String.Format("CN={0}", NewName))
            de.CommitChanges()
    
            'get the new object by name and return it
            Return New ADConnection(prin.Context).GetPrincipalByName(prin.Guid.ToString())
        End Function
    #End Region
    

    Here is the code in action in my custion UserPrinciple:

    <DirectoryObjectClass("user")> _
    <DirectoryRdnPrefix("CN")> _
    Public Class UserDetailedPrinciple
        Inherits UserPrincipal
    
        <DirectoryProperty("initials")> _
        Public Property MiddleInitial() As String
            Get
                Return ExtensionGetSingleString("initials")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("initials", value)
            End Set
        End Property
    
        <DirectoryProperty("wWWHomePage")> _
        Public Property HomePage() As String
            Get
                Return ExtensionGetSingleString("wWWHomePage")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("wWWHomePage", value)
            End Set
        End Property
    
        <DirectoryProperty("url")> _
        Public Property URLs() As String()
            Get
                Return ExtensionGetMultipleString("url")
            End Get
            Set(ByVal value As String())
                ExtensionSetMultipleValueDirect("url", value)
            End Set
        End Property
    
        <DirectoryProperty("info")> _
        Public Property Notes() As String
            Get
                Return ExtensionGetSingleString("info")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("info", value)
            End Set
        End Property
    
        Public ReadOnly Property ObjectType() As String
            Get
                Dim types() As String = ExtensionGetMultipleString("objectClass")
                Return types.Last()
            End Get
        End Property
    
        <DirectoryProperty("thumbnailPhoto")> _
        Public Property ThumbnailPhoto() As Image
            Get
                Return ExtensionGetImage("thumbnailPhoto")
            End Get
            Set(ByVal value As Image)
                ExtensionSetImage("thumbnailPhoto", value)
            End Set
        End Property
    
        <DirectoryProperty("thumbnailLogo")> _
        Public Property ThumbnailLogo() As Image
            Get
                Return ExtensionGetImage("thumbnailLogo")
            End Get
            Set(ByVal value As Image)
                ExtensionSetImage("thumbnailLogo", value)
            End Set
        End Property
    
        <DirectoryProperty("jpegPhoto")> _
        Public Property JpegPhoto() As Image()
            Get
                Return ExtensionGetImages("jpegPhoto")
            End Get
            Set(ByVal value As Image())
                ExtensionSetImages("jpegPhoto", value)
            End Set
        End Property
    
        <DirectoryProperty("title")> _
        Public Property Title() As String
            Get
                Return ExtensionGetSingleString("title")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("title", value)
            End Set
        End Property
    
        <DirectoryProperty("department")> _
        Public Property Department() As String
            Get
                Return ExtensionGetSingleString("department")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("department", value)
            End Set
        End Property
    
        <DirectoryProperty("company")> _
        Public Property Company() As String
            Get
                Return ExtensionGetSingleString("company")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("company", value)
            End Set
        End Property
    
        <DirectoryProperty("manager")> _
        Public Property Manager() As UserDetailedPrinciple
            Get
                Dim mgr As UserDetailedPrinciple = ExtensionGetSingleDistinguishedName("manager")
    
                If mgr IsNot Nothing Then
                    If Me.Guid <> mgr.Guid Then
                        Return mgr
                    End If
                End If
    
                Return Nothing
            End Get
            Set(ByVal value As UserDetailedPrinciple)
                'check for nulls
                If value Is Nothing Then
                    ExtensionSetStringValue("manager", Nothing)
                Else
                    ExtensionSetStringValue("manager", value.DistinguishedName)
                End If
            End Set
        End Property
    
        <DirectoryProperty("assistant")> _
        Public Property Assistant() As UserDetailedPrinciple
            Get
                Dim assist As UserDetailedPrinciple = ExtensionGetSingleDistinguishedName("assistant")
    
                If assist IsNot Nothing Then
                    Return assist
                End If
    
                Return Nothing
            End Get
            Set(ByVal value As UserDetailedPrinciple)
                'check for nulls
                If value Is Nothing Then
                    ExtensionSetStringValue("assistant", Nothing)
                Else
                    ExtensionSetStringValue("assistant", value.DistinguishedName)
                End If
            End Set
        End Property
    
        <DirectoryProperty("directReports")> _
        Public Property DirectReports() As Principal()
            Get
                Dim dReports As Principal() = ExtensionGetMultipleDistinguishedNames("directReports")
                Return dReports
            End Get
            Set(ByVal value As Principal())
                ExtensionSetMultipleDistinguishedNames("directReports", value)
            End Set
        End Property
    
        <DirectoryProperty("homePhone")> _
         Public Property HomePhone() As String
            Get
                Return ExtensionGetSingleString("homePhone")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("homePhone", value)
            End Set
        End Property
    
        <DirectoryProperty("pager")> _
         Public Property Pager() As String
            Get
                Return ExtensionGetSingleString("pager")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("pager", value)
            End Set
        End Property
    
        <DirectoryProperty("otherTelephone")> _
        Public Property OtherTelephone() As String()
            Get
                Return ExtensionGetMultipleString("otherTelephone")
            End Get
            Set(ByVal value As String())
                ExtensionSetMultipleValueDirect("otherTelephone", value)
            End Set
        End Property
    
        <DirectoryProperty("physicalDeliveryOfficeName")> _
        Public Property PhysicalLocation() As String
            Get
                Return ExtensionGetSingleString("physicalDeliveryOfficeName")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("physicalDeliveryOfficeName", value)
            End Set
        End Property
    
        <DirectoryProperty("l")> _
        Public Property AddressCity() As String
            Get
                Return ExtensionGetSingleString("l")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("l", value)
            End Set
        End Property
    
        <DirectoryProperty("postOfficeBox")> _
        Public Property AddressPOBox() As String
            Get
                Return ExtensionGetSingleString("postOfficeBox")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("postOfficeBox", value)
            End Set
        End Property
    
        <DirectoryProperty("st")> _
        Public Property AddressState() As String
            Get
                Return ExtensionGetSingleString("st")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("st", value)
            End Set
        End Property
    
        <DirectoryProperty("streetAddress")> _
        Public Property Address() As String
            Get
                Return ExtensionGetSingleString("streetAddress")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("streetAddress", value)
            End Set
        End Property
    
        <DirectoryProperty("postalCode")> _
        Public Property AddressZipCode() As String
            Get
                Return ExtensionGetSingleString("postalCode")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("postalCode", value)
            End Set
        End Property
    
        <DirectoryProperty("c")> _
         Public Property AddressCountry() As String
            Get
                Return ExtensionGetSingleString("c")
            End Get
            Set(ByVal value As String)
                ExtensionSetStringValue("c", value)
            End Set
        End Property
    
        <DirectoryProperty("whenCreated")> _
        Public ReadOnly Property Created() As Nullable(Of DateTime)
            Get
                Return ExtensionGetSingleDate("whenCreated")
            End Get
        End Property
    
        <DirectoryProperty("whenChanged")> _
        Public ReadOnly Property LastModified() As Nullable(Of DateTime)
            Get
                Return ExtensionGetSingleDate("whenChanged")
            End Get
        End Property
    
        Public Sub New()
            MyBase.New(ADConnection.CurrentADPrincipalContext)
        End Sub
        Public Sub New(ByVal context As PrincipalContext)
            MyBase.New(context)
    
        End Sub
    
        Public Overloads Shared Function FindByIdentity(ByVal context As PrincipalContext, ByVal identityValue As String) As UserDetailedPrinciple
            Return DirectCast(Principal.FindByIdentityWithType(context, GetType(UserDetailedPrinciple), identityValue), UserDetailedPrinciple)
        End Function
    
        Public Overloads Shared Function FindByIdentity(ByVal context As PrincipalContext, ByVal identityType As IdentityType, ByVal identityValue As String) As UserDetailedPrinciple
            Return DirectCast(Principal.FindByIdentityWithType(context, GetType(UserDetailedPrinciple), identityType, identityValue), UserDetailedPrinciple)
        End Function
    End Class