Last active
January 29, 2021 19:15
-
-
Save darrenjrobinson/71f382902a4c81ddceab687bb1b99422 to your computer and use it in GitHub Desktop.
FIM/MIM Oracle Internet Directory PowerShell Management Agent. Associated blogpost https://blog.darrenjrobinson.com/microsoft-identity-manager-powershell-management-agent-for-oracle-internet-directory/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param ( | |
$Username, | |
$Password, | |
$OperationType, | |
[bool] $usepagedimport, | |
$pagesize, | |
$Credentials | |
) | |
$DebugFilePath = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\OID\Debug\usersImport.txt" | |
# LDAP ChangeLog ChangeNumber Watermark so we can get just the changes since the last sync | |
$WaterMarkFilePath = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\OID\changeNumberWatermark.bin" | |
# Debug Logging File | |
if (!(Test-Path $DebugFilePath)) { | |
$DebugFile = New-Item -Path $DebugFilePath -ItemType File | |
} | |
else { | |
$DebugFile = Get-Item -Path $DebugFilePath | |
} | |
" " | Out-File $DebugFile -Append | |
"-------------------------------------------------------" | Out-File $DebugFile -Append | |
"Starting Import as : " + $OperationType + " - " + (Get-Date) | Out-File $DebugFile -Append | |
"Paged Import : " + $usepagedimport | Out-File $DebugFile -Append | |
"PageSize : " + $pagesize | Out-File $DebugFile -Append | |
# Needs reference to .NET assembly used in the script. | |
Add-Type -AssemblyName System.DirectoryServices.Protocols | |
<# | |
LDAP Functions | |
FORK of https://www.powershellgallery.com/packages/Ldap/0.1.0.17/Content/Ldap.psm1 | |
Avail from https://github.com/darrenjrobinson/LDAP | |
Addition of Timeout (seconds) for Get-LdapConnection | |
#> | |
function Get-LdapObject { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory)] | |
[ValidateNotNull()] | |
[System.DirectoryServices.Protocols.LdapConnection] $LdapConnection, | |
[Parameter(ParameterSetName = 'DistinguishedName', | |
Mandatory)] | |
[String] $Identity, | |
[Parameter(ParameterSetName = 'LdapFilter', | |
Mandatory)] | |
[Alias('Filter')] | |
[String] $LdapFilter, | |
[Parameter(ParameterSetName = 'LdapFilter', | |
Mandatory)] | |
[String] $SearchBase, | |
[Parameter(ParameterSetName = 'LdapFilter')] | |
[System.DirectoryServices.Protocols.SearchScope] $Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree, | |
[Parameter()] | |
[String[]] $Property, | |
[Parameter()] | |
[ValidateSet('String', 'ByteArray')] | |
[String] $AttributeFormat = 'String', | |
# Do not attempt to clean up the LDAP output - provide the output as-is | |
[Parameter()] | |
[Switch] $Raw | |
) | |
begin { | |
if ($AttributeFormat -eq 'String') { | |
$attrType = [string] | |
} | |
else { | |
$attrType = [byte[]] | |
} | |
} | |
process { | |
$request = New-Object -TypeName System.DirectoryServices.Protocols.SearchRequest | |
if ($PSCmdlet.ParameterSetName -eq 'DistinguishedName') { | |
$request.DistinguishedName = $Identity | |
} | |
else { | |
$request.Filter = $LdapFilter | |
$request.DistinguishedName = $SearchBase | |
} | |
if (-not $Property -or $Property -contains '*') { | |
# Write-Output "Get-LdapObject Returning all properties" | |
} | |
else { | |
foreach ($p in $Property) { | |
[void] $request.Attributes.Add($p) | |
} | |
} | |
# Write-Output "Get-LdapObject Sending LDAP request" | |
$response = $LdapConnection.SendRequest($request) | |
if (-not $response) { | |
# Write-Verbose "No response was returned from the LDAP server." | |
return | |
} | |
if ($response.ResultCode -eq 'Success') { | |
if ($Raw) { | |
Write-Output ($response.Entries) | |
} | |
else { | |
# Convert results to a PSCustomObject. | |
foreach ($e in $response.Entries) { | |
$hash = @{ | |
PSTypeName = 'LdapObject' | |
DistinguishedName = $e.DistinguishedName | |
} | |
# Attributes are returned as an instance of the class | |
# System.DirectoryServices.Protocols.DirectoryAttribute. | |
# Translate that to a more PowerShell-friendly format here. | |
foreach ($a in $e.Attributes.Keys | Sort-Object) { | |
# Write-Output "Get-LdapObject Adding type [$a]" | |
$hash[$a] = $e.Attributes[$a].GetValues($attrType) | Expand-Collection | |
} | |
Write-Output ([PSCustomObject] $hash) | |
} | |
return | |
} | |
} | |
Write-Output $response | |
} | |
} | |
function Expand-Collection { | |
# Simple helper function to expand a collection into a PowerShell array. | |
# The advantage to this is that if it's a collection with a single element, | |
# PowerShell will automatically parse that as a single entry. | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory, | |
Position = 0, | |
ValueFromPipeline, | |
ValueFromRemainingArguments)] | |
[ValidateNotNull()] | |
[Object[]] $InputObject | |
) | |
process { | |
foreach ($i in $InputObject) { | |
ForEach-Object -InputObject $i -Process { Write-Output $_ } | |
} | |
} | |
} | |
$server = "oid.customer.com.au" | |
$port = "8001" | |
$pwd = $Password | ConvertTo-SecureString -asPlainText -Force | |
# Check to see if we have our Groups of returned LDAP Objects to process. | |
if (!$Global:groups) { | |
# Get the changeNumber from the last sync | |
if (Test-Path $WaterMarkFilePath) { | |
[int]$val = Get-Content -Path $WaterMarkFilePath | |
# Search will be for next changeNumber | |
$changeNumber = $val + 1 | |
} | |
else { | |
$val = 0 | |
$changeNumber = $val + 1 | |
} | |
# Users OU for LDAP SearchBase | |
$ldapSearchBase = "cn=users,dc=customer,dc=com,dc=au" | |
# Get and update the Delta ChangeLog Changenumber | |
# Uses slightly different query than that for general users as ChangeLog is extremely perscriptive on format in Oracle Internet Directory | |
$changeResponse = $null | |
$changeCredentials = New-Object System.Net.NetworkCredential($username, $pwd) | |
$ldapConnection = New-Object System.DirectoryServices.Protocols.LDAPConnection("$($server):$($port)", $changeCredentials, "Basic") | |
$changeFilter = "(&(targetdn=*$($ldapSearchBase))(changeNumber>=$($changeNumber)))" | |
$changeTimeOut = new-timespan -Seconds 30 | |
$changeRequest = New-Object System.DirectoryServices.Protocols.SearchRequest("cn=changelog", $changeFilter, "OneLevel", $null) | |
$changeResponse = $ldapConnection.SendRequest($changeRequest, $changeTimeOut) | |
if ($changeResponse.Entries) { | |
Try { | |
$LastChangeNumber = [int]$ChangeResponse.Entries[($ChangeResponse.Entries.Count - 1)].Attributes["changeNumber"][0] | |
Set-Content -Value $LastChangeNumber -Path $WaterMarkFilePath | |
"New Changenumber: $($LastChangeNumber)" | Out-File $DebugFile -Append | |
} | |
Catch { | |
# Something went wrong or nothing has changed since last run. Reuse last changenumber | |
$LastChangeNumber = $changeNumber | |
"Changenumber unchanged: $($LastChangeNumber)" | Out-File $DebugFile -Append | |
} | |
} | |
If ($OperationType -eq "Full") { | |
"Start of Full Sync" | Out-File $DebugFile -Append | |
# LDAP Search filter for users of interest (FULL Import) | |
$ldapSearchFilter = "(&(objectClass=Person)(!orclisenabled=DISABLED))" | |
$ldapSearchBase | Out-File $DebugFile -Append | |
$ldapSearchFilter | Out-File $DebugFile -Append | |
# ALl Active Users | |
# Increase Timeout to allow the return of all users | |
$ldapConnection.Timeout = new-timespan -Seconds 1800 | |
$results = Get-LdapObject -LdapConnection $ldapConnection -LdapFilter $ldapSearchFilter -SearchBase $ldapSearchBase | |
"$($results.count) users retrieved" | Out-File $DebugFile -Append | |
$Global:groups = $results | Group-Object -Property { [math]::Floor($counter.Value++ / $pagesize) } | |
[int]$Global:groupsCount = $Global:groups.count | |
[int]$global:groupsProcessed = -1 | |
"$($Global:groupsCount) Groups to process" | Out-File $DebugFile -Append | |
$results = $null | |
} | |
else { | |
If ($OperationType -eq "Delta") { | |
"Start of Delta Sync" | Out-File $DebugFile -Append | |
# Delta import based on Change Log | |
if ($changeResponse.Entries) { | |
$uniqueUsers = $changeResponse.Entries | ForEach-Object { $_.attributes["targetdn"][0] } | Get-Unique | |
"$($uniqueUsers.count) users updated based on ChangeLog" | Out-File $DebugFile -Append | |
$uniqueUsers | Out-File $DebugFile -Append | |
$results = @() | |
ForEach ($dn in $uniqueUsers) { | |
$userArray = $null | |
$userCN = $null | |
$userArray = $dn.Split(",") | |
$userCN = $userArray[0] | |
$user = Get-LdapObject -LdapConnection $ldapConnection -LdapFilter "(&(objectclass=person)($($userCN)))" -SearchBase $ldapSearchBase -Scope OneLevel | |
if ($user) { | |
$results += $user | |
} | |
} | |
if ($results.count -gt 0) { | |
"$($results.count) users retrieved as notified via Changelog" | Out-File $DebugFile -Append | |
$Global:groups = $results | Group-Object -Property { [math]::Floor($counter.Value++ / $pagesize) } | |
[int]$Global:groupsCount = $Global:groups.count | |
[int]$global:groupsProcessed = -1 | |
"$($Global:groupsCount) Groups to process" | Out-File $DebugFile -Append | |
$results = $null | |
} | |
} | |
} | |
} | |
# Process the first batch | |
# Process Users in Batches based off Page Size | |
# Get the next batch | |
$global:ldapBatch = $Global:groups[$global:groupsProcessed + 1] | |
# if we are at the end then set MoreToImport to False and quit | |
if (!$global:ldapBatch -or ($global:groupsProcessed + 1 -eq $Global:groupsCount)) { | |
"End of Import as : " + $OperationType + " - " + (Get-Date) | Out-File $DebugFile -Append | |
$global:MoreToImport = $false | |
break | |
} | |
# Process the batch | |
$usersProcessed = 0 | |
foreach ($user in $global:ldapBatch.Group) { | |
$obj = @{ } | |
$obj.add("orclguid", $user.orclguid) | |
$obj.add("objectClass", "ldapUser") | |
$obj.add("DistinguishedName", $user.DistinguishedName) | |
$obj.add("company", $user.company) | |
$obj.add("middlename", $user.middlename) | |
$obj.add("preferredname", $user.preferredname) | |
$obj.add("departmentnumber", $user.departmentnumber) | |
$obj.add("l", $user.l) | |
$obj.add("paygroup", $user.paygroup) | |
$obj.add("displayname", $user.displayname) | |
$obj.add("street", $user.street) | |
$obj.add("status", $user.status) | |
$obj.add("finalprocessdate", $user.finalprocessdate) | |
$obj.add("createtimestamp", $user.createtimestamp) | |
$obj.add("assignmentstartdate", $user.assignmentstartdate) | |
$obj.add("st", $user.st) | |
$obj.add("cn", $user.cn) | |
$obj.add("creatorsname", $user.creatorsname) | |
$obj.add("modifiersname", $user.modifiersname) | |
$obj.add("city", $user.city) | |
if ($user.orclisenabled) { | |
switch ($user.orclisenabled) { | |
"DISABLED" { $obj.add("orclisenabled", $false) } | |
"ENABLED" { $obj.add("orclisenabled", $true ) } | |
default { $obj.add("orclisenabled", $true) } | |
} | |
} | |
else { | |
$obj.add("orclisenabled", $true) | |
} | |
if ($user.strongauthenticated) { | |
switch ($user.strongauthenticated) { | |
"N" { $obj.add("strongauthenticated", $false ) } | |
"Y" { $obj.add("strongauthenticated", $true) } | |
default { $obj.add("strongauthenticated", $false) } | |
} | |
} | |
else { | |
$obj.add("strongauthenticated", $false) | |
} | |
$obj.add("ou", $user.ou) | |
$obj.add("payroll", $user.payroll) | |
$obj.add("orclnormdn", $user.orclnormdn) | |
$obj.add("personaltitle", $user.personaltitle) | |
$obj.add("sn", $user.sn) | |
$obj.add("employeenumber", $user.employeenumber) | |
$obj.add("positionfunction", $user.positionfunction) | |
$obj.add("givenname", $user.givenname) | |
$obj.add("sbitype", $user.sbitype) | |
$obj.add("title", $user.title) | |
$obj.add("c", $user.c) | |
$obj.add("hiredate", $user.$hiredate) | |
$obj.add("employmenttype", $user.employmenttype) | |
$obj.add("orcldateofbirth", $user.orcldateofbirth) | |
$obj.add("pwdchangedtime", $user.pwdchangedtime) | |
$obj.add("companycode", $user.companycode) | |
$obj.add("positiontitle", $user.positiontitle) | |
$obj.add("modifytimestamp", $user.modifytimestamp) | |
$obj.add("postalcode", $user.postalcode) | |
$obj.add("appointmentdate", $user.appointmentdate) | |
# Pass to the MA | |
$obj | |
$usersProcessed++ | |
} | |
"Users Processed: $($usersProcessed)" | Out-File $DebugFile -Append | |
# More Groups to Process? Let the Sync Engine know | |
$global:groupsProcessed++ | |
if ($global:groupsProcessed -lt $Global:groupsCount) { | |
"Groups Processed: $($groupsProcessed + 1)" | Out-File $DebugFile -Append | |
$global:groupsProcessed + "Groups out of " + $Global:groupsCount + " Groups processed." | Out-File $DebugFile -Append | |
$global:MoreToImport = $true | |
} | |
} | |
else { | |
# Global:groups Variable exists | |
# Process Users in Batches based off Page Size | |
# Get the next batch | |
$global:ldapBatch = $Global:groups[$global:groupsProcessed + 1] | |
# if we are at the end then set MoreToImport to False and quit | |
if (!$global:ldapBatch -or ($global:groupsProcessed + 1 -eq $Global:groupsCount)) { | |
"End of Import as : " + $OperationType + " - " + (Get-Date) | Out-File $DebugFile -Append | |
$global:MoreToImport = $false | |
break | |
} | |
# Process the batch | |
$usersProcessed = 0 | |
foreach ($user in $global:ldapBatch.Group) { | |
$obj = @{ } | |
$obj.add("orclguid", $user.orclguid) | |
$obj.add("objectClass", "ldapUser") | |
$obj.add("DistinguishedName", $user.DistinguishedName) | |
$obj.add("company", $user.company) | |
$obj.add("middlename", $user.middlename) | |
$obj.add("preferredname", $user.preferredname) | |
$obj.add("departmentnumber", $user.departmentnumber) | |
$obj.add("l", $user.l) | |
$obj.add("paygroup", $user.paygroup) | |
$obj.add("displayname", $user.displayname) | |
$obj.add("street", $user.street) | |
$obj.add("status", $user.status) | |
$obj.add("finalprocessdate", $user.finalprocessdate) | |
$obj.add("createtimestamp", $user.createtimestamp) | |
$obj.add("assignmentstartdate", $user.assignmentstartdate) | |
$obj.add("st", $user.st) | |
$obj.add("cn", $user.cn) | |
$obj.add("creatorsname", $user.creatorsname) | |
$obj.add("modifiersname", $user.modifiersname) | |
$obj.add("city", $user.city) | |
if ($user.orclisenabled) { | |
switch ($user.orclisenabled) { | |
"DISABLED" { $obj.add("orclisenabled", $false) } | |
"ENABLED" { $obj.add("orclisenabled", $true ) } | |
default { $obj.add("orclisenabled", $true) } | |
} | |
} | |
else { | |
$obj.add("orclisenabled", $true) | |
} | |
if ($user.strongauthenticated) { | |
switch ($user.strongauthenticated) { | |
"N" { $obj.add("strongauthenticated", $false ) } | |
"Y" { $obj.add("strongauthenticated", $true) } | |
default { $obj.add("strongauthenticated", $false) } | |
} | |
} | |
else { | |
$obj.add("strongauthenticated", $false) | |
} | |
$obj.add("ou", $user.ou) | |
$obj.add("payroll", $user.payroll) | |
$obj.add("orclnormdn", $user.orclnormdn) | |
$obj.add("personaltitle", $user.personaltitle) | |
$obj.add("sn", $user.sn) | |
$obj.add("employeenumber", $user.employeenumber) | |
$obj.add("positionfunction", $user.positionfunction) | |
$obj.add("givenname", $user.givenname) | |
$obj.add("sbitype", $user.sbitype) | |
$obj.add("title", $user.title) | |
$obj.add("c", $user.c) | |
$obj.add("hiredate", $user.$hiredate) | |
$obj.add("employmenttype", $user.employmenttype) | |
$obj.add("orcldateofbirth", $user.orcldateofbirth) | |
$obj.add("pwdchangedtime", $user.pwdchangedtime) | |
$obj.add("companycode", $user.companycode) | |
$obj.add("positiontitle", $user.positiontitle) | |
$obj.add("modifytimestamp", $user.modifytimestamp) | |
$obj.add("postalcode", $user.postalcode) | |
$obj.add("appointmentdate", $user.appointmentdate) | |
# Pass to the MA | |
$obj | |
$usersProcessed++ | |
} | |
"Users Processed: $($usersProcessed)" | Out-File $DebugFile -Append | |
# More Groups to Process? Let the Sync Engine know | |
$global:groupsProcessed++ | |
if ($global:groupsProcessed -lt $Global:groupsCount) { | |
"Groups Processed: $($groupsProcessed + 1)" | Out-File $DebugFile -Append | |
$global:groupsProcessed + "Groups out of " + $Global:groupsCount + " Groups processed." | Out-File $DebugFile -Append | |
$global:MoreToImport = $true | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment