By David Wiseman (Administrator)Created 21 Jan 2008, Modified 16 Mar 2012
My Rating:
Vote
Rating:
(6 votes)
Views:78920
Downloads:2473

Update User Attributes from CSV File

Language:  VBScript

Description

Update active directory user attributes from a CSV File. (See Notes section for additional information and instructions)


Notes

Introduction

This script can be used to update Active Directory User attributes from a CSV file.  One column in the CSV file is used to match rows in the CSV file to user accounts in Active Directory and the other columns are used to update attributes.  Normally the Pre-Windows 2000 username (sAMAccountName) attribute is used to match rows in the CSV file to user accounts in Active Directory, but you can easily modify the script to use a different attribute if required.  For example, you might want to identify the user account you want to update in the CSV file by email address (mail attribute) rather than username. 
 
As an alternative to this script, you might also to consider using the Bulk AD Users application (freeware) available on this website.  Bulk AD Users supports updating users via CSV file and it includes some other tools for updating users accounts such as the Edit In Excel and Bulk Modify features.  The Bulk AD Users application can make it easier for you to apply the update and it also produces an XML log file of the update which can be used to rollback changes.  That's not to say that Bulk AD Users is a complete replacement for this script as the script allows you to customize the code for your own requirements, and it can also be automated via task manager if required. 
 

Bulk Update Considerations

There is always an element of risk involved in performing bulk updates to Active Directory.  Please be sure to test updates in a QA environment before applying them to your live domain.  Also, it's a good idea to have a plan of action prepared should something go wrong with the update.  Taking a backup of your Active Directory database is a sensible precaution, and it also pays to be familiar with backup and restore techniques.  If you have a large domain or a domain that is connected over slow WAN links, you might also need to consider the impact of replication traffic when performing bulk updates.  Bulk updates can save an enormous amount of time and they normally run very smoothly if planned correctly.  

Setup

The first step in updating user accounts from a CSV file is to produce a valid CSV input file for the script.  A CSV file is simply a text file containing rows and columns of data like a spreadsheet and they can be read and written to by spreadsheet applications like Microsoft Excel.  There is normally a way to extract data from databases into CSV text files.  It's also possible to produce a text file manually by using an application such as notepad.  See here for more information on producing CSV files.
 
As well as taking care to ensure that the CSV file you have produced is valid, you also need to ensure that the CSV file includes a header row.  The header row is used by the script to identity the column names and the column names should correspond to the attribute names used by Active Directory.  The attribute names are not always obvious, so please take care to ensure that you are using the correct names.  For example, the surname attribute is "sn" and the first name attribute is "givenName".  As mentioned previously, the username (pre windows 2000) is used by default to match rows in the CSV file to the user accounts in Active Directory.  This attribute name is "sAMAccountName" and must be included in your CSV file (unless you plan to use a different search attribute).  You might find the Active Directory Schema Guide on this website useful for identifying the correct attribute names to use.
 
If you don't want to edit the script, the CSV file should be called "usermod.csv" and it should be located on the root of the "C:\" drive.  If you want to use a different filename or location, edit the strCSVFolder and strCSVFile attributes in the setup section of the script.  Change the strSearchAttribute if you want to use a different attribute for matching rows in the CSV file to users in Active Directory (e.g. userPrincipalName or mail attribute).  The attribute you choose for the strSearchAttribute should be unique as the script expects a maximum of 1 user to be returned for any attribute value.  If multiple users are found with the same attribute value, no user accounts will be updated.
 
The configuration of the script and the CSV file is now complete.  The script is designed to run in cscript mode (described in this article).  If you double click the script, the script will run in wscript mode - the script will work ok, but you will get a lot of annoying prompts.  To run in cscript mode, open a command prompt and change the current directory to the one that contains the script.  Use the following command to run the script:
 
cscript csv_user_update.vbs
 
The results of the script will be output to the console window.  If you prefer to have the results stored in a text file, use the following command instead:
 
cscript csv_user_update.vbs >> results.txt

Extracting CSV Data From Active Directory

If you would like to extract data as a CSV file from Active Directory as a base for your update, please refer to the scripts below:
 
Extract User Data To a CSV File

Custom Attribute Handling

The script should support most single-valued attributes by default. In addition, special handling has been added for the following attributes:

