Find and replace multiple Linux user attribute values at once
Top  Previous  Next


By now you probably know how to replace a single value in the Linux user directory; first use Data.Get.LD.Find to extract account names and the attribute of interest, then loop through all extracted values and search for values to replace with Lin.DS.Op.SetProperty. Pseudo code:

Data.Get.LD.Find Path§ColX§Attribute§ColY
Data.Loop
 If %colY%=OldValue Then
  Lin.DS.Op.Init Modify, %ColX%   
  Lin.DS.Op.SetProperty Attribute, %NewValue%   
  Lin.DS.Op.Commit
 EndIf
Data.EndLoop

But what if you need to replace multiple values at once? Well, you could use multiple if statements

Data.Get.LD.Find Path§ColX§Attribute§ColY
Data.Loop
 If %colY%=OldValue Then
  Lin.DS.Op.Init Modify, %ColX%   
  Lin.DS.Op.SetProperty Attribute, %NewValue%   
  Lin.DS.Op.Commit
 EndIf
 If
 %colY%=OldValue2 Then
  Lin.DS.Op.Init Modify, %ColX%   
  Lin.DS.Op.SetProperty Attribute, %NewValue%   
  Lin.DS.Op.Commit
 EndIf
 If
 %colY%=OldValue3 Then
  Lin.DS.Op.Init Modify, %ColX%   
  Lin.DS.Op.SetProperty Attribute, %NewValue%   
  Lin.DS.Op.Commit
 EndIf
Data.EndLoop

However, this is a rather clumpsy solution, and what if there are twenty or hundred of values to replace? Fortunately, there is a better way, have a look at this pseudo code:

Data.Get.LD.Find Path§ColX§Attribute§ColY
Data.Loop
 xData.Loop Replace
  If %colY%=%Replace1% Then
   Lin.DS.Op.Init Modify, %ColX%   
   Lin.DS.Op.SetProperty Attribute, %Replace2%   
   Lin.DS.Op.Commit
  EndIf
 xData.EndLoop
Data.EndLoop

[xData_Replace_Begin]
OldValue,NewValue
OldValue2,NewValue2
OldValue3,NewValue3
[xData_Replace_End]

By using an [xData] section and a corresponding loop, the code gets much more compact and you get a nice list of things to replace that easily can be extended.

We are now going to explore this technique in two complete scripts, the first one, script 1, includes a preview dialog that lets you to see the changes before they are carried out, the second one, script 2, uses wildcards to replace parts of text and a secondary [xData] section with a list of specific organizational units to manage accounts in.

Script 1 - replace five different zip codes throughout the entire directory

[Settings_Begin]
BatchSettings.Delimiter=;
BatchSettings.MarkerCol=4

// LDAP server, SSL must be enabled on port 636
SET LDServer=ldap.my-domain.com
SET LDAdminAccount=%LDDomainPath%/cn=ldapman
SET LDAdminPW=password

SET LDDomainPath=dc=com/dc=my-domain
[Settings_End]

[Batch_Begin]
Lin.DS.Logon %LDServer%, %LDAdminAccount%, %LDAdminPW%   
Data.Get.LD.Find %LDDomainPath%, (cn=*), yes, LDpath§1§postalCode§2§   
Data.Copy.xData 2§,3§,col2=replace1   
SET DataShowLabels=Account§Replace§With§0§   
Data.Show.Filter Replace zip code for listed accounts?,col3<>   
Data.Loop   
 If %col3%<> Then   
//  LogWindow.Write %col1% %col2% %col3%   
  Lin.DS.Op.Init Modify, %col1%   
  Lin.DS.Op.SetProperty postalCode, %col3%   
  Lin.DS.Op.Commit   
 EndIf   
Data.EndLoop   
[Batch_End]

[xData_Replace_Begin]
90509;90059
91342;90048
93306;90024
92422;90025
96322;90026
[xData_Replace_End]


About script 1


The [xData] section named "Replace" (xData sections can be named anyway you want) contains zip codes to replace in the first column and what to replace them with in the second column.

Data.Get.LD.Find fills the [Data] section with all accounts in the container (and all subcontainers if fourth parameter is "Yes") specified in the LDDomainPath variable, LDpath for each account is written to [Data] column 1 and postal (zip) code to column 2.

This is how the data section looks after
Data.Get.LD.Find:

[Data_Begin]
dc=com/dc=my-domain/ou=People/uid=klgr;92422
dc=com/dc=my-domain/ou=People/uid=shpo;92422
dc=com/dc=my-domain/ou=People/uid=ades;91342
dc=com/dc=my-domain/ou=People/uid=dolo;94400
.
.
[Data_End]


The line
"Data.Copy.xData 2§,3§,col2=replace1" copies data from [xData] to [Data] if there is a match between [xData] and [Data] - in this case - if the postal code in [xData_Replace] column 1 matches the existing postal code in [Data] column 2, then copy the new postal code from [xData_Replace] column 2 to [Data] column 3. If no match then [Data] column 3 is cleared.

This is how the data section looks after
Data.Copy.xData:

[Data_Begin]
dc=com/dc=my-domain/ou=People/uid=klgr;92422;90025
dc=com/dc=my-domain/ou=People/uid=shpo;92422;90025
dc=com/dc=my-domain/ou=People/uid=ades;91342;90048
dc=com/dc=my-domain/ou=People/uid=dolo;94400
.
.
[Data_End]


Notice line four, "94400" does not exist in [xData_Replace] - the third column is empty.

SET DataShowLabels sets labels for the below Data.Show.Filter command.

Data.Show.Filter shows the preview dialog, see below screen shot. Notice "col3<>", it means that rows are shown only if the third column in the [Data] section is not empty - only accounts that will be modified are displayed.



Click abort to stop the script or continue to make modifications.

The commands inside
Data.Loop..Data.EndLoop are executed once for each row in the [Data] section, %col1% refers to column 1 on current row, %col2% to column 2 on current row etc.

Lin.DS.Op.Init + Lin.DS.Op.SetProperty + Lin.DS.Op.Commit modifies the account.

Tip: remove the comment "//" before
LogWindow.Write and put it in front of the Lin.DS. commands to write to the log window instead of making actual changes.

Script 2 - Use wildcards to replace five different building numbers for accounts in three specific organizational units

This time things gets a bit more complicated since we are not going to replace the entire attribute value, only a part of it. The attribute in question looks like this: "internal mail code, building, floor', example: "5033, Bldg 34, 8th Flr", we will replace the building part "Bldg XX" with "Bldg YY". Also, we do not want to search through all objects in the entire directory, just the ones in "OU1", "OU2" and "OU3".

Script:

[Settings_Begin]
BatchSettings.CommandDelimiter=;
BatchSettings.Delimiter=;
BatchSettings.MarkerCol=5

// LDAP server, SSL must be enabled on port 636
SET LDServer=ldap.my-domain.com
SET LDAdminAccount=%LDDomainPath%/cn=ldapman
SET LDAdminPW=password

SET LDDomainPath=dc=com/dc=my-domain
[Settings_End]

[Batch_Begin]
Lin.DS.Logon %LDServer%; %LDAdminAccount%; %LDAdminPW%   
Data.Clear   
xData.Loop Containers   
 Data.Get.LD.Find %Containers1%; (cn=*); yes; LDpath§1§OU§2§;add   
xData.EndLoop   
Data.Connect.xData 3; col2=*replace1*   
   
Data.Loop   
 If %col3%<>0 Then   
  Data.Write 4; CopyWord(%col2%;,;1;0), xDataValue(replace;2;%col3%),CopyWord (%col2%;,;3;0)   
 EndIf   
Data.EndLoop   
   
SET DataShowLabels=Account§Replace§0§With§0§   
Data.Show.Filter Replace building number for listed accounts?;col4<>   
   
Data.Loop   
 If %col4%<> Then   
//  LogWindow.Write %col1% %col2% (%col3%) >> %col4%   
  Lin.DS.Op.Init Modify; %col1%   
  Lin.DS.Op.SetProperty ou; %col4%   
  Lin.DS.Op.Commit   
 EndIf   
Data.EndLoop   
[Batch_End]

[xData_Containers_Begin]
%LDDomainPath%/ou=OU1;;;;
%LDDomainPath%/ou=OU2;;;;
%LDDomainPath%/ou=OU5;;;;
[xData_Containers_End]

[xData_Replace_Begin]
Bldg 22;Bldg 62
Bldg 23;Bldg 63
Bldg 24;Bldg 64
Bldg 25;Bldg 65
Bldg 30;Bldg 15
[xData_Replace_End]


About script 2

(See above "about script 1" for basic details about
Data.Get.LD.Find, Data.Show.Filter, Data.Loop..Data.EndLoop and Lin.DS.Op.Init + Lin.DS.Op.SetProperty + Lin.DS.Op.Commit.)

The first [xData] section named "Containers" contains a list of the containers from which to extract accounts.

In this script the Data.Get.LD.Find command is placed inside an xData.Loop..xData.EndLoop loop; Data.Get.LD.Find is executed once for each row in the [xData] section "Containers", the %Containers1% variable contains the value of the first column on current row in the [xData] section.

