Public/Get-UserProvisioningSyncSessionOperation.ps1
|
function Get-UserProvisioningSyncSessionOperation { [CmdletBinding()] param () Begin { Write-ConnectorVerbose "Getting all users from all included OUs..." $Script:UserMap = @{} $Script:UserMapAdditional = @{} $Script:UserMapAdditionalDuplicates = @{} $Script:IncludedOUs | ForEach-Object { Write-ConnectorVerbose "Getting users from OU: $_" Get-ADUser -SearchBase $_ -LDAPFilter "($($Script:LifeCycleStateAttribute)=*)" -Properties $Script:UserProperties | ForEach-Object { $State = $_.($Script:LifeCycleStateAttribute) | ConvertFrom-Json if ($State.identifier) { if ($Script:UserMap.ContainsKey($State.identifier)) { Write-Warning "Multiple users with the same lifecycle state identifier '$($State.identifier)' found. This may lead to unexpected behavior. Please ensure that the attribute '$($Script:LifeCycleStateAttribute)' contains unique identifiers for each user." } $Script:UserMap[$State.identifier] = $_ } else { Write-Warning "User $($_.DistinguishedName) does not have a valid lifecycle state in attribute '$($Script:LifeCycleStateAttribute)'. Skipping user." } } if ($Script:AdditionalJoinAttribute) { Get-ADUser -SearchBase $_ -LDAPFilter "($($Script:AdditionalJoinAttribute)=*)" -Properties $Script:UserProperties | ForEach-Object { if ($_.($Script:LifeCycleStateAttribute)) { return; } $_joinValue = $_.$($Script:AdditionalJoinAttribute) | Select-Object -First 1 if ($Script:UserMapAdditionalDuplicates.ContainsKey($_joinValue) -or $Script:UserMapAdditional.ContainsKey($_joinValue)) { Write-Warning "Multiple users with the same additional join attribute '$($_joinValue)'" $Script:UserMapAdditionalDuplicates[$_joinValue] ??= @() $Script:UserMapAdditionalDuplicates[$_joinValue] += $_ if ($Script:UserMapAdditional.ContainsKey($_joinValue)) { $Script:UserMapAdditionalDuplicates[$_joinValue] += $Script:UserMapAdditional[$_joinValue] $Script:UserMapAdditional.Remove($_joinValue) } } else { $Script:UserMapAdditional[$_joinValue] = $_ } Write-Debug "User $($_.DistinguishedName) added to additional join attribute map with key '$($_joinValue)'" } } } Write-ConnectorVerbose "Total users with valid lifecycle state found in included OUs: $($Script:UserMap.Count)" if ($Script:AdditionalJoinAttribute) { Write-ConnectorVerbose "Total users in additional join attribute map found in included OUs: $($Script:UserMapAdditional.Count)" Write-Warning "Total number of duplicated additional join attribute values: $($Script:UserMapAdditionalDuplicates.Count)" if ($Script:UserMapAdditionalDuplicates.Count -gt 0) { Write-Warning "List of duplicated additional join attribute values:" $Script:UserMapAdditionalDuplicates.GetEnumerator() | ForEach-Object { Write-Warning " - Value '$($_.Key)' has $($Script:UserMapAdditionalDuplicates[$_.Key].Count) users:" $Script:UserMapAdditionalDuplicates[$_.Key] | ForEach-Object { Write-Warning " - $($_.DistinguishedName)" } } } } } Process { Write-Verbose "Getting all used sAMAccountNames" $UsedSamAccountNames = @{} Get-ADUser -Properties samaccountname -filter * | ForEach-Object { $UsedSamAccountNames[$_.samaccountname] = $true } $Script:SyncSessionObjects.GetEnumerator() | ForEach-Object { $Identifier = $_.Key $InputObject = $_.Value $ExistingUser = $Script:UserMap[$Identifier] # Try additional join attribute matching if enabled and no user found by lifecycle state identifier if (!$ExistingUser -and $Script:AdditionalJoinAttribute -and $InputObject.$($Script:AdditionalJoinAttribute)) { $joinValue = $InputObject.$($Script:AdditionalJoinAttribute) if ($Script:UserMapAdditional.ContainsKey($joinValue)) { $ExistingUser = $Script:UserMapAdditional[$joinValue] Write-Debug "User with identifier '$Identifier' matched to existing user '$($ExistingUser.DistinguishedName)' by additional join attribute '$($Script:AdditionalJoinAttribute)' with value '$joinValue'." } } if ($ExistingUser) { # Compare the existing user with the input object to determine if an update is needed $Parameters = @{} $OldValues = @{} $InputObject.Keys | Foreach-Object { if ($_ -eq "manager") { if ($null -eq $InputObject.manager) { if ($ExistingUser.manager) { Write-Verbose "Manager differs for user with identifier '$Identifier'. Manager will be cleared." $Parameters["Clear"] ??= @() $Parameters["Clear"] += "manager" $OldValues["manager"] = $ExistingUser.manager } } elseif ($Script:UserMap.ContainsKey($InputObject.manager)) { if ($Script:UserMap[$InputObject.manager].DistinguishedName -ne $ExistingUser.manager) { $Parameters["Replace"] ??= @{} $Parameters["Replace"][$_] = $Script:UserMap[$InputObject.manager].DistinguishedName $OldValues["manager"] = $ExistingUser.manager } } else { Write-Warning "Manager with identifier '$($InputObject.manager)' not found in existing users. Manager will not be updated for user with identifier '$Identifier'." } } elseif ($_ -eq "enabled") { if ($InputObject.enabled -ne $ExistingUser.Enabled) { Write-Verbose "Enabled state differs for user with identifier '$Identifier'. Enabled will be updated to '$($InputObject.enabled)'." $Parameters["Enabled"] = $InputObject.enabled $OldValues["Enabled"] = $ExistingUser.Enabled } } elseif ($ExistingUser.$_ -cne $InputObject.$_) { if ($null -eq $InputObject.$_) { $Parameters["Clear"] ??= @() $Parameters["Clear"] += $_ $OldValues[$_] = $ExistingUser.$_ } else { $Parameters["Replace"] ??= @{} $Parameters["Replace"][$_] = $InputObject.$_ $OldValues[$_] = $ExistingUser.$_ } } } if (!($ExistingUser."$($Script:LifeCycleStateAttribute)")) { $Parameters["Replace"] ??= @{} $Parameters["Replace"]["$($Script:LifeCycleStateAttribute)"] = @{ identifier = $Identifier disabled = $null } | ConvertTo-Json -Compress $OldValues["$($Script:LifeCycleStateAttribute)"] = $null } else { $State = $ExistingUser."$($Script:LifeCycleStateAttribute)" | ConvertFrom-Json if ($State.disabled) { $State.disabled = $null $Parameters["Replace"] ??= @{} $Parameters["Replace"]["$($Script:LifeCycleStateAttribute)"] = $State | ConvertTo-Json -Compress $OldValues["$($Script:LifeCycleStateAttribute)"] = $ExistingUser."$($Script:LifeCycleStateAttribute)" } } if ($Parameters.Count -gt 0) { Write-Verbose "User with identifier '$Identifier' exists but has differences. An update operation will be planned." New-UserProvisioningSyncSessionOperation -Action "Set-ADUser" -Identity $ExistingUser.ObjectGUID.ToString() -Parameters $Parameters -OldValues $OldValues } else { # User exists and is the same, no operation needed Write-Debug "User with identifier '$Identifier' already exists and is up to date. No operation will be planned." } } else { # User does not exist, create needed Write-Verbose "User with identifier '$Identifier' does not exist. A create operation will be planned." $Parameters = @{ OtherAttributes = @{} } $InputObject.Keys | ForEach-Object { if ($_ -eq "manager") { if ($InputObject.manager) { if ($Script:UserMap.ContainsKey($InputObject.manager)) { $Parameters["OtherAttributes"][$_] = $Script:UserMap[$InputObject.manager].DistinguishedName } else { Write-Warning "Manager with identifier '$($InputObject.manager)' not found in existing users. Manager will not be set for user with identifier '$Identifier'." } } } elseif ($_ -eq "enabled") { $Parameters["Enabled"] = $InputObject.enabled ?? $false } elseif ($_ -eq "ou") { $Parameters["Path"] = $InputObject.ou } elseif ($InputObject.$_) { $Parameters["OtherAttributes"][$_] = $InputObject.$_ } } $Parameters["OtherAttributes"][$Script:LifeCycleStateAttribute] = @{ identifier = $Identifier disabled = $null } | ConvertTo-Json -Compress if (!$Parameters.ContainsKey("SamAccountName")) { if (!$InputObject.givenName -or !$InputObject.sn) { Write-Warning "Cannot generate SamAccountName for user with identifier '$Identifier' because givenName or sn is missing." } else { Get-UserProvisioningSamAccountName -givenName $InputObject.givenName -sn $InputObject.sn | Where-Object { $s = $_ !$UsedSamAccountNames.ContainsKey($s) } | Select-Object -First 1 | ForEach-Object { $Parameters["SamAccountName"] = $_ } if (!$Parameters["SamAccountName"]) { $Parameters["SamAccountName"] = (New-Guid).ToString().Replace("-", "").Substring(0, 20) } $UsedSamAccountNames[$Parameters["SamAccountName"]] = $true } } if (!$Parameters.ContainsKey("Name")) { if ($Parameters["SamAccountName"]) { $Parameters["Name"] = $Parameters["SamAccountName"] } else { $Parameters["Name"] = $Identifier } } if (!$Parameters.ContainsKey("Path")) { $Parameters["Path"] = $Script:DefaultDestinationOU } New-UserProvisioningSyncSessionOperation -Action "New-ADUser" -Identity $Identifier -Parameters $Parameters } } # Any remaining users in the map are not present in the input objects and should be scheduld by deletion, by: # Checking the life cycle attribute 'disabled' property # If the date is more than $Script:DeleteUsersAfterDays (set during connect) days ago - create a Remove-ADObject operation # Else if the 'disabled' time stamp is not there, create a Set-ADUser operation that updates the life cycle attribute with a 'disabled'timestamp to now() $Script:UserMap.GetEnumerator() | Where-Object { -not $Script:SyncSessionObjects.ContainsKey($_.Key) } | ForEach-Object { $Identifier = $_.Key $ExistingUser = $_.Value $State = $ExistingUser.($Script:LifeCycleStateAttribute) | ConvertFrom-Json if ($null -ne $State.disabled) { $DisabledDate = Get-Date $State.disabled if ($ExistingUser.Enabled) { Write-Warning "User with identifier '$Identifier' is enabled but has a disabled timestamp in the lifecycle state attribute. This is an inconsistent state. A disable operation will be planned to correct the inconsistency before any deletion scheduling is done." New-UserProvisioningSyncSessionOperation -Action "Set-ADUser" -Identity $ExistingUser.DistinguishedName -Parameters @{ Replace = @{ "$($Script:LifeCycleStateAttribute)" = @{ identifier = $Identifier disabled = (Get-Date).ToString("o") } | ConvertTo-Json -Compress } Enabled = $false } } elseif ($DisabledDate -lt (Get-Date).AddDays(-$Script:DeleteUsersAfterDays)) { # User has been disabled for longer than the threshold, schedule for deletion Write-Verbose "User with identifier '$Identifier' has been disabled since '$DisabledDate', which is longer than the threshold of '$($Script:DeleteUsersAfterDays)' days. A delete operation will be planned." New-UserProvisioningSyncSessionOperation -Action "Remove-ADObject" -Identity $ExistingUser.DistinguishedName } else { # User has been disabled but not long enough, no operation needed Write-Debug "User with identifier '$Identifier' has been disabled since '$DisabledDate', which is not longer than the threshold of '$($Script:DeleteUsersAfterDays)' days. No operation will be planned at this time." } } else { # User is not disabled, schedule for disablement by updating the life cycle state attribute with a 'disabled' timestamp to now() Write-Verbose "User with identifier '$Identifier' is not disabled but is missing from the input objects. A disable operation will be planned." New-UserProvisioningSyncSessionOperation -Action "Set-ADUser" -Identity $ExistingUser.DistinguishedName -Parameters @{ Replace = @{ "$($Script:LifeCycleStateAttribute)" = @{ identifier = $Identifier disabled = (Get-Date).ToString("o") } | ConvertTo-Json -Compress } Enabled = $false } } } } End { } } |