UserPrincipal.Save() - vshost32.exe has stopped working - Corrupt heap

0

I'm writing some software to import a CSV file into Active Directory (to create user accounts). At some point I know it was working perfectly importing multiple accounts. I'm not sure what I've changed as it's been a while since I last worked on it. But it now imports 2 accounts successfully and then crashes on the line below during the third loop iteration (however the third account is still created):

newUser.Save()

When it crashes I get the error "vshost32.exe has stopped working". I then enabled native code debugging and now get this error: "0xC0000374: A heap has been corrupted" and InvalidCastException (see immediate window at end of post for full error). For testing I've been deleting and recreating the same accounts. If I don't delete the first three accounts, the principal exists exception is handled and then the program crashes on the 4th iteration, and then the 5th and so on. But it never crashes on the first two. (The data I'm importing is identical except for numbers - E.g. sAMAccountNames: Test1, Test2, Test3 etc)

My Code

Private Sub bwImport_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bwImport.DoWork
    Dim _worker As BackgroundWorker = CType(sender, BackgroundWorker)

    Dim beginImport As New StartImport(AddressOf progressForm.StartImport)
    Me.Invoke(beginImport, New Object() {dtUsers.Rows.Count})

    Dim log As New UpdateLog(AddressOf progressForm.UpdateLog)

    '### TO DO: Check that all mandatory columns/attributes are in the DataTable

    '### TO DO: Check for duplicate sAMAccountNames, userPrincipalNames and Cononical Names

#If Not Debug Then
    Try
#End If

    Dim rowNum As Integer = 0 'Keep track of how many accounts have been created
        For Each row As DataRow In dtUsers.Rows
            Dim newUser As UserPrincipalEx = New UserPrincipalEx(adCtx)

            newUser.SamAccountName = row("sAMAccountName")
            newUser.SetPassword(row("Password"))

            'General Tab (of Template Exporter)
            If row.Table.Columns.Contains("initials") Then
                newUser.Initials = row("initials")
            End If

            If row.Table.Columns.Contains("givenName") Then
                newUser.GivenName = row("givenName")
            End If

            If row.Table.Columns.Contains("sn") Then
                newUser.Surname = row("sn")
            End If

            If row.Table.Columns.Contains("displayName") Then
                newUser.DisplayName = row("displayName")
            End If

            If row.Table.Columns.Contains("description") Then
                newUser.Description = row("description")
            End If

            If row.Table.Columns.Contains("physicalDeliveryOfficeName") Then
                newUser.Office = row("physicalDeliveryOfficeName")
            End If

            If row.Table.Columns.Contains("telephoneNumber") Then
                newUser.TelephoneNumber = row("telephoneNumber")
            End If

            If row.Table.Columns.Contains("wWWHomePage") Then
                newUser.WebPage = row("wWWHomePage")
            End If

            'Address Tab (of Template Exporter)
            If row.Table.Columns.Contains("streetAddress") Then
                newUser.Street = row("streetAddress")
            End If

            If row.Table.Columns.Contains("postOfficeBox") Then
                newUser.POBox = row("postOfficeBox")
            End If

            If row.Table.Columns.Contains("l") Then 'City
                newUser.City = row("l")
            End If

            If row.Table.Columns.Contains("st") Then 'State/Province
                newUser.State = row("st")
            End If

            If row.Table.Columns.Contains("postalCode") Then
                newUser.PostCode = row("postalCode")
            End If

            '### TO DO: Add country fields

            'Account Tab (of Template Exporter)
            If row.Table.Columns.Contains("userPrincipalName") Then
                newUser.UserPrincipalName = row("userPrincipalName")
            End If

            If row.Table.Columns.Contains("ResetPassword") Then
                If row("ResetPassword").ToString.ToLower = "yes" Then
                    newUser.ExpirePasswordNow() 'Force the user to change their password at next logon
                End If
            End If

            If row.Table.Columns.Contains("PreventPasswordChange") Then
                If row("PreventPasswordChange").ToString.ToLower = "yes" Then
                    newUser.UserCannotChangePassword = True
                End If
            End If

            If row.Table.Columns.Contains("PasswordNeverExpires") Then
                If row("PasswordNeverExpires").ToString.ToLower = "yes" Then
                    newUser.PasswordNeverExpires = True
                End If
            End If

            If row.Table.Columns.Contains("AccountDisabled") Then

                If row("AccountDisabled").ToString.ToLower = "yes" Then
                    newUser.Enabled = False
                Else
                    newUser.Enabled = True
                End If

            Else 'Enable the account by default if not specified
                newUser.Enabled = True
            End If

            If row.Table.Columns.Contains("accountExpires") Then
                Dim expireyDate As Date
                Date.TryParse(row("accountExpires"), expireyDate) 'Try to convert the data from row("accountExpires") into a date
                newUser.AccountExpirationDate = expireyDate
            End If

            'Profile Tab (of Template Exporter)
            If row.Table.Columns.Contains("profilePath") Then
                newUser.ProfilePath = row("profilePath")
            End If

            If row.Table.Columns.Contains("scriptPath") Then
                newUser.ScriptPath = row("scriptPath")
            End If

            If row.Table.Columns.Contains("homeDrive") Then
                newUser.HomeDrive = row("homeDrive")
            End If

            If row.Table.Columns.Contains("homeDirectory") Then
                newUser.HomeDirectory = row("homeDirectory")
            End If

            'Telephones Tab (of Template Exporter)
            If row.Table.Columns.Contains("homePhone") Then
                newUser.HomePhone = row("homePhone")
            End If

            If row.Table.Columns.Contains("pager") Then
                newUser.Pager = row("pager")
            End If

            If row.Table.Columns.Contains("mobile") Then
                newUser.Mobile = row("mobile")
            End If

            If row.Table.Columns.Contains("facsimileTelephoneNumber") Then
                newUser.Fax = row("facsimileTelephoneNumber")
            End If

            If row.Table.Columns.Contains("ipPhone") Then
                newUser.IPPhone = row("ipPhone")
            End If

            'Organization Tab
            If row.Table.Columns.Contains("title") Then
                newUser.Title = row("title")
            End If

            If row.Table.Columns.Contains("department") Then
                newUser.Department = row("department")
            End If

            If row.Table.Columns.Contains("company") Then
                newUser.Company = row("company")
            End If

        rowNum += 1
        _worker.ReportProgress(rowNum) 'Update progress dialog

        Try
            newUser.Save() 'Save the user to Active Directory
            Me.Invoke(log, New Object() {"Successfully created " + row("sAMAccountName") + " (" + row("displayName") + ")", frmProgress.LogType.Success})
        Catch ex As PrincipalExistsException
            Me.Invoke(log, New Object() {"Error creating " + row("sAMAccountName") + " (" + row("displayName") + "). " + ex.Message, frmProgress.LogType.Failure})
            Continue For
        End Try

        'Member Of Tab
        If row.Table.Columns.Contains("MemberOf") Then
            Dim groups() As String = row("MemberOf").ToString.Split(";")

            'Add the user to any specified groups
            Dim groupPrincipal As GroupPrincipal

            Try 'Try adding group(s)

                For Each group As String In groups
                    groupPrincipal = groupPrincipal.FindByIdentity(adCtx, group) 'Search for the group name, sid, sAMAccountName or display name

                    If groupPrincipal IsNot Nothing Then
                        groupPrincipal.Members.Add(newUser) 'Add the user to the group
                        groupPrincipal.Save()
                    Else
                        Me.Invoke(log, New Object() {"Unable to add " + row("sAMAccountName") + " to group: " + group + ". Group not found.", frmProgress.LogType.Failure})
                    End If

                Next

            Catch ex As PrincipalExistsException
                '### TO DO: Try to get group name in exception
                Me.Invoke(log, New Object() {"Error adding " + row("sAMAccountName") + " (" + row("displayName") + ") to " + "group(s). " + ex.Message, frmProgress.LogType.Failure})
            End Try

        End If

        newUser.Dispose() 'Dispose of the newUser object

        Next

#If Not Debug Then
    Catch ex As Exception
        MsgBox(ex.Message, MsgBoxStyle.Critical)
    End Try
#End If


End Sub

Immediate Window (When Native Debugging is enabled)

Critical error detected c0000374
First-chance exception at 0x76fbf996 in AD User Importer.exe: 0xC0000374: A heap has been corrupted.
A first chance exception of type 'System.InvalidCastException' occured in System.DirectoryServices.AccountManagement.dll
vb.net
directoryservices
asked on Stack Overflow Jul 8, 2015 by Daniel

1 Answer

1

I think your issue lies in this block:

Try
    newUser.Save() 'Save the user to Active Directory
    Me.Invoke(log, New Object() {"Successfully created " + row("sAMAccountName") + " (" + row("displayName") + ")", frmProgress.LogType.Success})
Catch ex As PrincipalExistsException
    Me.Invoke(log, New Object() {"Error creating " + row("sAMAccountName") + " (" + row("displayName") + "). " + ex.Message, frmProgress.LogType.Failure})
    Continue For
End Try

This code handles exceptions but does not dispose newUser before continuing with the next loop iteration.

I recently began to receive similar heap exceptions and after a close examination I realized that I was not disposing my UserPrincipal object. Once I correctly disposed of the object the issue seems to have stopped.

You should wrap your newUser object in a Using block:

For Each row As DataRow In dtUsers.Rows
    Using newUser As UserPrincipalEx = New UserPrincipalEx(adCtx)
        newUser.SamAccountName = row("sAMAccountName")
        newUser.SetPassword(row("Password"))

        ' ... the remainder of the code
        ' ... now wrapped in a Using block
    End Using
Next

The newUser object will be automatically disposed no matter how the Using block is exited. Because the Using block disposes for you, you can remove the explicit call to newUser.Dispose().

If the Using block is not available in your version of VB.Net then you should wrap the loop in a Try...Finally block and explicitly dispose the newUser in the Finally block.

answered on Stack Overflow Oct 4, 2018 by Jake

User contributions licensed under CC BY-SA 3.0