Notice the last parameter for Data.Get.LD.Find,"add" , without it Data.Get.LD.Find replaces everything in the [Data] section, with it, data is added at the end.

Data.Clear is used to remove old data from the [Data] section in case the script is executed more than once.

This is how the data section looks after
Data.Get.LD.Find:

[Data_Begin]
dc=com/dc=my-domain/ou=OU1/uid=fore;5049, Bldg 25, 4th Flr
dc=com/dc=my-domain/ou=OU1/uid=euli;5048, Bldg 24, 1st Flr
dc=com/dc=my-domain/ou=OU1/uid=lego;5030, Bldg 30, 4th Flr
dc=com/dc=my-domain/ou=OU1/uid=mado;5049, Bldg 25, 4th Flr
dc=com/dc=my-domain/ou=OU1/uid=raba;5035, Bldg 36, 8th Flr
dc=com/dc=my-domain/ou=OU1/uid=jual;5033, Bldg 34, 7th Flr
.
.
[Data_End]


The second [xData] section named "Replace" contains building numbers to replace in the first column and what to replace them with in the second.

Data.Connect.xData basically connects an [xData] section with the [Data] section by a row number (much like "pointers" if you are familiar with C programming or foreign keys if you are into databases). Remember Data.Copy.xData in the previous script? Data.Connect.xData works almost the same way, but instead of copying actual data from [xData] to [Data], the row number of the [xData] section row that matches the criteria is written to [Data]. This is useful in two cases (1) if there are many items on each row in [xData] - leave them there and use the referring row number in [Data] to access them when needed, or in this case, (2) when data is to be manipulated in any way before copied to [Data].

OK let's get back to the actual script.

Data.Connect.xData 3; col2=*replace1* 
connects [xData] to [Data] if there is a match between [xData] and [Data] - in this case - if the building number in [xData_Replace] column 1 matches the existing building number in [Data] column 2, then write the row number of the matching [xData_Replace] row in [Data] column 3. If no match write 0. Notice the wildcards "*" before and after "replace1", they mean that it does not matter what is before and after "Bldg XX".

This is how the data section looks after
Data.Connect.xData:

[Data_Begin]
dc=com/dc=my-domain/ou=OU1/uid=fore;5049, Bldg 25, 4th Flr;4
dc=com/dc=my-domain/ou=OU1/uid=euli;5048, Bldg 24, 1st Flr;3
dc=com/dc=my-domain/ou=OU1/uid=lego;5030, Bldg 30, 4th Flr;5
dc=com/dc=my-domain/ou=OU1/uid=mado;5049, Bldg 25, 4th Flr;4
dc=com/dc=my-domain/ou=OU1/uid=raba;5035, Bldg 36, 8th Flr;0
dc=com/dc=my-domain/ou=OU1/uid=jual;5033, Bldg 34, 7th Flr;0
.
.
[Data_End]


The third (rightmost) column in the [Data] section now contains pointers to the [xData_Replace] section. For example, have a look at the third column of the third row in the [Data] section, it reads "5". Now have a look at the fifth row in the [xData_Replace] section, it reads "Bldg 30;Bldg 15" - "Bldg 30" was found in the [Data] section and will soon be replaced by "Bldg 15".

The if-statement and the Data.Write command inside the Data.Loop..Data.EndLoop structure replaces the building number part of the string. Example: "5030, Bldg 30, 4th Flr", replace "Bldg 30" with "Bldg 15" -
CopyWord(%col2%;,;1;0) reads the first "5030" part from [Data] section column 2, then xDataValue(replace;2;%col3%) reads the replacement value "Bldg 15" from the row in the [xData] section that the second column in [Data] refers to and finally the last CopyWord (%col2%;,;3;0) reads third word "4th Flr" from [Data].

This is how the data section looks after the if-statement.


[Data_Begin]
dc=com/dc=my-domain/ou=OU1/uid=fore;5049, Bldg 25, 4th Flr;4;5049, Bldg 65, 4th Flr
dc=com/dc=my-domain/ou=OU1/uid=euli;5048, Bldg 24, 1st Flr;3;5048, Bldg 64, 1st Flr
dc=com/dc=my-domain/ou=OU1/uid=lego;5030, Bldg 30, 4th Flr;5;5030, Bldg 15, 4th Flr
dc=com/dc=my-domain/ou=OU1/uid=mado;5049, Bldg 25, 4th Flr;4;5049, Bldg 65, 4th Flr
dc=com/dc=my-domain/ou=OU1/uid=raba;5035, Bldg 36, 8th Flr;0;;
dc=com/dc=my-domain/ou=OU1/uid=jual;5033, Bldg 34, 7th Flr;0;;
.
.
[Data_End]


The last part of the script is identical to the last part of the first script.