Attribute Name
Description
cn
This attribute is the "Name" column that is shown in Active Directory Users & Computers and it's a standard attribute in AD.  The cn (common name) forms part of an objects distinguished name.  This attribute must be unique within it's parent container (organizational unit).  This attribute can be read the same as any other attribute, but to change the value you need to use the "MoveHere" method.
TerminalServicesProfilePath
Terminal Services attributes are stored in the "userparameters" attribute in Active Directory.  Modifying this attribute directly isn't recommended, but it is possible to modify these attributes via the IADsTSUserEx interface.  The TsUserEx.dll is required to modify these values, otherwise you will get a "Object doesn't support this property or method" error.  To fix this you will need to install the Windows 2000/2003 Administration Tools Pack.
TerminalServicesHomeDirectory
TerminalServicesHomeDrive
AllowLogon
Password
Microsoft doesn't provide any access to user passwords stored in Active Directory via ADSI, either in encrypted or unencrypted format.  It is possible to change a password by calling the "SetPassword" method though.  The script won't output the previous value as it does with other attributes.  Note: Please take care to ensure passwords meet length and complexity requirements.
Manager_sAMAccountName
The manager attribute in active directory requires a distinguished name. e.g.
CN=David.Wiseman,OU=MyUsers,DC=WiseSoft,DC=co,DC=UK
To make this attribute easier to modify, you can specify "Manager_sAMAccountName" as the attribute name in the CSV header instead of "manager".  This allows you to use the username (Pre Windows 2000) instead of the manager's distinguished name. e.g.
David.Wiseman

You might want to refer to the Active Directory Schema Guide to help identify the correct attribute names for other attributes.
 
Note: Bulk AD Users provides better support for multi-valued attributes if required.

Troubleshooting

Provider cannot be found error

The script uses the Microsoft.Jet.OLEDB.4.0 provider.  If you are using x64, you will need to use the 32bit script host for this provider to be available. To do this, open a command prompt and navigate to the "SysWow64" folder in your Windows directory. e.g.
C:\Windows\SysWOW64\
 
You can now run cscript from this location, passing the full location of your script file. e.g.
 
cscript "C:\update_users_csv.vbs"

Data Not Imported as Expected

This script uses the Microsoft.Jet.OLEDB.4.0 provider for parsing the CSV file.  Sometimes the data type gets mis-interpreted and the data is not imported as expected.  An example of this is where you are updating the telephoneNumber attribute and your telephone numbers start with a leading zero.  The provider is likely to interpret this as a number rather than a string which results in the leading zeros getting truncated.  To get around this issue, you can create a schema.ini file.  If you have a CSV file consisting only of sAMAccountName and telephoneNumber columns, your schema.ini file might look something like this:
 
[usermod.csv]
ColNameHeader=True
Format=CSVDelimited
Col1=sAMAccountName Text
Col2=telephoneNumber Text
 
For more information on schema.ini files, please refer to this article.

Version History

Version Date Notes
1.1 2009-11-19 Changed script to use CSV header
Added support for terminal services attributes
Added support for changing passwords
1.0 2008-01-21 Initial release.

 
Code

Line Numbers: On  Off      Plain Text
OPTION EXPLICIT ' Variables must be declared
' *************************************************
' * Instructions
' *************************************************

' Edit the variables in the "Setup" section as required.
' Run this script from a command prompt in cscript mode.
' e.g. cscript usermod.vbs
' You can also choose to output the results to a text file:
' cscript usermod.csv >> results.txt

' *************************************************
' * Constants / Decleration
' *************************************************
Const adOpenStatic = 3
Const adLockOptimistic = 3
Const adCmdText = &H0001
Const ADS_PROPERTY_CLEAR = 1

DIM strSearchAttribute 
DIM strCSVHeader, strCSVFile, strCSVFolder
DIM strAttribute, userPath
DIM userChanges
DIM cn,cmd,rs
DIM objUser
DIM oldVal, newVal
DIM objField
DIM blnSearchAttributeExists
' *************************************************
' * Setup
' *************************************************

' The Active Directory attribute that is to be used to match rows in the CSV file to
' Active Directory user accounts.  It is recommended to use unique attributes.
' e.g. sAMAccountName (Pre Windows 2000 Login) or userPrincipalName
' Other attributes can be used but are not guaranteed to be unique.  If multiple user 
' accounts are found, an error is returned and no update is performed.
strSearchAttribute = "sAMAccountName" 'User Name (Pre Windows 2000)

' Folder where CSV file is located 
strCSVFolder = "C:\"
' Name of the CSV File
strCSVFile = "usermod.csv"

' *************************************************
' * End Setup
' *************************************************

' Setup ADO Connection to CSV file
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")

cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
          "Data Source=" & strCSVFolder & ";" & _
          "Extended Properties=""text;HDR=YES;FMT=Delimited"""

rs.Open "SELECT * FROM [" & strCSVFile & "]", _
          cn, adOpenStatic, adLockOptimistic, adCmdText

