Public/Start-ChangeEmailAgentActiveDirectoryListener.ps1

<#
.DESCRIPTION
    Starts the ChangeEmailAgent listener. The listener continuously checks for password reset requests and processes them.

.SYNOPSIS
    Starts the ChangeEmailAgent listener.

.EXAMPLE
    Start-ChangeEmailAgentActiveDirectoryListener -Sleep 5
#>

function Start-ChangeEmailAgentActiveDirectoryListener {
    [CmdletBinding()]
    Param(
        # Sleep interval in seconds between checks for new requests
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 3600)]
        [int] $Sleep = 10,

        # Max number of loops to run. Default is infinite.
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 2147483647)]
        [int] $MaxNumberOfRRuns = 2147483647,

        [Parameter(Mandatory = $false)]
        [scriptblock] $RunBlockAfterChange = $null
    )

    process {
        if (!(get-command Set-ADUser* | ? Name -eq Set-ADUser)) {
            Write-Error -Message "Set-ADUser cmdlet is not available. Please ensure the Active Directory module is installed and imported."
            Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1210 -EntryType Error -Message "Set-ADUser cmdlet is not available. Please ensure the Active Directory module is installed and imported." -ErrorAction Continue
            return
        }

        Write-Verbose "Starting ChangeEmailAgent $($PSCmdlet.MyInvocation.MyCommand.ScriptBlock.Module.Version?.ToString()) listener with a sleep interval of $Sleep seconds"
        Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1006 -EntryType Information -Message "Starting ChangeEmailAgent $($PSCmdlet.MyInvocation.MyCommand.ScriptBlock.Module.Version?.ToString()) listener with a sleep interval of $Sleep seconds" -ErrorAction Continue

        while ($true) {
            $MaxNumberOfRRuns -= 1
            if ($MaxNumberOfRRuns -lt 0) {
                Write-Verbose "MaxNumberOfRRuns reached, exiting listener"
                Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1011 -EntryType Information -Message "MaxNumberOfRRuns reached, exiting listener" -ErrorAction Continue
                break
            }
            

            $requests = Receive-ChangeEmailAgentRequests

            if ($requests) {
                foreach ($request in $requests) {
                    Write-Verbose "Processing request: $($request | ConvertTo-Json -Depth 5)"

                    if (!$request.onPremisesImmutableId) {
                        Write-Warning "Request does not contain onPremisesImmutableId: $($request | ConvertTo-Json -Depth 5)"
                        Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1008 -EntryType Warning -Message "Request does not contain onPremisesImmutableId: $($request | ConvertTo-Json -Depth 5)" -ErrorAction Continue
                        $request | Complete-ChangeEmailAgentRequest -Status "Failed" -ErrorMessage "Request does not contain onPremisesImmutableId"
                        continue
                    }

                    $immutableid = [System.Convert]::FromBase64String($request.onPremisesImmutableId)
                    $matchingusers = Get-ADUser -Filter { ms-Ds-ConsistencyGuid -eq $immutableid } -Properties mail, proxyAddresses, userPrincipalName

                    if (!$matchingusers) {
                        Write-Warning "No matching user found in Active Directory for request $($request | ConvertTo-Json -Depth 5)"
                        Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1009 -EntryType Warning -Message "No matching user found in Active Directory for request $($request | ConvertTo-Json -Depth 5)" -ErrorAction Continue
                        $request | Complete-ChangeEmailAgentRequest -Status "Failed" -ErrorMessage "No matching user found in Active Directory"
                        continue
                    }

                    if ($matchingusers.Count -gt 1) {
                        Write-Warning "Multiple matching users found in Active Directory for request $($request | ConvertTo-Json -Depth 5)"
                        Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1010 -EntryType Warning -Message "Multiple matching users found in Active Directory for request $($request | ConvertTo-Json -Depth 5)" -ErrorAction Continue
                        $request | Complete-ChangeEmailAgentRequest -Status "Failed" -ErrorMessage "Multiple matching users found in Active Directory"
                        continue
                    }

                    $user = $matchingusers | Select-Object -First 1

                    $proxyaddresses = @(
                        "SMTP:$($request.emailAddress)"
                    )

                    if ($user.proxyAddresses) {
                        $user.proxyAddresses | 
                        Where-Object { $_ -like "smtp:*" } | 
                        Where-Object { $_ -notin $proxyaddresses } | 
                        ForEach-Object {
                            $proxyaddresses += "{0}" -f ($_ -replace "^SMTP", "smtp")
                        }

                        $user.proxyAddresses | 
                        Where-Object { $_ -notlike "smtp:*" } | 
                        Where-Object { $_ -notin $proxyaddresses } | 
                        ForEach-Object {
                            $proxyaddresses += "{0}" -f $_
                        }
                    }

                    $_UpdatingUserMessage = @(
                        "Updating user $($user.SamAccountName):"
                        " - userPrincipalName: $($user.UserPrincipalName) -> $($request.emailAddress)"
                        " - mail: $($user.mail) -> $($request.emailAddress)"
                        " - proxyAddresses:"
                    )
                    $user.proxyAddresses | Where-Object { $_ -cnotin $proxyaddresses } | ForEach-Object { $_UpdatingUserMessage += " - $_ (removed)" }
                    $proxyAddresses | Where-Object { $_ -cnotin $user.proxyaddresses } | ForEach-Object { $_UpdatingUserMessage += " + $_ (added)" }
                    $user.proxyAddresses | Where-Object { $_ -cin $proxyaddresses } | ForEach-Object { $_UpdatingUserMessage += " ~ $_ (no change)" }

                    $UpdatingUserMessage = $_UpdatingUserMessage -join "`n"
                    
                    Write-Verbose $UpdatingUserMessage
                    Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1012 -EntryType Information -Message $UpdatingUserMessage -ErrorAction Continue
                    
                    try {
                        $user | Set-ADUser -UserPrincipalName $request.emailAddress -EmailAddress $request.emailAddress -Replace @{
                            proxyAddresses = $proxyaddresses
                        }
                    }
                    catch {
                        Write-Error "Failed to update user $($user.SamAccountName): $_"
                        Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1013 -EntryType Error -Message "Failed to update user $($user.SamAccountName): $_" -ErrorAction Continue
                        $request | Complete-ChangeEmailAgentRequest -Status "Failed" -ErrorMessage "$_"
                        continue
                    }
                    
                    $request | Complete-ChangeEmailAgentRequest
                }

                if ($RunBlockAfterChange) {
                    Write-Verbose "Running custom script block after processing changes"
                    & $RunBlockAfterChange
                }
            }
            else {
                Write-Verbose "No requests found, sleeping for $Sleep seconds"
                Write-EventLog -LogName "Application" -Source "ChangeEmailAgent" -EventId 1007 -EntryType Information -Message "No requests found, sleeping for $Sleep seconds" -ErrorAction Continue
            }

            Start-Sleep -Seconds $Sleep
        }
        
    }
}