📜 ⬆️ ⬇️

Update Active Directory Credentials

Many people remember the feeling when a company expands to the size when there are not enough working groups, and the first Active Directory domain rises: “Oh, now everything will be all right!” But no, the domain is growing more and more, new accounts are being created, old ones are being blocked computers are added, girls get married, names are changed, and in the end, the directory service database looks like a full seam. In this topic, we will establish a connection between the Active Directory database and the personnel system of the enterprise, as well as create a mechanism for maintaining employee data in AD up to date.

First of all, we will describe the requirements that we must present to the employee records. And we will try to estimate these requirements based on the user's needs. It is no secret that many corporate systems that use authentication through Active Directory often display various fields of AD accounts in their admin panels and just during the work: this is Sharepoint, Citrix, and many, many others. As an example of such a system, I will take the well-known MS Outlook, but not completely, but only its address book, which derives its data directly from Active Directory.



What does the user use? In our organization, he often searches by phone for his name, email address, and the name of the department. Of course, it is not bad to fill in the address information too, but due to the fact that we have a topic about a bundle with an abstract personnel system, we will leave addresses and phone numbers outside the brackets.
First, we write out the list of fields that we wish to take from the personnel system; we will have it:
At this stage it is useful to consolidate our list of fields and issue an Order in the name of the Biggest Director, obliging HR officers and administrators to keep this data up to date.
')

Mechanism


It goes without saying that in order to link a person from the personnel system and a person from Active Directory, it is necessary to have some identifier linking these two records. Usually, such an identifier is the employee's employee number, it is assigned when applying for a job and never changes, however, I have encountered situations where the personnel number is not static, in which case such an identifier should be invented.

Information about an Active Directory user is not limited to information that can be seen in the Active Directory Users and Computers snap-in (well-established abbreviation ADUC), and is far from being exhausted. In fact, a user object has a trillion attributes, and these attributes can even be added by the schema administrator. For example, there is an attribute such as carLicense , which contains information about a driver's license, or drink , which characterizes the user's favorite drink. In general, Microsoft in this sense has provided a lot.

In my example, I will use the employeeID attributes to store the user ID, and flags , for which I will inform you later.

We will also use attributes to fill in user fields:
Pedants can, of course, additionally use givenName, initials, and sn to store the first name, initials, and last name, respectively, but I think that this is subtlety.

So, our application will work like this:
  1. List accounts with employeeID
  2. Search in the personnel system for each account the changed data
  3. Update data in Active Directory
  4. Log changes to the file

To business


The first thing to do is to put down the employeeID, which our personnel number represents, to all users. If there are few users, then it is easiest to do this through ADSI Edit , if there are a few more, then you can fasten a script for prescription, for example like this . And if there are a lot of users, the arrangement of identifiers must be delegated, I want a pleasant interface and additional baubles are used, then you can create this additional tab in ADUC:



However, the creation of such a tab is in itself a topic for a separate topic.

The second subtle point is that sometimes it happens that for some people only certain attributes should be changed. For example, we have an employee, let's call him Kudrymunbekov Sadruddin Fatkhullarovich, but everyone calls him simply San Sanych. And there is a general director, whose position in the personnel is written not only as the General Director of the Open Joint-Stock Company of Far Space Communication Horn and Hoof, which in AD it would be better to just leave exactly with the connection, but without horns and hoofs. Thus, we see the need to lay out some exceptions to the logic of our application, and we will store these exceptions in the Active Directory attribute in the flags attribute. This attribute has a value of four bytes, which means that by setting one or another bit to one or another value, we can, if necessary, remember as many as 32 exceptions. However, we will still use only six.