' Check if search attribute exists
blnSearchAttributeExists=false
for each objField in rs.Fields
	if UCASE(objField.Name) = UCASE(strSearchAttribute) then
		blnSearchAttributeExists=true
	end if
Next
		
if blnSearchAttributeExists=false then
	MsgBox "'" & strSearchAttribute & "' attribute must be specified in the CSV header." & _
		VbCrLf & "The attribute is used to map the data the csv file to users in Active Directory.",vbCritical
	wscript.quit
end if

' Read CSV File
Do Until rs.EOF
	' Get the ADsPath of the user by searching for a user in Active Directory on the search attribute
	' specified, where the value is equal to the value in the csv file.
	' e.g. LDAP://cn=user1,cn=users,dc=wisesoft,dc=co,dc=uk
	userPath = getUser(strSearchAttribute,rs(strSearchAttribute))
	' Check that an ADsPath was returned
	if LEFT(userPath,6) = "Error:" then
		wscript.echo userPath
	else
		wscript.echo userPath
		' Get the user object
		set objUser = getobject(userpath)
		userChanges = 0
		' Update each attribute in the CSV string
		for each objField in rs.Fields
			strAttribute = objField.Name
			oldval = ""
			newval = ""
			' Ignore the search attribute (this is used only to search for the user account)
			if UCASE(strAttribute) <> UCASE(strSearchAttribute) and UCASE(strAttribute) <> "NULL" then
				newVal = rs(strAttribute) ' Get new attribute value from CSV file
				if ISNULL(newval) then
					newval = ""
				end If
				' Special handling for common-name attribute. If the new value contains
				' commas they must be escaped with a forward slash.
				If strAttribute = "cn" then
					newVal = REPLACE(newVal,",","\,")
				end If
				' Read the current value before changing it
				readAttribute strAttribute
								
				' Check if the new value is different from the update value
				if oldval <> newval then
					wscript.echo "Change " & strAttribute & " from '" & oldVal & "' to '" & newVal & "'"
					' Update attribute
					writeAttribute strAttribute,newVal
					' Used later to check if any changes need to be committed to AD
					userChanges = userChanges + 1
				end If
			end If
		next
		' Check if we need to commit any updates to AD
		if userChanges > 0 then
			' Allow script to continue if an update fails
			on error resume next
			err.clear
			' Save Changes to AD
			objUser.setinfo
			' Check if update succeeded/failed
			if err.number <> 0 then
				wscript.echo "Commit Changes: Failed. " & err.description
				err.clear
			else
				wscript.echo "Commit Changes: Succeeded"
			end if
			on error goto 0
		else
			wscript.echo "No Changes"
		end if
		
	end If

 	userPath = ""
    rs.MoveNext
Loop

' Cleanup
rs.close
cn.close
' *************************************************
' * End of script
' *************************************************

' *************************************************
' * Functions
' *************************************************
' Reads specified attribute and sets the value for the oldVal variable
Sub readAttribute(ByVal strAttribute)
	Select Case LCASE(strAttribute) 
		Case "manager_samaccountname" 
			' special handling to allow update of manager attribute using sAMAccountName (UserName)
			' instead of using the distinguished name
			Dim objManager, managerDN
			' Ignore error if manager is null
			On Error Resume Next
			managerDN = objUser.Get("manager")
			On Error GoTo 0
			If managerDN = "" Then
				oldVal=""
			Else
				Set objManager = GetObject("LDAP://" & managerDN)
				oldVal = objManager.sAMAccountName
				Set objManager=Nothing
			End If
		Case "terminalservicesprofilepath"
			'Special handling for "TerminalServicesProfilePath" attribute
			oldVal=objUser.TerminalServicesProfilePath
		Case "terminalserviceshomedirectory"
			'Special handling for "TerminalServicesHomeDirectory" attribute
			oldVal = objUser.TerminalServicesHomeDirectory
		Case "terminalserviceshomedrive"
			'Special handling for "TerminalServicesHomeDrive" attribute
			oldVal=objUser.TerminalServicesHomeDrive
		Case "allowlogon"
			' Special handling for "allowlogon" (Terminal Services) attribute
			' e.g. 1=Allow, 0=Deny
			oldVal=objUser.AllowLogon
		Case "password"
			' Password can't be read, just return ****
			oldVal="****"
		Case Else
			on error resume next ' Ignore error if value is null
			' Get old attribute value
			oldVal = objUser.Get(strAttribute)
			On Error goto 0
	End Select
