Public/Convert-EntraIDUnifierUser.ps1

function Convert-EntraIDUnifierUser
{
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory=$true)]
        [Microsoft.Open.AzureAD.Model.User] $EntraIDUser,
        [Parameter(
            Mandatory=$true)]
        [SecureString] $AccountPassword,
        [Parameter(
            Mandatory=$false)]
        [Switch] $ChangePasswordAtLogon,
        [Parameter(
            Mandatory=$false)]
        [System.String] $OUPath = 'CN=Users,' + (Get-ADRootDSE -Properties supportedExtension).defaultNamingContext,
        [Parameter(
            Mandatory=$false)]
        [Switch] $SkipAzureADModuleConnectionCheck,
        [Parameter(
            Mandatory=$false)]
        [Switch] $AllowsAMAccountNameTruncation,
        [Parameter(
            Mandatory=$false)]
        [Switch] $OnlyVerifyActions
    )

    # Check if AzureAD is connected
    if ($SkipAzureADModuleConnectionCheck.IsPresent) {
        Write-Verbose "Skipping AzureAD module connection check"
    } else {
        Test-AzureADModuleConnection
    }

    # Update the passed $EntraIDUser with data from EntraID
    try {
        Write-Verbose "Updating the EntraIDUser object with data from Entra ID"
        $EntraIDUser = Get-AzureADUser -ObjectId $EntraIDUser.ObjectId -ErrorAction Stop
        Write-Verbose "EntraIDUser object has been updated"
    }
    catch {
        Write-Verbose "Unable to update EntraIDUser object with data from Entra ID. Error $($Error[0])"
    }
    
    # Check that the account isn't a Directory Synchronization Service Account
    Write-Verbose "Checking if Microsoft Entra ID user is a On-Premises Directory Synchronization Service Account"
    if ($EntraIDUser.DisplayName -eq 'On-Premises Directory Synchronization Service Account') {
        Throw "The passed Microsoft Entra ID user looks to be a On-Premises Directory Synchronization Service Account."
    }
    
    # Check if the Microsoft Entra ID user directory synced
    Write-Verbose "Checking if Microsoft Entra ID user is already directory synced"
    if ($EntraIDUser.DirSyncEnabled) {
        Throw "Microsoft Entra ID user already synced with Microsoft Entra Connect. This user looks to already be synced with Microsoft Entra Connect."
    }

    # Generating a sAMAccountName from the UserPrincipalName
    Write-Verbose "Generating sAMAccountName for the user"
    $GeneratedsAMAccountName = $EntraIDUser.UserPrincipalName.Split("@")[0]


    # Check the generated sAMAccountName length. Microsoft Active Directory has a max length of 15 characters of the sAMAccountName attribute
    Write-Verbose "Checking the generated sAMAccountName length"
    if ($GeneratedsAMAccountName.Length -gt 15) {

        $GeneratedsAMAccountName = $GeneratedsAMAccountName.substring(0,15)

        if ($AllowsAMAccountNameTruncation.IsPresent) {
            Write-Verbose "AllowsAMAccountNameTruncation switch is present. Automatically truncating the sAMAccountName"
        } else {
            Throw "The generated sAMAccountName is over the 15 character limit ('$($EntraIDUser.UserPrincipalName.Split("@")[0])')."
        }

    }

    Write-Verbose "Generated sAMAccountName is '$($GeneratedsAMAccountName)'"

    # Check if the UserPrincipalName & SamAccountName is already in use
    Write-Verbose "Checking if the sAMAccountName is available for use to use within Active Directory"
    $sAMAccountNameSearchCheck = Get-ADUser -Filter "sAMAccountName -like '$($GeneratedsAMAccountName)'"
    if ($sAMAccountNameSearchCheck) {
        Throw "The generated sAMAccountName ('$($GeneratedsAMAccountName)') is already in use within Active Directory"
    }
    
    Write-Verbose "Checking if the UserPrincipalName is available for use to use within Active Directory"
    $UserPrincipalNameSearchCheck = Get-ADUser -Filter "UserPrincipalName -like '$($EntraIDUser.UserPrincipalName)'"
    if ($UserPrincipalNameSearchCheck) {
        Throw "The generated UserPrincipalName is already in use within Active Directory"
    }

    # Check if the object name is already in use in the path
    ## The display name is used as the cn by default in active directory
    if ($EntraIDUser.DisplayName -in (Get-ADObject -Filter * -SearchBase $OUPath).Name) {
        Throw "The proposed CN is already in use in the '$OUPath' OU"
    }

    # Building new user object
    Write-Verbose "Building Active Directory user object"
    $NewActiveDirectoryUser = Build-ADUserPropertiesObject -EntraIDUser $EntraIDUser

    # Add AccountPassword to the $NewActiveDirectoryUser object
    Write-Verbose "Adding 'AccountPassword' property to Active Directory user object from passed parameter"
    $NewActiveDirectoryUser | Add-Member -MemberType NoteProperty -Name 'AccountPassword' -Value $AccountPassword

    # Add ChangePasswordAtLogon if Present in the switch parameters
    if ($ChangePasswordAtLogon.IsPresent) {
        Write-Verbose "Adding 'ChangePasswordAtLogon' property to Active Directory user object as -ChangePasswordAtLogon parameter switch is present"
        $NewActiveDirectoryUser | Add-Member -MemberType NoteProperty -Name 'ChangePasswordAtLogon' -Value $True
    }

    # Add UserPrincipalName to the $NewActiveDirectoryUser object
    Write-Verbose "Adding 'UserPrincipalName' property to Active Directory user object from generated value"
    $NewActiveDirectoryUser | Add-Member -MemberType NoteProperty -Name 'UserPrincipalName' -Value $EntraIDUser.UserPrincipalName

    # Add sAMAccountName to the $NewActiveDirectoryUser object
    Write-Verbose "Adding 'sAMAccountName' property to Active Directory user object from generated value"
    $NewActiveDirectoryUser | Add-Member -MemberType NoteProperty -Name 'sAMAccountName' -Value $GeneratedsAMAccountName
    
    # Add Path in the pass variables
    Write-Verbose "Adding 'Path' property to Active Directory user object."
    $NewActiveDirectoryUser | Add-Member -MemberType NoteProperty -Name 'Path' -Value $OUPath

    # Check if we can run create and update actions
    if (!$OnlyVerifyActions.IsPresent) {

        # Attempt to create active direcory user account with the the $NewActiveDirectoryUser object
        try {
            Write-Verbose "Attempting to create Active Directory user account"
            $NewActiveDirectoryUser | New-AdUser -ErrorAction Stop
            Write-Verbose "Active Directory user account has been created"
        } catch {
            Write-Error "Unable to create Active Directory user account. Error $($Error[0])" -ErrorAction Stop
        }
        
        # Attempt to get the newly created active directory user account
        try {
            Write-Verbose "Attempting to get the newly created Active Directory user account"
            $NewActiveDirectoryUser = Get-AdUser -Identity $GeneratedsAMAccountName
        } catch {
            Write-Error "Unable to get the newly Active Directory user account" -ErrorAction Stop
        }

        # Attempt to sync the newly created account with Entra ID
        try {
            Write-Verbose "Attempting to sync the Microsoft Entra ID user to the newly created Active Directory Account"
            Sync-EntraIDUnifierUser -EntraIDUser $EntraIDUser -ActiveDirectoryUser $NewActiveDirectoryUser -SkipAzureADModuleConnectionCheck:$true
            Write-Verbose "Microsoft Entra ID user has been updated to sync with the newly created Active Directory Account"
        }
        catch {
            Write-Error "Unable to sync the newly create Active Directory account with Entra ID. Error $($Error[0])"
        }

    } else {

        Write-Verbose "The OnlyVerifyActions switch parameter has been passed. Not adding or making changes to the account"

    }


}