public/Update-GWADObject.ps1

Function Update-GWADObject {
    [CmdletBinding(
        DefaultParameterSetName = 'ByParameters',
        SupportsShouldProcess,
        ConfirmImpact = 'High')
    ]
    param (
        # Parameter set for passing individual parameters
        [Parameter(ParameterSetName = 'ByParameters', Mandatory = $true)]
        [string]$Identity,

        [Parameter(ParameterSetName = 'ByParameters', Mandatory = $false)]
        [string]$UserPrincipalName = $null,    

        [Parameter(ParameterSetName = 'ByParameters', Mandatory = $false)]
        [string]$OrganizationalUnit = $null,

        # Parameter set for passing a collection of objects
        [Parameter(ParameterSetName = 'ByObject', Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject[]]$InputObject,

        [Parameter(ParameterSetName = 'ByObject', Mandatory = $false)]
        [Parameter(ParameterSetName = 'ByParameters', Mandatory = $false)]
        [switch]$Force
    )

    <#
    .SYNOPSIS
        Updates the user principal name and organizational unit of an AD object or collection of AD objects
 
    .DESCRIPTION
        This function updates the UserPrincipalName and OrganizationalUnit of user accounts, groups and devices. It supports two modes of operation:
        - You can provide individual parameters for a single object
        - You can provide a collection of objects, each containing the properties to be updated.
 
    .PARAMETER Identity
        The identity of the object. This can be a UserPrincipalName, a SamAccountName or a Name. The function will try to find a unique match for that in the current domain, This parameter is used in the 'ByParameters' parameter set.
 
    .PARAMETER UserPrincipalName
        The new UserPrincipalName for the object. This parameter is used in the 'ByParameters' parameter set.
 
    .PARAMETER OrganizationalUnit
        The new Organizational Unit to which the object should be moved. This parameter is used in the 'ByParameters' parameter set.
 
    .PARAMETER InputObject
        A collection of objects where each object has properties 'Identity', 'UserPrincipalName', and 'OrganizationalUnit'.
        This parameter is used in the 'ByObject' parameter set and supports pipeline input.
 
    .PARAMETER Force
        Force updating the object so that it doesn't ask you to confirm each one (alternative to -Confirm:$False)
 
    .EXAMPLE
        Update-GWADObject -Identity "user1@domain.com" -UserPrincipalName "user1.new@domain.com" -OrganizationalUnit "OU=NewOU,DC=domain,DC=com"
         
        Description:
        This example updates a single object's User Principal Name and moves the object to a new Organizational Unit.
 
    .EXAMPLE
        $objects = @(
            [PSCustomObject]@{Identity = "user2@domain.com"; UserPrincipalName = "user2.new@domain.com"; OrganizationalUnit = "OU=NewOU,DC=domain,DC=com"},
            [PSCustomObject]@{Identity = "Group Name"; UserPrincipalName = $null; OrganizationalUnit = "OU=NewOU,DC=domain,DC=com"}
        )
        $objects | Update-GWADObject
 
    Description:
        This example takes a collection of objects from a pipeline and updates their User Principal Name and/or Organizational Unit
 
    .NOTES
        You can use this function in two ways: by specifying individual parameters for one object or by passing a collection of objects representing multiple objects.
 
    .LINK
        https://geekwolf.cloud/something
    #>



    process {
        if ($Force -and -not $Confirm){
            $ConfirmPreference = 'None'
        }

        if ($PSCmdlet.ParameterSetName -eq 'ByParameters') {
            $ObjectsToProcess = @( @{
                Identity=$Identity;
                UserPrincipalName=$UserPrincipalName;
                OrganizationalUnit=$OrganizationalUnit
            } )
        } elseif ($PSCmdlet.ParameterSetName -eq 'ByObject') {
            $ObjectsToProcess = $InputObject
        }

        foreach( $ObjectToProcess in $ObjectsToProcess ) {
            if( [string]::IsNullOrEmpty($ObjectToProcess.Identity) ) {
                $ObjectsFound = @(Get-ADObject -LDAPFilter `
                    "(&(|(objectClass=group)(objectClass=user)(objectClass=computer))(anr=$($ObjectToProcess.Identity)))")
            } else {
                $ObjectsFound = @(Get-ADObject -LDAPFilter `
                    "(&(|(objectClass=group)(objectClass=user)(objectClass=computer))(|(userprincipalname=$($ObjectToProcess.Identity))(anr=$($ObjectToProcess.Identity))))")
            }

            if( $ObjectsFound.Count -gt 1 ) {
                # do we have exact match on UPN
                $Object = @($ObjectsFound | Where-Object { $_.userPrincipalName -eq $ObjectToProcess.Identity })

                # if not, do we have exact match on samAccountName
                if( $Object.count -ne 1 ) {
                    $Object = @($ObjectsFound | Where-Object { $_.SamAccountName -eq $ObjectToProcess.Identity })
                }

                # if not, do we have exact match on Name
                if( $Object.count -ne 1 ) {
                    $Object = @($ObjectsFound | Where-Object { $_.Name -eq $ObjectToProcess.Identity })
                }

                if( $Object.Count -eq 1 ) {
                    $ObjectsFound = $Object
                }
            }

            $ResultMessage = ""
            $Succeeded = $True
            Switch( $ObjectsFound.count ) {
                0 { 
                    Write-Verbose "No object found for [$($ObjectToProcess.Identity)]"
                    $ResultMessage += "[No such object found]"
                    $Succeeded = $False
                   }
                1 {
                    # Validate the OU if it was provided
                    if( [string]::IsNullOrEmpty( $ObjectToProcess.OrganizationalUnit ) -eq $false ) {
                        try {
                            $ADOrganizationalUnit = @(Get-ADObject -Identity $ObjectToProcess.OrganizationalUnit -ErrorAction SilentlyContinue)
                        } catch {
                            $ADOrganizationalUnit = @()
                        }

                        if( $ADOrganizationalUnit.count -eq 0 ) {
                            Write-Verbose "Unable to move [$($ObjectToProcess.Identity)] to [$($ObjectToProcess.OrganizationalUnit)] as OU does not exist" 
                            $ResultMessage += "[Object Move Failed OU does not exist]"
                            $Succeeded = $False
                        }
                    }

                    if( $PSCmdLet.ShouldProcess($ObjectToProcess.Identity) ){
                        # Update the UPN if it was provided
                        if( [string]::IsNullOrEmpty( $ObjectToProcess.UserPrincipalName ) -eq $false ) {
                            try {
                                Set-ADObject -Identity $ObjectsFound[0].ObjectGUID -Replace @{userPrincipalName=$($ObjectToProcess.UserPrincipalName)} | Out-Null
                                $ResultMessage += "[Object Updated Successfully]"
                            } catch {
                                Write-Verbose "Unable to rename [$($ObjectToProcess.Identity)] to [$($ObjectToProcess.UserPrincipalName)] exception: $($_.Exception)" 
                                $ResultMessage += "[Object Update Failed ($($_.Exception))]"
                                $Succeeded = $False
                            }
                        }

                        # Update the OU if it was provided
                        if( [string]::IsNullOrEmpty( $ObjectToProcess.OrganizationalUnit ) -eq $false ) {
                            if( $ADOrganizationalUnit.count -eq 1 ) {
                                try {
                                    Move-ADObject -Identity $ObjectsFound[0].ObjectGUID -TargetPath $ObjectToProcess.OrganizationalUnit | Out-Null
                                    $ResultMessage += "[Object Moved Successfully]"
                                } catch {
                                    Write-Verbose "Unable to move [$($ObjectToProcess.Identity)] to [$($ObjectToProcess.OrganizationalUnit)] exception: $($_.Exception)" 
                                    $ResultMessage += "[Object Move Failed ($($_.Exception))]"
                                    $Succeeded = $False
                                }
                            }
                        }

                     }
                   }
                default { 
                    Write-Verbose "Multiple objects found for [$($ObjectToProcess.Identity)]" 
                    $ResultMessage += "[Multiple objects found]"
                    $Succeeded = $False
                   }
            }

            New-Object PSObject -Property @{
                Identity = $ObjectToProcess.Identity;
                UserPrincipalName = $ObjectToProcess.UserPrincipalName;
                OrganizationalUnit = $ObjectToProcess.OrganizationalUnit;
                Succeeded = $Succeeded;
                ResultMessage = $ResultMessage -replace "`r`n", "\r\n"
            }
        }
    }
}