End Sub
' updates the specified attribute
Sub writeAttribute(ByVal strAttribute,newVal)
	Select Case LCASE(strAttribute)
		Case "cn" 'Special handling required for common-name attribute
			DIM objContainer
			set objContainer = GetObject(objUser.Parent)

			on error resume Next
			objContainer.MoveHere objUser.ADsPath,"cn=" & newVal

			' The update might fail if a user with the same common-name exists within
				' the same container (OU)
			if err.number <> 0 Then
				wscript.echo "Error changing common-name from '" & oldval & "' to '" & newval & _
						     "'.  Check that the common-name is unique within the container (OU)"
				err.clear
			End If
			on Error goto 0
		Case "terminalservicesprofilepath"
			'Special handling for "TerminalServicesProfilePath" attribute
			objUser.TerminalServicesProfilePath=newVal
		Case "terminalserviceshomedirectory"
			'Special handling for "TerminalServicesHomeDirectory" attribute
			objUser.TerminalServicesHomeDirectory=newVal
		Case "terminalserviceshomedrive"
			'Special handling for "TerminalServicesHomeDrive" attribute
			objUser.TerminalServicesHomeDrive=newVal
		Case "allowlogon" 
			' Special handling for "allowlogon" (Terminal Services) attribute
			' e.g. 1=Allow, 0=Deny
			objUser.AllowLogon=newVal
		Case "password"
			' Special handling for setting password
			objUser.SetPassword newVal
		Case "manager_samaccountname" 
			' special handling to allow update of manager attribute using sAMAccountName (UserName)
			' instead of using the distinguished name
			If newVal = "" Then
				objUser.PutEx ADS_PROPERTY_CLEAR, "manager", Null
			Else
				Dim objManager, managerPath, managerDN
				managerPath = GetUser("sAMAccountName",newVal)
				If LEFT(managerPath,6) = "Error:" THEN
					wscript.echo "Error resolving manager DN:" & managerPath
				Else
					SET objManager = GetObject(managerPath)
					managerDN = objManager.Get("distinguishedName")
					Set objManager = Nothing
					objUser.Put "manager",managerDN
				End If
			End If	
		Case ELSE ' Any other attribute
			' code to update "normal" attribute
			If newVal = "" then
				' Special handling to clear an attribute
				objUser.PutEx ADS_PROPERTY_CLEAR, strAttribute, Null
			Else
				objUser.put strAttribute,newVal
			End If
	End Select
End Sub

' Function to return the ADsPath of a user account by searching
' for a particular attribute value
' e.g. LDAP://cn=user1,cn=users,dc=wisesoft,dc=co,dc=uk
Function getUser(Byval strSearchAttribute,strSearchValue)
	DIM objRoot
	DIM getUserCn,getUserCmd,getUserRS

	on error resume next
	set objRoot = getobject("LDAP://RootDSE")

	set getUserCn = createobject("ADODB.Connection")
	set getUserCmd = createobject("ADODB.Command")
	set getUserRS = createobject("ADODB.Recordset")

	getUserCn.open "Provider=ADsDSOObject;"
	
	getUserCmd.activeconnection=getUserCn
	getUserCmd.commandtext="<LDAP://" & objRoot.get("defaultNamingContext") & ">;" & _
			"(&(objectCategory=person)(objectClass=user)(" & strSearchAttribute & "=" & strSearchValue & "));" & _
			"adsPath;subtree"
	
	set getUserRs = getUserCmd.execute

	if getUserRS.recordcount = 0 then
		getUser = "Error: User account not found"
	elseif getUserRS.recordcount = 1 then
     	getUser = getUserRs(0)
	else
		getUser = "Error: Multiple user accounts found.  Expected one user account."
	end if
	
	getUserCn.close
end function

 


Got a useful script? Click here to upload!


 

  Post Comment
Order By:  
User Comments
      
broonie
Posted On: 2/27/2008 3:17:37 AM

I realise mail isn't a mandatory attribute but do you have any plans to do sometihng similar using mail as the unique identifier rather then SAMaccountname. The .csv I have, which has been has been exported from a telephone directory DB, doesn't contain samAccountname but is does contain everyones email address.

broonie
Posted On: 2/27/2008 4:31:52 AM

I think I've been a bit daft here. Am I right in saying that all I have to do to use "mail" as my unique identifier is change the getUser function so that getUserCmd.commandtext searches based on the attribute "mail" instead of "sAMAccountName".




David Wiseman (Administrator)
United Kingdom
Posted On: 2/27/2008 4:09:26 PM

Hi broonie,

I know you emailed to say that you got the script working. I thought I'd post back here to inform everyone that I've modified the script code to make it easy for users to choose the "search" attribute.

Simply change the strSearchAttribute as required (to 'mail' in your case). It's probably quite a common requirement so hopefully other people will benefit from the change.

Grimloque
Posted On: 3/9/2008 3:26:00 PM

Hello,

First off, thanks a bunch for this script it is quite handy.

I'm having trouble updating the attribute "physicalDeliveryOfficeName". It just gets skipped during the update process.

I think my CSV is good, if I substitute in another field like "company" it works like a champ.

Regards,

Grimloque

David Wiseman (Administrator)
United Kingdom
Posted On: 3/9/2008 3:56:49 PM [Edited 3/9/2008 3:58:22 PM]

Hi Grimloque,

It appears to work fine for me. The script will check to see if the value is different from the current value before performing an update. Please send me a copy of your csv file and a copy of the script (if modified). I'll take a look at it when I get chance.

sasa.boskovic
Posted On: 3/19/2008 3:29:10 AM

Can you also make script to generate usermod.csv file with existing users from your AD?

JamesT
Posted On: 4/1/2008 8:05:09 AM

Hello, I am trying to get this script working for an ADAM directory but I am getting a User Cannot be Found error Messages. The Connection to the csv file works just fine just the connection to the ADAM directory seems to have a problem. I tried replacing the "LDAP://RootDSE" with "LDAP://localhost:389/dc=company,dc=com" but had no success at all. Any ideas on getting this script working on ADAM will be appreciated. Regards

JamesT
Posted On: 4/1/2008 8:05:12 AM

Hello, I am trying to get this script working for an ADAM directory but I am getting a User Cannot be Found error Messages. The Connection to the csv file works just fine just the connection to the ADAM directory seems to have a problem. I tried replacing the "LDAP://RootDSE" with "LDAP://localhost:389/dc=company,dc=com" but had no success at all. Any ideas on getting this script working on ADAM will be appreciated. Regards

djayam
Posted On: 4/16/2008 12:03:38 PM [Edited 4/16/2008 12:07:33 PM]

Dave - this script is just brilliant. Thank you! I have just successfully updated 2000 user accounts in 5 minutes thanks to this.

The simple and detailed instructions mean someone like me who has had virtually no dealings with VB can work it.

Thanks again mate - much appreciated.

David Wiseman (Administrator)
United Kingdom
Posted On: 4/16/2008 12:59:33 PM

Hi JamesT,

I replied earlier via email but thought I'd post my comments here as it might help someone else.

I’ve never actually used ADAM before, but you might want to try replacing this line:

getUserCmd.commandtext="<LDAP://" & objRoot.get("defaultNamingContext") & ">;" & _
"(&(objectCategory=user)(" & strSearchAttribute & "=" & strSearchValue & "));" & _
"adsPath;subtree"

With this one:

getUserCmd.commandtext="<LDAP://localhost:389/dc=company,dc=com >;" & _
"(&(objectCategory=user)(" & strSearchAttribute & "=" & strSearchValue & "));" & _
"adsPath;subtree"

The following two lines can be removed if required:
DIM objRoot
set objRoot = getobject("LDAP://RootDSE")

I’m only binding to the objRootDSE as an alternative to hard coding the default naming context (e.g. dc=wisesoft,dc=co,d=uk).

Hope this helps,

David

David Wiseman (Administrator)
United Kingdom
Posted On: 4/18/2008 2:04:48 PM

Hi sasa,

You can use the following script to generate a csv file:

http://www.wisesoft.co.uk/Scripts/display_script.aspx?id=376

Grimloque
Posted On: 5/5/2008 2:12:10 PM

Hello,

One of the attributes that I'm updating is title and I've found that some of my users have job titles that include commas. Is it possible to change the field delimiter from being the comma to say using the | character?

Thanks again,

Grimloque

David Wiseman (Administrator)
United Kingdom
Posted On: 5/5/2008 3:07:34 PM

Hi Grimloque,

You can use the double-quotes text qualifier to include commas in your CSV file. If you have Excel, just get excel to save the CSV file with the commas in.

e.g.

"Wiseman, David"

It's also possible to use a custom delimiter - you just need to change the connection string.

(Changing the connection string can also allow you to update from other data sources - SQL database etc)

Hope this helps.

David Wiseman (Administrator)
United Kingdom
Posted On: 5/20/2008 2:31:05 AM

The latest version of Password Control now has a CSV Update feature. There is also an option to edit user account details in Microsoft Excel.


quadris
Posted On: 5/26/2008 1:03:47 PM

hi, i tried to update the manager field, but i need the full LDAP path..how can i include that in this script? br

12345