23 August 2016

PowerShell: Import Active Directory Extension Attributes

Recently, my firm has started using the AD extension attributes for custom data. I wrote a script a while back that read the data from a .CSV and imported it into a specific ExtensionAttribute. This came up again and it was brought to my attention that we may start using more of the fields. In order to automate this so that I don't have to modify the script each time, I decided to write one that gets all of its information from the .CSV file. With the help of Sapien's PowerShell Studio, this script was easy to write and easy to document.

This script will import a populated .CSV file that resides in the same directory as the script. The first line in the .CSV file contains the headers. The script reads that line to know what fields to populate in AD. You enter the exact extensionAttribute name in the header field and all data beneath it will be populated in that field for each user. Here is a screenshot of a sample .CSV file.


The first line is what tells it which extension attribute to populate the data to. It can have as many attribute lines as you need. I also added the features to remove data from an extension attribute. If a cell is left blank or CLEAR is input into the cell, the extension attribute will be cleared. If you do not want anything done to an attribute, input NO CLEAR into the cell and the script will not do anything to the attribute.

Logging has also been added to the script. If you want a log file written, then populate the -LogFile parameter. If this is left unpopulated, the script will not create a log file.

Another feature it -ProcessDelay. I added this as a precautionary feature. This puts a specified XX seconds delay between each record. This allows you to slowly implement the changes in AD without it all happening at once. Say you accidentally made an error in the .CSV file and discover it midway through. At least not all records got changed at that time. If you leave that field blank, the script will not pause between each record.

The final feature I added was for the script to go back and verify the change was actually made.

I do highly recommend that you test this script out on a test account first before using it against all of your user accounts. It works in my environment, but it is no guarantee that it will work in yours.

Here is a screenshot of the script in action.



You can download the script from here.