We proceed to the implementation on powershell:
#       Active Directory #   param($strServer, $strContainer, $strUserName, $strPassword, $strFileName, $strLogName) function Write-LogFile([string]$logFileName) { Process { $_ $dt = Get-Date $str = $dt.DateTime + " " + $_ $str | Out-File -FilePath $logFileName -Append } } #      ,    #      .       1, #    ,      Oracle e-Buisness suite, #        csv-. #  ,     Import-CSV , #     ,  - ,     function Get-Employee($employeeID, $fileName, [ref]$title, [ref]$department, [ref]$displayName, [ref]$company, [ref]$postalCode, [ref]$employeeType) { $records = $fileName | Import-CSV -Delimiter ";" $employee = $records | where-object {$_.EmployeeID -eq $employeeID} if ($employee -eq $null) {return $false} $title.Value = [string]$employee.Title $department.Value = [string]$employee.Department $displayName.Value = [string]$employee.Name $company.Value = [string]$employee.Company $postalCode.Value = [string]$employee.PostalCode $employeeType.Value = [string]$employee.EmployeeType return $true } #     "---" | Write-LogFile $strLogName "  :" | Write-LogFile $strLogName ": " + $strServer | Write-LogFile $strLogName ": " + $strContainer | Write-LogFile $strLogName " : " + $strUserName | Write-LogFile $strLogName ": " + $strPassword | Write-LogFile $strLogName " : " +$strFileName | Write-LogFile $strLogName "  : " + $strLogName | Write-LogFile $strLogName #   ,           #  ,     000001, 000010, 000100, 001000, 010000  100000 #   .  ,     , #     New-Variable -Option constant -Name C_COMPANY_FLAG -Value 1 New-Variable -Option constant -Name C_POSTALCODE_FLAG -Value 2 New-Variable -Option constant -Name C_TITLE_FLAG -Value 4 New-Variable -Option constant -Name C_DEPARTMENT_FLAG -Value 8 New-Variable -Option constant -Name C_NAME_FLAG -Value 16 New-Variable -Option constant -Name C_EMPLOYEETYPE_FLAG -Value 32 #     title.  title   # http://msdn.microsoft.com/en-us/library/windows/desktop/ms680037(v=VS.85).aspx #  64     Windows Server 2003 #  128     Windows Server 2008 #     ,   New-Variable -Option constant -Name C_PARAMETERS_LENGTH -Value 64 # (!userAccountControl:1.2.840.113556.1.4.803:=2)   "     " $strFilter = "(&(objectClass=user)(!objectClass=computer)(employeeID=*)(!userAccountControl:1.2.840.113556.1.4.803:=2))" # , ,    Active Directory  Windows Server 2008 # http://blogs.msdn.com/adpowershell #        Windows Server 2003  Windows XP, #     $objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$strServer+"/"+$strContainer) $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objSearcher.SearchRoot = $objDomain $objSearcher.PageSize = 1000 $objSearcher.Filter = $strFilter $objSearcher.SearchScope = "Subtree" $colProplist = "employeeID","postalCode","title","department", "displayName", "cn", "employeeType" foreach ($i in $colPropList) { $objSearcher.PropertiesToLoad.Add($i) } $colResults = $objSearcher.FindAll() #   colResults      $startTime = Get-Date $totalCount = $colResults.Count $i = 0 foreach ($objResult in $colResults) { $objItem = $objResult.Properties $aDEmployeeID = $objItem.employeeid #  ,     flags,        # ,      ,      $flagProtectCompany = $false $flagProtectPostalCode = $false $flagProtectTitle = $false $flagProtectDepartment = $false $flagProtectName = $false $flagProtectEmployeeType = $false if (!($objItem.flags -eq $null)) { $flags = $objItem.flags if (($flags[0] -band $C_COMPANY_FLAG) -ne 0) {$flagProtectCompany = $true} if (($flags[0] -band $C_POSTALCODE_FLAG) -ne 0) {$flagProtectPostalCode = $true} if (($flags[0] -band $C_TITLE_FLAG) -ne 0) {$flagProtectTitle = $true} if (($flags[0] -band $C_DEPARTMENT_FLAG) -ne 0) {$flagProtectDepartment = $true} if (($flags[0] -band $C_NAME_FLAG) -ne 0) {$flagProtectName = $true} if (($flags[0] -band $C_EMPLOYEETYPE_FLAG) -ne 0) {$flagProtectEmployeeType = $true} } #   ,      $cSVName = "" $cSVTitle = "" $cSVDepartment = "" $cSVCompany = "" $cSVPostalCode = "" $cSVEmployeeType = "" #         PowerShell,    # ,     $rc = Get-Employee $aDEmployeeID $strFileName ([ref]$cSVTitle) ([ref]$cSVDepartment) ([ref]$cSVName) ([ref]$cSVCompany) ([ref]$cSVPostalCode) ([ref]$cSVEmployeeType) if ($rc) { #            ,   #  .       , #    , ,       #    $objDirectoryEntry = new-object System.DirectoryServices.DirectoryEntry($objItem.adspath, $strUsername, $strPassword, [System.DirectoryServices.AuthenticationTypes]::Secure) $oTitle = $cSVTitle if ($oTitle.Length -gt $C_PARAMETERS_LENGTH) {$oTitle = $oTitle.Substring(0,$C_PARAMETERS_LENGTH)} $oDepartment = $cSVDepartment if ($oDepartment.Length -gt $C_PARAMETERS_LENGTH) {$oDepartment = $oDepartment.Substring(0,$C_PARAMETERS_LENGTH)} $newEmployeeType = $cSVEmployeeType #     ,    ,    ,  , #    (    ).       if (($newEmployeeType -ne $objItem.employeetype) -and -not $flagProtectEmployeeType) { " EmployeeType  """ + $objDirectoryEntry.name + """" | Write-LogFile $strLogName " """ + $objDirectoryEntry.employeetype + """  """ + $newEmployeeType + """" | Write-LogFile $strLogName $objDirectoryEntry.employeetype = [string]$newEmployeeType $objDirectoryEntry.CommitChanges() } if (($cSVCompany -ne $objItem.company) -and -not $flagProtectCompany) { "   """ + $objDirectoryEntry.name + """" | Write-LogFile $strLogName " """ + $objDirectoryEntry.company + """  """ + $cSVCompany + """" | Write-LogFile $strLogName $objDirectoryEntry.company = [string]$cSVCompany $objDirectoryEntry.CommitChanges() } if (($cSVPostalCode -ne $objItem.postalcode) -and -not $flagProtectPostalCode) { "   """ + $objDirectoryEntry.name + """" | Write-LogFile $strLogName " """ + $objDirectoryEntry.postalCode + """  """ + $cSVPostalCode + """" | Write-LogFile $strLogName $objDirectoryEntry.postalCode = $cSVPostalCode $objDirectoryEntry.CommitChanges() } if (($oTitle -ne $objItem.title) -and -not $flagProtectTitle) { "   """ + $objDirectoryEntry.name + """" | Write-LogFile $strLogName " """ + $objDirectoryEntry.title + """  """ + $cSVTitle + """" | Write-LogFile $strLogName if ($title.Length -gt $C_PARAMETERS_LENGTH) { $objDirectoryEntry.title = $cSVTitle.Substring(0,$C_PARAMETERS_LENGTH) } else { $objDirectoryEntry.title = $cSVTitle.ToString() } $objDirectoryEntry.CommitChanges() } if (($oDepartment -ne $objItem.department) -and -not $flagProtectDepartment) { "   """ + $objDirectoryEntry.name + """" | Write-LogFile $strLogName " """ + $objDirectoryEntry.department + """  """ + $cSVDepartment + """" | Write-LogFile $strLogName if ($department.Length -gt $C_PARAMETERS_LENGTH) { $objDirectoryEntry.department = $cSVDepartment.Substring(0,$C_PARAMETERS_LENGTH) } else { $objDirectoryEntry.department = $cSVDepartment.ToString() } $objDirectoryEntry.description = $cSVDepartment.ToString() $objDirectoryEntry.CommitChanges() } if ((($cSVName -ne $objItem.displayname) -or ($cSVName -ne $objItem.cn)) -and -not $flagProtectName) { "   """ + $objDirectoryEntry.name + """" | Write-LogFile $strLogName " """ + $objDirectoryEntry.displayname + """  """ + $cSVName + """" | Write-LogFile $strLogName $objDirectoryEntry.displayName = $cSVName $objDirectoryEntry.CommitChanges() $objDirectoryEntry.Rename("cn="+$cSVName) } $i++ #         ,    #  ,  , ,       $status = $i.ToString() + " of " + $totalCount.ToString() + " complete - " + $objDirectoryEntry.name $currentTime = Get-Date $diffTime = [int][System.Math]::Round(($currentTime - $startTime).Ticks / $i) $delta = $diffTime*$totalCount $endTime = $startTime.Add([int64]($delta)) $activityString = " .    " + $endTime Write-Progress -Activity $activityString -Status $status -PercentComplete (($i / $totalCount) * 100) } } " " | Write-LogFile $strLogName #      ,   Write-Host `a 


Let's create a test environment, absolutely arbitrarily assign names to accounts:



Now run the script with the parameters. Of course, it can be entered and executed on a schedule, just do not forget the account on whose behalf the task is being performed, assign the right to log in as a batch task :



As you can see, after the execution, we got good readable names, excellent positions and great company names:



Of course, by shallow script modification, we can fill the user with everything: from phones and addresses to notorious favorite drinks, there would be a source. And if the script is run with a certain regularity, then we ensure that all user data will be relevant.

upd Corrected a small error, moved the reset of flags in the loop

Source: https://habr.com/ru/post/132591/


All Articles