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)
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
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
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.
User contributions licensed under CC BY-SA 3.0