1:  <#  
2:       .SYNOPSIS  
3:            A brief description of the ImportADExtensions.ps1 file.  
4:         
5:       .DESCRIPTION  
6:            This script will import data from a CSV file to be written to the desired extension attributes in active directory  
7:         
8:       .PARAMETER DataFile  
9:            Name of the csv file that contains the data to import into active directory  
10:         
11:       .PARAMETER LogFile  
12:            Name of the log file to write the status of each change to  
13:         
14:       .PARAMETER ProcessDelay  
15:            This will pause the script for XX number of seconds before processing the next AD user. This is intended as a safety measure in the event that wrong data is being written to each AD profile. This allows for not all profile to be affected at once.  
16:         
17:       .EXAMPLE  
18:            Run with no logging and no delays between entry changes  
19:                 powershell.exe -executionpolicy bypass -file ImportADExtensions.ps1 -DataFile Data.csv  
20:    
21:            Run with logging and no delays between entry changes  
22:                 powershell.exe -executionpolicy bypass -file ImportADExtensions.ps1 -DataFile Data.csv -LogFile ADExtensions.log  
23:    
24:            Run with logging and 10 second delay between entry changes  
25:                 powershell.exe -executionpolicy bypass -file ImportADExtensions.ps1 -DataFile Data.csv -LogFile ADExtensions.log -ProcessDelay 10  
26:    
27:            You can also pre-populate the parameters within the Param fields inside the script.  
28:    
29:       .NOTES  
30:            ===========================================================================  
31:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.126  
32:            Created on:       7/28/2016 8:55 AM  
33:            Created by:       Mick Pletcher  
34:            Organization:  
35:            Filename:         ImportADExtensions.ps1  
36:            ===========================================================================  
37:  #>  
38:  [CmdletBinding()]  
39:  param  
40:  (  
41:       [ValidateNotNullOrEmpty()][string]  
42:       $DataFile,  
43:       [string]  
44:       $LogFile,  
45:       [int]  
46:       $ProcessDelay  
47:  )  
48:    
49:  function Get-RelativePath {  
50:  <#  
51:       .SYNOPSIS  
52:            Get the relative path  
53:         
54:       .DESCRIPTION  
55:            Returns the location of the currently running PowerShell script  
56:         
57:       .NOTES  
58:            Additional information about the function.  
59:  #>  
60:         
61:       [CmdletBinding()][OutputType([string])]  
62:       param ()  
63:         
64:       $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
65:       Return $Path  
66:  }  
67:    
68:  function Import-DataFile {  
69:  <#  
70:       .SYNOPSIS  
71:            Import data file  
72:         
73:       .DESCRIPTION  
74:            Import the data from a csv file  
75:         
76:       .EXAMPLE  
77:            PS C:\> Import-DataFile  
78:         
79:       .NOTES  
80:            Additional information about the function.  
81:  #>  
82:         
83:       [CmdletBinding()][OutputType([object])]  
84:       param ()  
85:         
86:       #Get the path this script is being executed from  
87:       $RelativePath = Get-RelativePath  
88:       #Associate the relative path with the data file to be imported  
89:       $File = $RelativePath + $DataFile  
90:       #Read the data file to a variable  
91:       $FileData = Get-Content -Path $File -Force  
92:       #Get the attribute fields  
93:       $Fields = ($FileData[0]).Split(",")  
94:       $ImportedRecords = @()  
95:       foreach ($Record in $FileData) {  
96:            If ($Record -notlike "*extensionattribute*") {  
97:                 $SplitRecord = $Record.Split(",")  
98:                 $objRecord = New-Object System.Management.Automation.PSObject  
99:                 for ($i = 0; $i -lt $Fields.Length; $i++) {  
100:                      $objRecord | Add-Member -type NoteProperty -Name $Fields[$i] -Value $SplitRecord[$i]  
101:                 }  
102:                 $ImportedRecords += $objRecord  
103:            }  
104:       }  
105:       Return $ImportedRecords  
106:  }  
107:    
108:  function New-Logfile {  
109:  <#  
110:       .SYNOPSIS  
111:            Create a new log file  
112:         
113:       .DESCRIPTION  
114:            This will create a new log file. If an old one exists, it will delete it.  
115:         
116:       .EXAMPLE  
117:                      PS C:\> New-Logfile  
118:         
119:       .NOTES  
120:            Additional information about the function.  
121:  #>  
122:         
123:       [CmdletBinding()]  
124:       param ()  
125:         
126:       $RelativePath = Get-RelativePath  
127:       $Logs = $RelativePath + $LogFile  
128:       If ((Test-Path $Logs) -eq $true) {  
129:            $Output = "Deleting old log file....."  
130:            Remove-Item -Path $Logs -Force | Out-Null  
131:            If ((Test-Path $Logs) -eq $false) {  
132:                 $Output += "Success" + "`n"  
133:            } else {  
134:                 $Output += "Failed" + "`n"  
135:            }  
136:       }  
137:       If (($LogFile -ne "") -and ($LogFile -ne $null)) {  
138:            $Output += "Creating new log file....."  
139:            New-Item -Path $Logs -ItemType File -Force | Out-Null  
140:            If ((Test-Path $Logs) -eq $true) {  
141:                 $Output += "Success"  
142:            } else {  
143:                 $Output += "Failed"  
144:            }  
145:            Write-Output $Output  
146:       }  
147:  }  
148:    
149:  function Write-ExtensionAttributes {  
150:  <#  
151:       .SYNOPSIS  
152:            Write Extension Attributes to Active Directory  
153:         
154:       .DESCRIPTION  
155:            This script will write the extension attributes to active directory. It reads the name of the object field to associate with the correct extension attribute in AD.  
156:         
157:       .PARAMETER Records  
158:            List of imported objects  
159:         
160:       .EXAMPLE  
161:            PS C:\> Write-ExtensionAttributes -Records $value1  
162:         
163:       .NOTES  
164:            Additional information about the function.  
165:  #>  
166:         
167:       [CmdletBinding()]  
168:       param  
169:       (  
170:            [ValidateNotNullOrEmpty()][object]  
171:            $Records  
172:       )  
173:         
174:       #Get all member of $Records  
175:       $Fields = $Records | Get-Member  
176:       #Filter for just the extension attribute properties  
177:       $Fields = ($Fields | Where-Object { (($_.MemberType -eq "NoteProperty") -and ($_.Name -like "*extensionattribute*")) }).name  
178:       for ($i = 0; $i -lt @($Records).Count; $i++) {  
179:            #Get all active directory properties for specified user  
180:            $User = Get-ADUser $Records[$i].Username -Properties *  
181:            $Output += "User " + ($i+1) + " of " + @($Records).Count + "`n"  
182:            $Output += "Username: " + $Records[$i].Username + "`n"  
183:            foreach ($Field in $Fields) {  
184:                 $Output += $Field + ": " + $Records[$i].$Field + "`n"  
185:                 If ((($Records[$i].$Field -eq "Clear") -or ($Records[$i].$Field -eq "") -or ($Records[$i].$Field -eq $null)) -and ($Records[$i].$Field -ne "NO CLEAR")) {  
186:                      $Output += "Clearing " + $Field + "....."  
187:                      Set-ADUser -Identity $Records[$i].Username -Clear $Field  
188:                      #Get the field that was change from active directory  
189:                      $Test = Get-ADUser $Records[$i].Username -Properties * | select $Field  
190:                      #Test if the data in the AD field matches the data from the imported file  
191:                      if ($Test.$Field -eq $null) {  
192:                           $Output += "Success" + "`n"  
193:                      } else {  
194:                           $Output += "Failed" + "`n"  
195:                      }  
196:                 } elseif ($Records[$i].$Field -ne "NO CLEAR") {  
197:                      $User.$Field = $Records[$i].$Field  
198:                      $Output += "Setting " + $Field + "....."  
199:                      #Write change to active directory  
200:                      Set-ADUser -Instance $User  
201:                      #Get the field that was change from active directory  
202:                      $Test = Get-ADUser $Records[$i].Username -Properties * | select $Field  
203:                      #Test if the data in the AD field matches the data from the imported file  
204:                      if ($Test.$Field -eq $Records[$i].$Field) {  
205:                           $Output += "Success" + "`n"  
206:                      } else {  
207:                           $Output += "Failed" + "`n"  
208:                      }  
209:                 }  
210:            }  
211:            Write-Output $Output  
212:            #If the Logfile parameter is populated, then write the output to a logfile  
213:            If (($LogFile -ne "") -and ($LogFile -ne $null)) {  
214:                 #Get the path where this script is being executed from  
215:                 $RelativePath = Get-RelativePath  
216:                 #Define the log file path  
217:                 $Logs = $RelativePath + $LogFile  
218:                 #Write the output to the log file  
219:                 Add-Content -Value $Output -Path $Logs -Encoding UTF8 -Force  
220:            }  
221:            $Output = $null  
222:            If (($ProcessDelay -ne $null) -and ($ProcessDelay -ne "")) {  
223:                 Start-Sleep -Seconds $ProcessDelay  
224:            }  
225:            cls  
226:       }  
227:  }  
228:    
229:  Import-Module -Name ActiveDirectory  
230:  #Delete old log file and create a new one  
231:  New-Logfile  
232:  #Import all records from the csv file  
233:  $Records = Import-DataFile  
234:  #Apply changes to active directory  
235:  Write-ExtensionAttributes -Records $Records  
236:    

0 comments:

Post a Comment