Skip to content

Instantly share code, notes, and snippets.

@darrenjrobinson
Last active January 29, 2021 19:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darrenjrobinson/71f382902a4c81ddceab687bb1b99422 to your computer and use it in GitHub Desktop.
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/
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