Public/CallQueue/Set-TeamsCallQueue.ps1

# Module: Orbit.Teams
# Function: CallQueue
# Author: David Eberhardt
# Updated: 01-OCT-2020
# Status: Live
#VALIDATE Adding Users where SIP and UPN are different results in error?
#TEST WelcomeTextToSpeechPrompt - Parametername is a guess based on SharedVoicemail - Could be named completely different.
#CHECK WelcomeMessage input is ONE parameter for both TTV and File, ShareVoicemail params are TWO params separately.
#CHECK Reasoning for TWO is: both can be specified, clear input, but bloat; Reason for ONE is: Neat, can identify anyway - Harmonise?
#TODO Add EnableSystemPromptSuppression - instead of building EnableOverflowSharedVoicemailSuppressSystemMessage
#TODO Add EnableTranscription - Remove EnableOverflowSharedVoicemailTranscription (both/all 3)
#TODO Rework AudioFilePrompt and TextToSpeechPrompt to OverflowPrompt, TimeoutPrompt and NoAgentPrompt (to accept either)
function Set-TeamsCallQueue {
  <#
  .SYNOPSIS
    Set-CsCallQueue with UPNs instead of IDs
  .DESCRIPTION
    Does all the same things that Set-CsCallQueue does, but differs in a few significant respects:
    UserPrincipalNames can be provided instead of IDs, FileNames (FullName) can be provided instead of IDs
    File Import is handled by this Script
    Set-CsCallQueue is used to apply parameters dependent on specification.
    Partial implementation is possible, output will show differences.
  .PARAMETER Name
    Required. Friendly Name of the Call Queue. Used to Identify the Object
  .PARAMETER DisplayName
    Optional. Updates the Name of the Call Queue. Name will be normalised (unsuitable characters are filtered)
  .PARAMETER Greeting
    Optional. Text-To-Voice String or Path to Audio File to be used as a Welcome message
    Accepted Text-To-Voice Strings: Minimum 10, Maximum 1000 characters.
    Accepted Audio File Formats: MP3, WAV or WMA format, max 5MB
  .PARAMETER MusicOnHoldAudioFile
    Optional. Path to Audio File to be used as Music On Hold.
    Accepted Formats: MP3, WAV or WMA, max 5MB
  .PARAMETER UseDefaultMusicOnHold
    Optional Switch. Indicates whether the default Music On Hold should be used.
  .PARAMETER RoutingMethod
    Optional. Describes how the Call Queue is hunting for an Agent.
    Serial will Alert them one by one in order specified (Distribution lists will contact alphabethically)
    Attendant behaves like Parallel if PresenceBasedRouting is used.
    Default: Attendant, Values: Attendant, Serial, RoundRobin, LongestIdle
  .PARAMETER PresenceBasedRouting
    Optional. Default: FALSE. If used alerts Agents only when they are available (Teams status).
  .PARAMETER ConferenceMode
    Optional. Will establish a conference instead of a direct call and should help with connection time.
    Default: TRUE, Microsoft Default: FALSE
  .PARAMETER DistributionLists
    Optional. Display Names of DistributionLists or Groups. Their members are to become Agents in the Queue.
    Mutually exclusive with TeamAndChannel. Can be combined with Users.
    Will be parsed after Users if they are specified as well.
    To be considered for calls, members of the DistributionsLists must be Enabled for Enterprise Voice.
  .PARAMETER Users
    Optional. UserPrincipalNames of Users that are to become Agents in the Queue.
    Mutually exclusive with TeamAndChannel. Can be combined with DistributionLists.
    Will be parsed first. Order is only important if Serial Routing is desired (See Parameter RoutingMethod)
    Users are only added if they have a PhoneSystem license and are or can be enabled for Enterprise Voice.
  .PARAMETER ChannelOwner
    Optional. UserPrincipalName of a owner for the Channel. Unknown use-case right now. Feeds Parameter ChannelUserObjectId
    Users are only added if they have a PhoneSystem license and are or can be enabled for Enterprise Voice.
  .PARAMETER TeamAndChannel
    Optional. Uses a Channel to route calls to. Members of the Channel become Agents in the Queue.
    Mutually exclusive with Users and DistributionLists.
    Acceptable format for Team and Channel is "TeamIdentifier\ChannelIdentifier".
    Acceptable Identifier for Teams are GroupId (GUID) or DisplayName. NOTE: DisplayName may not be unique.
    Acceptable Identifier for Channels are Id (GUID) or DisplayName.
  .PARAMETER AgentAlertTime
    Optional. Time in Seconds to alert each agent. Works depending on Routing method
    Size AgentAlertTime and TimeoutThreshold depending on Routing method and # of Agents available.
  .PARAMETER AllowOptOut
    Optional Switch. Allows Agents to Opt out of receiving calls from the Call Queue
  .PARAMETER OverflowThreshold
    Optional. Number of people waiting in the queue before the Action to trigger
  .PARAMETER OverflowAction
    Optional. Action to be taken if the Queue size limit (OverflowThreshold) is reached
    Forward requires specification of OverflowActionTarget
    Default: DisconnectWithBusy, Values: DisconnectWithBusy, Forward, VoiceMail, SharedVoiceMail
  .PARAMETER OverflowActionTarget
    Situational. Required only if OverflowAction is not DisconnectWithBusy
    UserPrincipalName of the Target
  .PARAMETER OverflowPrompt
    Optional. Prompt to play when Overflow is reaching threshold but before Overflow Action is taken
    String as a Path for a Recording or a Greeting (Text-to-Voice)
    Aliases for backward compatibility: OverflowSharedVoicemailTextToSpeechPrompt, OverflowSharedVoicemailAudioFile
  .PARAMETER TimeoutThreshold
    Optional. Time in Seconds for the TimeoutAction to trigger
  .PARAMETER TimeoutAction
    Optional. Action to be taken if the TimeoutThreshold is reached
    Forward requires specification of TimeoutActionTarget
    Default: Disconnect, Values: Disconnect, Forward, VoiceMail, SharedVoiceMail
  .PARAMETER TimeoutActionTarget
    Situational. Required only if TimeoutAction is not Disconnect
    UserPrincipalName of the Target
  .PARAMETER TimeoutPrompt
    Optional. Prompt to play when Timeout is reaching threshold but before Timeout Action is taken
    String as a Path for a Recording or a Greeting (Text-to-Voice)
    Aliases for backward compatibility: TimeoutSharedVoicemailTextToSpeechPrompt, TimeoutSharedVoicemailAudioFile
  .PARAMETER NoAgentApplyTo
    Optional. Instructs that the setting applies to AllCalls or only NewCalls
  .PARAMETER NoAgentAction
    Optional. Action to be taken if the NoAgent is logged/opted into the Queue
    Forward requires specification of NoAgentActionTarget
    Default: Queue, Values: Queue, Disconnect, Forward, VoiceMail, SharedVoiceMail
  .PARAMETER NoAgentActionTarget
    Situational. Required only if NoAgentAction is not Queue or Disconnect
    Expected are UserPrincipalName (User, ResourceAccount), a TelURI (ExternalPstn), an Office 365 Group Name (SharedVoicemail)
  .PARAMETER NoAgentPrompt
    Optional. Prompt to play when NoAgent is logged/opted into the Queue but before NoAgent Action is taken
    String as a Path for a Recording or a Greeting (Text-to-Voice)
    Aliases for backward compatibility: NoAgentSharedVoicemailTextToSpeechPrompt, NoAgentSharedVoicemailAudioFile
  .PARAMETER SetPromptForAllTypes
    Situational. Boolean Switch. This switch will apply any provided prompt to all valid parameters
    For example: If OverflowPrompt is used as a Text-to-Voice string value and a NoAgentPrompt is provided as a Recording,
    this switch will apply the Text-to-Voice string for all Overflow*TextToSpeechPrompt parameters of the queue and
    it will apply the Recording to all NoAgent*AudioFile parameters. Replace * with one of the below.
    This affects Disconnect, RedirectPerson, RedirectVoiceApp, RedirectPhoneNumber, RedirectVoicemail & SharedVoicemail
    Prompts that are not provided will not be set (in the example above, any Timeout parameters would not be affected).
  .PARAMETER EnableTranscription
    Situational. Boolean Switch. Requires specification of LanguageId
    Enables a transcription of the Voicemail message to be sent to the Group mailbox
    Aliases for backward compatibility: EnableOverflowSharedVoicemailTranscription, EnableTimeoutSharedVoicemailTranscription
  .PARAMETER EnableSystemPromptSuppression
    Situational. Boolean Switch. Requires specification of LanguageId
    Enables a transcription of the Voicemail message to be sent to the Group mailbox
  .PARAMETER LanguageId
    Optional Language Identifier indicating the language that is used to play shared voicemail prompts.
    This parameter becomes a required parameter If either OverflowAction or TimeoutAction is set to SharedVoicemail.
  .PARAMETER ResourceAccountsForCallerId
    Optional. Resource Account to be used for allowing Agents to use its number as a Caller Id.
  .PARAMETER AuthorizedUsers
    Optional. Users allowed to change certain aspects of the Call Queue (like Greetings or AudioFiles)
    These settings are governed by the assigned CsTeamsVoiceApplicationsPolicy (assigned to the User)
  .EXAMPLE
    Set-TeamsCallQueue -Name "My Queue" -DisplayName "My new Queue Name"
 
    Changes the DisplayName of Call Queue "My Queue" to "My new Queue Name"
  .EXAMPLE
    Set-TeamsCallQueue -Name "My Queue" -OverflowThreshold 5 -TimeoutThreshold 90
 
    Changes the Call Queue "My Queue" to overflow with more than 5 Callers waiting and a timeout window of 90s
  .EXAMPLE
    Set-TeamsCallQueue -Name "My Queue" -MusicOnHoldAudioFile C:\Temp\Moh.wav -Greeting C:\Temp\WelcomeMessage.wmv
 
    Changes the Call Queue "My Queue" with custom Audio Files
  .EXAMPLE
    Set-TeamsCallQueue -Name "My Queue" -AgentAlertTime 15 -RoutingMethod Serial -AllowOptOut:$false -DistributionLists @(List1@domain.com,List2@domain.com)
 
    Changes the Call Queue "My Queue" alerting every Agent nested in Azure AD Groups List1@domain.com and List2@domain.com in sequence for 15s.
  .EXAMPLE
    Set-TeamsCallQueue -Name "My Queue" -OverflowAction Forward -OverflowActionTarget SIP@domain.com -TimeoutAction Voicemail
 
    Changes the Call Queue "My Queue" forwarding to SIP@domain.com for Overflow and to Voicemail when it times out.
    #TODO Add more examples with new switches - condense examples to do multiple things to allow for good cover - max 6/7?
  .INPUTS
    System.String
  .OUTPUTS
    System.Object or None
  .NOTES
    Audio Files, if not found will result in errors being generated.
    This deviates from the behaviour in New-TeamsCallQueue
    Greeting - Setting not changed. If set before, this will remain.
    MusicOnHoldAudioFile - Setting not changed. If set before, this will remain.
    OverflowSharedVoicemailAudioFile - Setting not changed. OverflowAction will not be changed
    TimeoutSharedVoicemailAudioFile - Setting not changed. TimeoutAction will not be changed
    .COMPONENT
    TeamsCallQueue
  .FUNCTIONALITY
    Changes a Call Queue with friendly names as input
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Teams/Set-TeamsCallQueue.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_TeamsCallQueue.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/
  #>


  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', PositionalBinding = $false)]
  [Alias('Set-TeamsCQ')]
  [OutputType([System.Void], [System.Object])]
  param(
    [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName, HelpMessage = 'Name of the Call Queue')]
    [string]$Name,

    [Parameter(HelpMessage = 'Changes the Name to this DisplayName')]
    [string]$DisplayName,

    #region Music files
    [Parameter(HelpMessage = 'Path to Audio File for Welcome Message')]
    [Alias('WelcomeMessage', 'WelcomeTextToSpeechPrompt', 'WelcomeMusicAudioFile')]
    [AllowNull()]
    [ArgumentCompleter( { '<Your Text-to-speech-string>', 'C:\Temp\' })]
    [string]$Greeting,

    [Parameter(HelpMessage = 'Path to Audio File for MusicOnHold (cannot be used with UseDefaultMusicOnHold switch!)')]
    [AllowNull()]
    [ArgumentCompleter( { 'C:\Temp\' })]
    [string]$MusicOnHoldAudioFile,

    [Parameter(HelpMessage = 'Indicates whether the default Music On Hold is used')]
    [boolean]$UseDefaultMusicOnHold,
    #endregion

    #region Routing
    [Parameter(HelpMessage = 'Method to alert Agents')]
    [Validateset('Attendant', 'Serial', 'RoundRobin', 'LongestIdle')]
    [string]$RoutingMethod = 'Attendant',

    [Parameter(HelpMessage = 'If used, Agents receive calls only when their presence state is Available')]
    [boolean]$PresenceBasedRouting,

    [Parameter(HelpMessage = 'If used, Conference mode is used to establish calls')]
    [boolean]$ConferenceMode,
    #endregion

    #region Agents
    [Parameter(HelpMessage = 'Name of one or more Distribution Lists')]
    [string[]]$DistributionLists,

    [Parameter(HelpMessage = 'UPN of one or more Users')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_) -or $script:OrbitRegexGuid.isMatch($_)) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$Users,

    [Parameter(HelpMessage = 'UPN of one Owner of the Channel')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_) -or $script:OrbitRegexGuid.isMatch($_)) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string]$ChannelOwner,

    [Parameter(HelpMessage = "Team and Channel in the format 'Team\Channel'")]
    [ValidateScript( { $_ -match '\\' })]
    [string]$TeamAndChannel,
    #endregion

    #region Agent Settings
    [Parameter(HelpMessage = 'Time an agent is alerted in seconds (15-180s)')]
    [ValidateScript( {
        If ($_ -ge 15 -and $_ -le 180) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Must be a value between 30 and 180s (3 minutes)'
        } })]
    [int16]$AgentAlertTime,

    [Parameter(HelpMessage = 'Can agents opt in or opt out from taking calls from a Call Queue (Default: TRUE)')]
    [boolean]$AllowOptOut,
    #endregion

    #region Exception Handling
    #region Overflow Params
    #Deviation from MS Default (50)
    [Parameter(HelpMessage = 'People in Queue (0-200) before action is triggered (Default: 10, Note: Microsoft default: 50)')]
    [Alias('OfThreshold', 'OfQueueLength')]
    [ValidateScript( {
        If ($_ -ge 0 -and $_ -le 200) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'OverflowThreshold: Must be a value between 0 and 200.'
        } })]
    [int16]$OverflowThreshold,

    [Parameter(HelpMessage = 'Action to be taken for Overflow')]
    [Validateset('DisconnectWithBusy', 'Forward', 'Voicemail', 'SharedVoicemail')]
    [Alias('OA')]
    [string]$OverflowAction = 'DisconnectWithBusy',

    # if OverflowAction is not DisconnectWithBusy, this is required
    [Parameter(HelpMessage = 'TEL URI or UPN that is targeted upon overflow, only valid for forwarded calls')]
    [Alias('OAT')]
    [string]$OverflowActionTarget,

    [Parameter(HelpMessage = 'Prompt for Overflow trigger. Text-to-Voice will require the LanguageId Parameter')]
    [Alias('OP', 'OverflowSharedVoicemailTextToSpeechPrompt', 'OverflowSharedVoicemailAudioFile')]
    [string]$OverflowPrompt,
    #endregion

    #region Timeout Params
    #Deviation from MS Default (1200s)
    [Parameter(HelpMessage = 'Time in seconds (0-2700s) before timeout action is triggered (Default: 30, Note: Microsoft default: 1200)')]
    [Alias('ToThreshold')]
    [ValidateScript( {
        If ($_ -ge 0 -and $_ -le 2700) { $True }else {
          throw [System.Management.Automation.ValidationMetadataException] 'TimeoutThreshold: Must be a value between 0 and 2700s, will be rounded to nearest 15s interval (0/15/30/45)'
        } })]
    [int16]$TimeoutThreshold,

    [Parameter(HelpMessage = 'Action to be taken for Timeout')]
    [Validateset('Disconnect', 'Forward', 'Voicemail', 'SharedVoicemail')]
    [Alias('TA')]
    [string]$TimeoutAction = 'Disconnect',

    # if TimeoutAction is not Disconnect, this is required
    [Parameter(HelpMessage = 'TEL URI or UPN that is targeted upon timeout, only valid for forwarded calls')]
    [Alias('TAT')]
    [string]$TimeoutActionTarget,

    [Parameter(HelpMessage = 'Text-to-Voice Message. This will require the LanguageId Parameter')]
    [Alias('TP', 'TimeoutSharedVoicemailTextToSpeechPrompt', 'TimeoutSharedVoicemailAudioFile')]
    [string]$TimeoutPrompt,
    #endregion

    #region NoAgent Params
    #Deviation from MS Default (1200s)
    [Parameter(HelpMessage = 'Applies NoAgent setting to NewCalls (default) or AllCalls')]
    [Validateset('NewCalls', 'AllCalls')]
    [string]$NoAgentApplyTo = 'NewCalls',

    [Parameter(HelpMessage = 'Action to be taken for NoAgent')]
    [Validateset('Disconnect', 'Forward', 'Voicemail', 'SharedVoicemail')]
    [Alias('NA')]
    [string]$NoAgentAction = 'Queue',

    # if NoAgentAction is not Disconnect, this is required
    [Parameter(HelpMessage = 'TEL URI or UPN that is targeted upon NoAgent, only valid for forwarded calls')]
    [Alias('NAT')]
    [string]$NoAgentActionTarget,

    [Parameter(HelpMessage = 'Text-to-Voice Message. This will require the LanguageId Parameter')]
    [Alias('NP', 'NoAgentSharedVoicemailTextToSpeechPrompt', 'NoAgentSharedVoicemailAudioFile')]
    [string]$NoAgentPrompt,

    #endregion

    [Parameter(HelpMessage = 'Applies any provided prompt to all types')]
    [switch]$SetPromptForAllTypes,

    #region Additional parameters for SharedVoiceMail
    # if OverflowAction is SharedVoicemail one of the following two have to be provided
    [Parameter(HelpMessage = 'Using this Parameter will make a Transcription of the Voicemail message available in the Mailbox')]
    [Alias('EnableOverflowSharedVoicemailTranscription', 'EnableTimeoutSharedVoicemailTranscription', 'EnableNoAgentSharedVoicemailTranscription')]
    [bool]$EnableTranscription,

    [Parameter(HelpMessage = 'Using this Parameter will make a Transcription of the Voicemail message available in the Mailbox')]
    [Alias('EnableOverflowSharedVoicemailEnableSystemPromptSuppression', 'EnableTimeoutSharedVoicemailEnableSystemPromptSuppression', 'EnableNoAgentSharedVoicemailSystemPromptSuppression')]
    [bool]$EnableSystemPromptSuppression,
    #endregion
    #endregion

    #region Other
    [Parameter(HelpMessage = 'Language Identifier from Get-CsAutoAttendantSupportedLanguage.')]
    [ValidateScript( {
        if ($_ -in $(Get-OrbitAcSbCsAutoAttendantSupportedLanguage @args) ) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a supported Langauge Id. Use Intellisense for options'
        } })]
    # [ArgumentCompleter({ Get-OrbitAcSbCsAutoAttendantSupportedLanguage @args })]
    [string]$LanguageId,

    [Parameter(HelpMessage = 'UPN of one or more Resource Accounts used for Caller Id')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_) -or $script:OrbitRegexGuid.isMatch($_)) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$ResourceAccountsForCallerId,

    [Parameter(HelpMessage = 'UPN of one or more Users')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_) -or $script:OrbitRegexGuid.isMatch($_)) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$AuthorizedUsers,

    [Parameter(HelpMessage = 'Hides Authorised Users')]
    [boolean]$HideAuthorizedUsers,
    #endregion


    [Parameter(HelpMessage = 'By default, no output is generated, PassThru will display the Object changed')]
    [switch]$PassThru
  ) #param

  begin {
    Show-OrbitFunctionStatus -Level Live
    Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)"

    # Asserting Graph Connection
    if ( -not (Test-GraphConnection) ) { throw 'Connection to Microsoft Graph not established. Please validate connection' }

    # Asserting MicrosoftTeams Connection
    if ( -not (Assert-MicrosoftTeamsConnection) ) { throw 'Connection to Microsoft Teams not established. Please validate connection' }

    # Setting Preference Variables according to Upstream settings
    if (-not $PSBoundParameters['Verbose']) { $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference') }
    if (-not $PSBoundParameters['Confirm']) { $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference') }
    if (-not $PSBoundParameters['WhatIf']) { $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference') }
    $DebugPreference = if (-not $PSBoundParameters['Debug']) { $PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference') } else { 'Continue' }
    $InformationPreference = if ( $PSBoundParameters['InformationAction']) { $PSCmdlet.SessionState.PSVariable.GetValue('InformationAction') } else { 'Continue' }

    #Initialising Counters
    $private:StepsID0, $private:StepsID1 = Get-WriteBetterProgressSteps -Code $($MyInvocation.MyCommand.Definition) -MaxId 1
    $private:ActivityID0 = $($MyInvocation.MyCommand.Name)
    [int] $private:CountID0 = [int] $private:CountID1 = 1

    #region Validating Input
    $StatusID0 = 'Validating Input'
    # Language has to be normalised as the Id is case sensitive
    $CurrentOperationID0 = 'LanguageId'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['LanguageId']) {
      $LanguageId = $($LanguageId.Split('-')[0]).ToLower() + '-' + $($LanguageId.Split('-')[1]).ToUpper()
      Write-Verbose "LanguageId '$LanguageId' normalised to '$LanguageId'"
      $VRSupported = ((Get-CsAutoAttendantSupportedLanguage -Id $LanguageId).VoiceResponseSupported)
      Write-Information "INFO: LanguageId '$LanguageId' - Voice Responses are $(if ( $VRSupported ) { 'not' }) supported"
    }
    # if not provided, will be validated after querying the Queue

    # Mutual exclusivity of Channel and Users/Groups
    $CurrentOperationID0 = 'Checking for mutual exclusivity of Channel and Users/Groups'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['TeamAndChannel'] -and ($PSBoundParameters['Users'] -or $PSBoundParameters['DistributionLists'])) {
      Write-Warning "Parameter 'TeamAndChannel' cannot be combined with Users. It will be ignored!"
      [void]$PSBoundParameters.Remove('TeamAndChannel')

      if ($PSBoundParameters['DistributionLists']) {
        Write-Information 'INFO: DistributionList is parsed directly from provided channel. This value will be omitted.'
        [void]$PSBoundParameters.Remove('DistributionLists')
      }

      if ($PSBoundParameters['ChannelOwner']) {
        Write-Information 'INFO: ChannelOwner must be an owner in the Channel'
      }
    }
    #endregion

    #region Warning for setting prompts via PowerShell (as they are not yet present via Admin Center)
    if ( $PSBoundParameters['OverflowPrompt'] -or $PSBoundParameters['TimeoutPrompt'] -or `
        $PSBoundParameters['NoAgentPrompt'] -or $PSBoundParameters['SetPromptForAllTypes'] ) {
      Write-Warning -Message "The configuration parameters for all Prompts are currently only available in PowerShell `
      and do not appear in Teams admin center. Saving a call queue configuration through Teams admin center will remove `
      any of these configured items"

    }
    #endregion

    #region Workaround for NoAgent parameters not yet present
    $ParametersToCheck = @('HideAuthorizedUsers', 'NoAgentApplyTo', 'NoAgentPrompt', 'NoAgentAction', 'NoAgentActionTarget', 'SetPromptForAllTypes')
    $CommandToCheck = Get-Command Set-CsCallQueue
    $ParametersToCheck | ForEach-Object {
      $NoActionParametersAvailable = $CommandToCheck.Parameters[$_]
      if ( $PSBoundParameters["$_"] -and -not $NoActionParametersAvailable ) {
        Write-Warning -Message "The provided parameter '$_' is not yet live for this version of MicrosoftTeams. `
        If they have been introduced recently, updating the Module MicrosoftTeams may make them available.`
        If they are not yet available, please set via the Admin Center instead."

        [void]$PSBoundParameters.Remove($_)
        Write-Verbose -Message "Parameter '$_' is removed and not processed, but CmdLet will continue to run"
      }
    }
    #endregion

  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"

    # re-Initialising counters for Progress bars (for Pipeline processing)
    [int] $private:CountID0 = 2

    #region PREPARATION
    $StatusID0 = 'Querying Object'
    # preparing Splatting Object
    $Parameters = @{}

    #region Query Unique Element
    $CurrentOperationID0 = "Finding unique result for provided Name '$Name'"
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ( $script:OrbitRegexGuid.isMatch($Name) ) {
      #Identity or ObjectId
      Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand) - ID - '$Name'"
      $CallQueue = Get-CsCallQueue -Identity "$Name" -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
    }
    else {
      #Name
      Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand) - Name - '$Name'"
      # Initial Query to determine unique result (single object)
      $CallQueue = Get-CsCallQueue -NameFilter "$Name" -WarningAction SilentlyContinue
      $CallQueue = $CallQueue | Where-Object Name -EQ "$Name"
    }

    if ($null -eq $CallQueue) {
      $Message = "'$Name' No Object found - Please check 'Name' provided"
      Write-Error -Message $Message -Category ParserError -RecommendedAction "Please check 'Name' provided" -ErrorAction Stop
    }
    elseif ($CallQueue.GetType().BaseType.Name -eq 'Array') {
      $Message = "'$Name' Multiple Results found! Cannot determine unique result. - Please use Set-CsCallQueue with the -Identity switch!"
      Write-Error -Message $Message -Category ParserError -RecommendedAction 'Please use Set-CsCallQueue with the -Identity switch!' -ErrorAction Stop
    }
    else {
      $ID = $CallQueue.Identity
      Write-Information "INFO: Call Queue '$Name' Call Queue found: Identity: $ID"
      $Parameters.Identity = $ID
    }
    #endregion

    #region Additional Requirements
    $StatusID0 = "'$($CallQueue.Name)' - Processing additional requirements"
    # For SET, this must be set to the current value, for NEW it will be $null. The Processing scripts rely on this.
    $Message = "'$Name' Language"
    $AudioFileMatch = '.(wav|wma|mp3)$'
    if ( $LanguageId ) {
      $Parameters.LanguageId = $LanguageId
      Write-Verbose -Message "$Message`: Language is provided - '$LanguageId' is used" -Verbose
    }
    elseif ( $CallQueue.LanguageId ) {
      $Parameters.LanguageId = $CallQueue.LanguageId
      Write-Verbose -Message "$Message`: Language already set - '$($CallQueue.LanguageId)' is used" -Verbose
    }
    else {
      # Language not provided and not set - Checking for Parameters which would require LanguageId
      if (( $PSBoundParameters['EnableTranscription'] -and $EnableTranscription ) -or `
        ( $PSBoundParameters['OverflowPrompt'] -and $OverflowPrompt -notmatch $AudioFileMatch ) -or `
        ( $PSBoundParameters['TimeoutPrompt'] -and $TimeoutPrompt -notmatch $AudioFileMatch ) -or `
        ( $PSBoundParameters['NoAgentPrompt'] -and $NoAgentPrompt -notmatch $AudioFileMatch )) {

        Write-Error -Message "$Message`: Prompt or Transcription require a Language set selection. Please provide Parameter LanguageId" -ErrorAction Stop -RecommendedAction 'Add Parameter LanguageId'
        return
      }
    }
    #endregion

    $StatusID0 = "'$($CallQueue.Name)' - Processing Name, Audio Files & Queue Options"
    #region DisplayName
    $CurrentOperationID0 = 'DisplayName'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    # Normalising $DisplayName
    if ($PSBoundParameters['DisplayName']) {
      $Name = Format-StringForUse -InputString "$DisplayName" -As DisplayName
      Write-Information "INFO: Call Queue '$Name' DisplayName normalised to: '$Name'"
      $Parameters.Name = "$Name"
    }
    #endregion

    #region Music On Hold
    $CurrentOperationID0 = 'Music On Hold Audio File'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['MusicOnHoldAudioFile'] -and $PSBoundParameters['UseDefaultMusicOnHold']) {
      Write-Warning -Message "'$Name' MusicOnHoldAudioFile and UseDefaultMusicOnHold are mutually exclusive. UseDefaultMusicOnHold is ignored!"
      $UseDefaultMusicOnHold = $false
    }
    if ($PSBoundParameters['MusicOnHoldAudioFile']) {
      if ($null -ne $MusicOnHoldAudioFile) {
        try {
          $TeamsCallQueuePrompt = Checkpoint-TeamsCallQueuePrompt -Prompt MusicOnHold -String "$MusicOnHoldAudioFile" -ErrorAction Stop
          Write-Information "INFO: Call Queue '$Name' $($TeamsCallQueuePrompt.Prompt): Using $($TeamsCallQueuePrompt.Type): '$($TeamsCallQueuePrompt.Value)'"
          $Parameters.$($TeamsCallQueuePrompt.Parameter) = $TeamsCallQueuePrompt.Value
        }
        catch {
          Write-Error -Message "$($_.Exception.Message)"
          Write-Verbose -Message "'$Name' MusicOnHold: Using: NONE or EXISTING"
        }
        <# RETAIN - old working code for Import of MusicOnHoldAudioFile
        # File import handles file existence, format & size requirements
        $MOHFileName = Split-Path $MusicOnHoldAudioFile -Leaf
        Write-Verbose -Message "'$Name' MusicOnHoldAudioFile: Parsing: '$MOHFileName'"
        try {
          $MOHFile = Import-TeamsAudioFile -ApplicationType CallQueue -File "$MusicOnHoldAudioFile" -ErrorAction STOP
          Write-Information "INFO: Call Queue '$Name' MusicOnHoldAudioFile: Using: '$($MOHFile.FileName)'"
          $Parameters.MusicOnHoldAudioFileId = $MOHFile.Id
        }
        catch {
          Write-Error -Message "Import of MusicOnHoldAudioFile: '$MOHFileName' failed. Please check file size and compression ratio. If in doubt, provide WAV" -Category InvalidData -RecommendedAction 'Please check file size and compression ratio. If in doubt, provide WAV'
          return
        }
        #>

      }
      else {
        Write-Verbose -Message "'$Name' MusicOnHoldAudioFile: Using: DEFAULT"
      }
    }
    elseif ($UseDefaultMusicOnHold -and $PSBoundParameters['UseDefaultMusicOnHold']) {
      Write-Verbose -Message "'$Name' MusicOnHoldAudioFile: Using: DEFAULT"
      $Parameters.UseDefaultMusicOnHold = $true
    }
    else {
      Write-Verbose -Message "'$Name' MusicOnHoldAudioFile: Using: EXISTING SETTING"
    }
    #endregion

    #region Welcome Message
    $CurrentOperationID0 = 'Greeting - Welcome Message'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['Greeting']) {
      if ($Greeting -eq "$null") {
        $Parameters.WelcomeTextToSpeechPrompt = "$null"
        $Parameters.WelcomeMusicAudioFileId = "$null"
      }
      elseif ($null -ne $Greeting) {
        try {
          $TeamsCallQueuePrompt = Checkpoint-TeamsCallQueuePrompt -Prompt Greeting -String "$Greeting" -ErrorAction Stop
          Write-Information "INFO: Call Queue '$Name' $($TeamsCallQueuePrompt.Prompt): Using $($TeamsCallQueuePrompt.Type): '$($TeamsCallQueuePrompt.Value)'"
          $Parameters.$($TeamsCallQueuePrompt.Parameter) = $TeamsCallQueuePrompt.Value
        }
        catch {
          Write-Error -Message "$($_.Exception.Message)"
          Write-Verbose -Message "'$Name' Greeting: Using: NONE or EXISTING"
        }
        <# RETAIN - old working code for Import of Greeting
        if ($Greeting -match '.(wav|wma|mp3)$') {
          # Recording - File import handles file existence, format & size requirements
          $WMFileName = Split-Path $Greeting -Leaf
          Write-Verbose -Message "'$Name' Greeting: Parsing: '$WMFileName'"
          try {
            $WMFile = Import-TeamsAudioFile -ApplicationType CallQueue -File "$Greeting" -ErrorAction STOP
            Write-Information "INFO: Call Queue '$Name' Greeting: Using: '$($WMFile.FileName)'"
            $Parameters.WelcomeMusicAudioFileId = $WMFile.Id
          }
          catch {
            Write-Error -Message "Import of Greeting: '$WMFileName' failed. Please check file size and compression ratio. If in doubt, provide WAV" -Category InvalidData -RecommendedAction 'Please check file size and compression ratio. If in doubt, provide WAV'
            Write-Verbose -Message "'$Name' Greeting: Using: NONE or EXISTING"
          }
        }
        else {
          # Text-To-Voice is assumed if String does not end with a supported file extension
          if ( $Greeting.length -gt 1000 ) {
            $Greeting = $Greeting.Trim().Substring(0, 999)
            Write-Warning -Message "'$Name' Greeting: Message too long, cutting string at 1000 characters. Please validate!"
          }
          Write-Information "'$Name' Greeting: Using String as Text-to-Voice Prompt: '$Greeting'"
          $Parameters.WelcomeTextToSpeechPrompt = "$($Greeting.Trim())"
        }
        #>

      }
      else {
        Write-Verbose -Message "'$Name' Greeting: Using: NONE"
        $Parameters.WelcomeMusicAudioFileId = $null
      }
    }
    else {
      Write-Verbose -Message "'$Name' Greeting: Using: EXISTING SETTING"
    }
    #endregion

    #region PassThru Parameters
    $CurrentOperationID0 = 'PassThru Parameters - Routing metrics, Thresholds and Language'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    switch ($PSBoundParameters.Keys) {
      'RoutingMethod' { $Parameters.RoutingMethod = $RoutingMethod }
      'PresenceBasedRouting' { $Parameters.PresenceBasedRouting = $PresenceBasedRouting }
      'AllowOptOut' { $Parameters.AllowOptOut = $AllowOptOut }
      'ConferenceMode' { $Parameters.ConferenceMode = $ConferenceMod }
      'AgentAlertTime' { $Parameters.AgentAlertTime = $AgentAlertTime }
      # 'LanguageId' { $Parameters.LanguageId = $LanguageId } # LanguageId is not set here as it is determined above
      # Also adding threshold parameters here as they are just passed through
      'OverflowThreshold' { $Parameters.OverflowThreshold = $OverflowThreshold }
      'TimeoutThreshold' { $Parameters.TimeoutThreshold = $TimeoutThreshold }
    }
    #endregion


    #region Exception Handling
    $CurrentOperationID0 = 'Exception Handling'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    $Stages = @('Overflow', 'Timeout', 'NoAgent')
    $Stages | ForEach-Object {
      $DefaultAction = switch ( $_ ) {
        'Overflow' { 'DisconnectWithBusy' }
        'Timeout' { 'Disconnect' }
        'NoAgent' { 'Queue' }
      }
      $NewTeamsCallQueueTriggerParams = @{
        Name    = $Name
        Trigger = $_
        Action  = if ( $PSBoundParameters["$_`Action"] ) { (Get-Variable "$_`Action").Value } else { $CallQueue["$_`Action"] }
      }
      #NOTE: For NEW, Action is set to default if not provided, for SET it is set to the current value if not provided
      switch ( $PSBoundParameters ) {
        "$_`Prompt" { $NewTeamsCallQueueTriggerParams.Prompt = (Get-Variable "$_`Prompt").Value }
        "$_`ActionTarget" { $NewTeamsCallQueueTriggerParams.Target = (Get-Variable "$_`Target").Value }
        'LanguageId' { $NewTeamsCallQueueTriggerParams.LanguageId = $Language }
        'EnableTranscription' { $NewTeamsCallQueueTriggerParams.EnableTranscription = $EnableTranscription }
        'EnableSystemPromptSuppression' { $NewTeamsCallQueueTriggerParams.EnableSystemPromptSuppression = $EnableSystemPromptSuppression }
        'SetPromptForAllTypes' { $NewTeamsCallQueueTriggerParams.SetPromptForAllTypes = $SetPromptForAllTypes }
      }

      $TeamsCallQueueTrigger = New-TeamsCallQueueTrigger @NewTeamsCallQueueTriggerParams
      $TeamsCallQueueTrigger | ForEach-Object {
        Write-Verbose -Message "'$Name' $($_.Key): $($_.Value)"
        $Parameters.Add($($_.key = $_.Value))
      }
    }

    <# Remove if successful
    #region Overflow
    $StatusID0 = "'$($CallQueue.Name)' - Processing Overflow Parameters"
    #region OverflowAction
    $CurrentOperationID0 = 'OverflowAction'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['OverflowAction']) {
      Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction' Parsing requirements"
      if ($PSBoundParameters['OverflowActionTarget']) {
        # We have a Target
        if ($OverflowAction -eq 'DisconnectWithBusy') {
          #but we don't need one
          Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction' does not require an OverflowActionTarget. It will not be processed" -Verbose
          # Remove OverflowActionTarget if set
          [void]$PSBoundParameters.Remove('OverflowActionTarget')
        }
        else {
          # OK
          Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction' and OverflowActionTarget '$OverflowActionTarget' specified. Processing both."
        }
      }
      elseif ($OverflowAction -ne 'DisconnectWithBusy') {
        Write-Warning -Message "'$Name' OverflowAction '$OverflowAction' not set! Parameter OverflowActionTarget missing"
      }
      elseif ($OverflowAction -eq 'DisconnectWithBusy') {
        if ($PSBoundParameters['OverflowActionTarget']) {
          Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget will be removed." -Verbose
          # Remove OverflowActionTarget if set
          [void]$PSBoundParameters.Remove('OverflowActionTarget')
        }
      }
      # NEW: Adding Action only with a Target | SET: Adding Action if specified
      $Parameters.OverflowAction = $OverflowAction
    }
    else {
      $OverflowAction = $CallQueue.OverflowAction
      Write-Verbose -Message "'$Name' Parameter OverflowAction not present. Using existing setting: '$OverflowAction'"
    }
    #endregion
 
    #region OverflowActionTarget
    # Processing for Target is dependent on Action
    $CurrentOperationID0 = 'OverflowActionTarget'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['OverflowActionTarget']) {
      Write-Verbose -Message "'$Name' Parsing OverflowActionTarget" -Verbose
      try {
        switch ($OverflowAction) {
          'DisconnectWithBusy' {
            # Explicit setting of DisconnectWithBusy
            if (-not $PSBoundParameters['OverflowAction']) {
              Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': No Overflow-Parameters are processed" -Verbose
            }
            #else: No Action
          }
          'Forward' {
            # Forward requires an OverflowActionTarget (Tel URI, ObjectId of UPN of a User or an Application Instance to be translated to GUID)
            $Target = $OverflowActionTarget
            $CallTarget = $null
            $CallTarget = Get-TeamsCallableEntity -Identity "$Target"
            switch ( $CallTarget.ObjectType ) {
              'TelURI' {
                #Telephone Number (E.164)
                $Parameters.OverflowActionTarget = $CallTarget.Identity
              }
              'User' {
                try {
                  $Assertion = $null
                  $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
                  if ($Assertion) {
                    $Parameters.OverflowActionTarget = $CallTarget.Identity
                  }
                  else {
                    Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' not asserted"
                  }
                }
                catch {
                  Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' Error: $($_.Exception.Message)"
                }
              }
              'ResourceAccount' {
                try {
                  $Assertion = $null
                  $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -WarningAction SilentlyContinue -ErrorAction Stop
                  if ($Assertion) {
                    $Parameters.OverflowActionTarget = $CallTarget.Identity
                  }
                  else {
                    Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' not asserted"
                  }
                }
                catch {
                  Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' Error: $($_.Exception.Message)"
                }
              }
              default {
                # Capturing any other specified Target that does not match for the Forward
                Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' is incompatible and is not processed!"
                Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget expected is: Tel URI or a UPN of a User or Resource Account" -Verbose
              }
            }
          }
          'VoiceMail' {
            # VoiceMail requires an OverflowActionTarget (UPN of a User to be translated to GUID)
            $Target = $OverflowActionTarget
            $CallTarget = Get-TeamsCallableEntity -Identity "$Target"
            if ($CallTarget.ObjectType -eq 'User') {
              try {
                $Assertion = $null
                $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
                if ($Assertion) {
                  $Parameters.OverflowActionTarget = $CallTarget.Identity
                }
                else {
                  Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' not asserted"
                }
              }
              catch {
                Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' Error: $($_.Exception.Message)"
              }
            }
            else {
              Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' is incompatible and is not processed!"
              Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget expected is: UPN of a User" -Verbose
            }
          }
          'SharedVoiceMail' {
            # SharedVoiceMail requires an OverflowActionTarget (UPN of a Group to be translated to GUID)
            # For NEW, we process all under the condition that OverflowAction is set.
            # For SET, we process them separately, but dependent on the current status of OverflowAction or provided parameter
            # Affected parameters: OverflowActionTarget, OverflowSharedVoicemailAudioFile, OverflowSharedVoicemailTextToSpeechPrompt & EnableOverflowSharedVoicemailTranscription
 
            #region OverflowAction SharedVoicemail - Processing OverflowActionTarget
            if ($PSBoundParameters['OverflowActionTarget']) {
              Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' - Querying Object"
              $CallTarget = $null
              $CallTarget = Get-TeamsCallableEntity -Identity "$OverflowActionTarget"
              switch ( $CallTarget.ObjectType ) {
                'Group' {
                  $OverflowActionTargetId = $CallTarget.Identity
                  Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' - Object found!"
                  $Parameters.OverflowActionTarget = $OverflowActionTargetId
                }
                'Unknown' {
                  Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' not set! Error enumerating Target"
                }
                default {
                  Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' not a Group!"
                }
              }
            }
            #endregion
          }
        }
      }
      catch {
        Write-Warning -Message "'$Name' OverflowAction '$OverflowAction': OverflowActionTarget '$OverflowActionTarget' not set! Error enumerating Target: $($_.Exception.Message)"
      }
    }
    else {
      # Verifying whether OverflowAction DisconnectWithBusy is used to blank the Target
      if ($OverflowAction -eq 'DisconnectWithBusy') {
        # Remove OverflowActionTarget if set
        [void]$PSBoundParameters.Remove('OverflowActionTarget')
      }
    }
    #endregion
 
    #region OverflowAction SharedVoicemail - Processing Parameters
    #TEST SET required processing of SharedVoicemail parameters (except Target) here
    #region SharedVoiceMail prerequisites
    if ($PSBoundParameters['OverflowSharedVoicemailAudioFile'] -and $PSBoundParameters['OverflowSharedVoicemailTextToSpeechPrompt']) {
      # Both Parameters provided
      Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowSharedVoicemailAudioFile and OverflowSharedVoicemailTextToSpeechPrompt are mutually exclusive. Processing File only" -Verbose
      [void]$PSBoundParameters.Remove('OverflowSharedVoicemailTextToSpeechPrompt')
    }
    elseif ($PSBoundParameters['OverflowSharedVoicemailTextToSpeechPrompt']) {
      if (($null -eq $CallQueue.LanguageId) -and (-not $PSBoundParameters['LanguageId'])) {
        Write-Error -Message "'$Name' OverflowAction '$OverflowAction': OverflowSharedVoicemailTextToSpeechPrompt requires Language selection. Please provide Parameter LanguageId" -ErrorAction Stop -RecommendedAction 'Add Parameter LanguageId'
        return
      }
      elseif ($PSBoundParameters['LanguageId']) {
        Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowSharedVoicemailTextToSpeechPrompt: Language '$Language' is used" -Verbose
      }
      else {
        Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': OverflowSharedVoicemailTextToSpeechPrompt: Language '$($CallQueue.LanguageId)' is already set"
      }
    }
    elseif ($PSBoundParameters['OverflowSharedVoicemailAudioFile']) {
      # Asserting provided Audio File
      if ( -not (Assert-TeamsAudioFile "$OverflowSharedVoicemailAudioFile")) {
        Write-Warning -Message 'OverflowSharedVoicemailAudioFile: AudioFile could not be imported. Please validate!'
        [void]$Parameters.Remove('OverflowSharedVoicemailAudioFile')
      }
    }
    #endregion
 
    #region OverflowAction SharedVoicemail - Processing OverflowSharedVoicemailAudioFile
    if ($PSBoundParameters['OverflowSharedVoicemailAudioFile']) {
      if ($OverflowAction -eq 'SharedVoicemail' -or $CallQueue.OverflowAction -eq 'SharedVoicemail') {
        $OfSVmFileName = Split-Path $OverflowSharedVoicemailAudioFile -Leaf
        Write-Verbose -Message "'$Name' OverflowSharedVoicemailAudioFile: Parsing: '$OfSVmFileName'"
        try {
          $OfSVmFile = Import-TeamsAudioFile -ApplicationType CallQueue -File "$OverflowSharedVoicemailAudioFile" -ErrorAction STOP
          Write-Information "INFO: Call Queue '$Name' OverflowSharedVoicemailAudioFile: Using: '$($OfSVmFile.FileName)'"
          $Parameters.OverflowSharedVoicemailAudioFilePrompt = $OfSVmFile.Id
        }
        catch {
          Write-Error -Message "Import of OverflowSharedVoicemailAudioFile: '$OfSVmFileName' failed." -Category InvalidData -RecommendedAction 'Please check file size and compression ratio. If in doubt, provide WAV'
        }
      }
      else {
        Write-Verbose -Message "'$Name' OverflowSharedVoicemailAudioFile: Not processing Parameter as it is not valid for selected or current OverflowAction" -Verbose
      }
    }
    #endregion
 
    #region OverflowAction SharedVoicemail - Processing OverflowSharedVoicemailTextToSpeechPrompt
    if ($PSBoundParameters['OverflowSharedVoicemailTextToSpeechPrompt']) {
      if ($OverflowAction -eq 'SharedVoicemail' -or $CallQueue.OverflowAction -eq 'SharedVoicemail') {
        if ( $OverflowSharedVoicemailTextToSpeechPrompt.length -gt 1000 ) {
          $OverflowSharedVoicemailTextToSpeechPrompt = $OverflowSharedVoicemailTextToSpeechPrompt.Trim().Substring(0, 999)
          Write-Warning -Message 'OverflowSharedVoicemailTextToSpeechPrompt: Message too long, cutting string at 1000 characters. Please validate!'
        }
        $Parameters.OverflowSharedVoicemailTextToSpeechPrompt = "$OverflowSharedVoicemailTextToSpeechPrompt"
      }
      else {
        Write-Verbose -Message "'$Name' OverflowSharedVoicemailTextToSpeechPrompt: Not processing Parameter as it is not valid for selected or current OverflowAction" -Verbose
      }
    }
    #endregion
 
    #region OverflowAction SharedVoicemail - Processing EnableOverflowSharedVoicemailTranscription
    if ($PSBoundParameters['EnableOverflowSharedVoicemailTranscription']) {
      if ($OverflowAction -eq 'SharedVoicemail' -or $CallQueue.OverflowAction -eq 'SharedVoicemail') {
        $Parameters.EnableOverflowSharedVoicemailTranscription = $EnableOverflowSharedVoicemailTranscription
      }
      else {
        Write-Verbose -Message "'$Name' EnableOverflowSharedVoicemailTranscription: Not processing Parameter as it is not valid for selected or current OverflowAction" -Verbose
      }
    }
    #endregion
    #endregion
 
    #region OverflowAction Parameter cleanup
    if ($Parameters.OverflowActionTarget -eq '') {
      [void]$Parameters.Remove('OverflowActionTarget')
    }
    if ($Parameters['OverflowAction'] -and (-not $Parameters['OverflowActionTarget']) -and ($OverflowAction -ne 'DisconnectWithBusy')) {
      Write-Verbose -Message "'$Name' OverflowAction '$OverflowAction': Action not set as OverflowActionTarget was not correctly enumerated" -Verbose
      [void]$Parameters.Remove('OverflowAction')
    }
    else {
      if ($Parameters['OverflowAction']) {
        Write-Information "INFO: Call Queue '$Name' OverflowAction used: '$OverflowAction'"
      }
    }
    # For NEW: We remove all SharedVoicemail Parameters if no Target is present
    # For SET: Parameters may be applied individually (no removal of SharedVoicemail parameters)
    if ( $Parameters.OverflowActionTarget) {
      Write-Information "INFO: Call Queue '$Name' OverflowActionTarget: '$OverflowActionTarget'"
    }
    #endregion
    #endregion
 
    #region Timeout
    $StatusID0 = "'$($CallQueue.Name)' - Processing Timeout Parameters"
    #region TimeoutAction
    $CurrentOperationID0 = 'TimeoutAction'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['TimeoutAction']) {
      Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction' Parsing requirements"
      if ($PSBoundParameters['TimeoutActionTarget']) {
        # We have a Target
        if ($TimeoutAction -eq 'Disconnect') {
          #but we don't need one
          Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction' does not require an TimeoutActionTarget. It will not be processed" -Verbose
          # Remove TimeoutActionTarget if set
          [void]$PSBoundParameters.Remove('TimeoutActionTarget')
        }
        else {
          # OK
          Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction' and TimeoutActionTarget '$TimeoutActionTarget' specified. Processing both."
        }
      }
      elseif ($TimeoutAction -ne 'Disconnect') {
        Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction' not set! Parameter TimeoutActionTarget missing"
      }
      elseif ($TimeoutAction -eq 'Disconnect') {
        if ($PSBoundParameters['TimeoutActionTarget']) {
          Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget will be removed." -Verbose
          # Remove TimeoutActionTarget if set
          [void]$PSBoundParameters.Remove('TimeoutActionTarget')
        }
      }
      # NEW: Adding Action only with a Target | SET: Adding Action if specified
      $Parameters.TimeoutAction = $TimeoutAction
    }
    else {
      $TimeoutAction = $CallQueue.TimeoutAction
      Write-Verbose -Message "'$Name' Parameter TimeoutAction not present. Using existing setting: '$TimeoutAction'"
    }
    #endregion
 
    #region TimeoutActionTarget
    # Processing for Target is dependent on Action
    $CurrentOperationID0 = 'TimeoutActionTarget'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['TimeoutActionTarget']) {
      Write-Verbose -Message "'$Name' Parsing TimeoutActionTarget" -Verbose
      try {
        switch ($TimeoutAction) {
          'Disconnect' {
            # Explicit setting of DisconnectWithBusy
            if (-not $PSBoundParameters['TimeoutAction']) {
              Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': No Timeout-Parameters are processed" -Verbose
            }
            #else: No Action
          }
          'Forward' {
            # Forward requires an TimeoutActionTarget (Tel URI, ObjectId of UPN of a User or an Application Instance to be translated to GUID)
            $Target = $TimeoutActionTarget
            $CallTarget = Get-TeamsCallableEntity -Identity "$Target"
            switch ( $CallTarget.ObjectType ) {
              'TelURI' {
                #Telephone Number (E.164)
                $Parameters.TimeoutActionTarget = $CallTarget.Identity
              }
              'User' {
                try {
                  $Assertion = $null
                  $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
                  if ($Assertion) {
                    $Parameters.TimeoutActionTarget = $CallTarget.Identity
                  }
                  else {
                    Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' not asserted"
                  }
                }
                catch {
                  Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' Error: $($_.Exception.Message)"
                }
              }
              'ResourceAccount' {
                try {
                  $Assertion = $null
                  $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -InformationAction SilentlyContinue -WarningAction SilentlyContinue -ErrorAction Stop
                  if ($Assertion) {
                    $Parameters.TimeoutActionTarget = $CallTarget.Identity
                  }
                  else {
                    Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' not asserted"
                  }
                }
                catch {
                  Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' Error: $($_.Exception.Message)"
                }
              }
              default {
                # Capturing any other specified Target that does not match for the Forward
                Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' is incompatible and is not processed!"
                Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget expected is: Tel URI or a UPN of a User or Resource Account" -Verbose
              }
            }
          }
          'VoiceMail' {
            # VoiceMail requires an TimeoutActionTarget (UPN of a User to be translated to GUID)
            $Target = $TimeoutActionTarget
            $CallTarget = Get-TeamsCallableEntity -Identity "$Target"
            if ($CallTarget.ObjectType -eq 'User') {
              try {
                $Assertion = $null
                $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
                if ($Assertion) {
                  $Parameters.TimeoutActionTarget = $CallTarget.Identity
                }
                else {
                  Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' not asserted"
                }
              }
              catch {
                Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' Error: $($_.Exception.Message)"
              }
            }
            else {
              Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' is incompatible and is not processed!"
              Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget expected is: UPN of a User" -Verbose
            }
          }
          'SharedVoiceMail' {
            # SharedVoiceMail requires an TimeoutActionTarget (UPN of a Group to be translated to GUID)
            # For NEW, we process all under the condition that TimeOutAction is set.
            # For SET, we process them separately, but dependent on the current status of TimeOutAction or provided parameter
            # Affected parameters: TimeoutActionTarget, TimeoutSharedVoicemailAudioFile, TimeoutSharedVoicemailTextToSpeechPrompt & EnableTimeoutSharedVoicemailTranscription
 
            #region TimeoutAction SharedVoicemail - Processing TimeoutActionTarget
            if ($PSBoundParameters['TimeoutActionTarget']) {
              Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' - Querying Object"
              $CallTarget = $null
              $CallTarget = Get-TeamsCallableEntity -Identity "$TimeoutActionTarget"
              switch ( $CallTarget.ObjectType ) {
                'Group' {
                  $TimeoutActionTargetId = $CallTarget.Identity
                  Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' - Object found!"
                  $Parameters.TimeoutActionTarget = $TimeoutActionTargetId
                }
                'Unknown' {
                  Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' not set! Error enumerating Target"
                }
                default {
                  Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' not a Group!"
                }
              }
            }
            #endregion
 
          }
        }
      }
      catch {
        Write-Warning -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutActionTarget '$TimeoutActionTarget' not set! Error enumerating Target: $($_.Exception.Message)"
      }
    }
    else {
      # Verifying whether OverflowAction DisconnectWithBusy is used to blank the Target
      if ($TimeoutAction -eq 'Disconnect') {
        # Remove TimeoutActionTarget if set
        [void]$PSBoundParameters.Remove('TimeoutActionTarget')
      }
    }
    #endregion
 
    #region TimeoutAction SharedVoicemail - Processing Parameters
    #TEST SET required processing of SharedVoicemail parameters (except Target) here
    #region SharedVoiceMail prerequisites
    if ($PSBoundParameters['TimeoutSharedVoicemailAudioFile'] -and $PSBoundParameters['TimeoutSharedVoicemailTextToSpeechPrompt']) {
      # Both Parameters provided
      Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutSharedVoicemailAudioFile and TimeoutSharedVoicemailTextToSpeechPrompt are mutually exclusive. Processing File only" -Verbose
      [void]$PSBoundParameters.Remove('TimeoutSharedVoicemailTextToSpeechPrompt')
    }
    elseif ($PSBoundParameters['TimeoutSharedVoicemailTextToSpeechPrompt']) {
      if (($null -eq $CallQueue.LanguageId) -and (-not $PSBoundParameters['LanguageId'])) {
        Write-Error -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutSharedVoicemailTextToSpeechPrompt requires Language selection. Please provide Parameter LanguageId" -ErrorAction Stop -RecommendedAction 'Add Parameter LanguageId'
        return
      }
      elseif ($PSBoundParameters['LanguageId']) {
        Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutSharedVoicemailTextToSpeechPrompt: Language '$Language' is used" -Verbose
      }
      else {
        Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': TimeoutSharedVoicemailTextToSpeechPrompt: Language '$($CallQueue.LanguageId)' is already set" -Verbose
      }
    }
    elseif ($PSBoundParameters['TimeoutSharedVoicemailAudioFile']) {
      # Asserting provided Audio File
      if ( -not (Assert-TeamsAudioFile "$TimeoutSharedVoicemailAudioFile")) {
        Write-Warning -Message 'TimeoutSharedVoicemailAudioFile: AudioFile could not be imported. Please validate!'
        [void]$Parameters.Remove('TimeoutSharedVoicemailAudioFile')
      }
    }
    #endregion
 
    #region TimeoutAction SharedVoicemail - Processing TimeoutSharedVoicemailAudioFile
    if ($PSBoundParameters['TimeoutSharedVoicemailAudioFile']) {
      if ($TimeoutAction -eq 'SharedVoicemail' -or $CallQueue.TimeoutAction -eq 'SharedVoicemail') {
        $ToSVmFileName = Split-Path $TimeoutSharedVoicemailAudioFile -Leaf
        Write-Verbose -Message "'$Name' TimeoutSharedVoicemailAudioFile: Parsing: '$ToSVmFileName'"
        try {
          $ToSVmFile = Import-TeamsAudioFile -ApplicationType CallQueue -File "$TimeoutSharedVoicemailAudioFile" -ErrorAction STOP
          Write-Information "INFO: Call Queue '$Name' TimeoutSharedVoicemailAudioFile: Using: '$($ToSVmFile.FileName)'"
          $Parameters.TimeoutSharedVoicemailAudioFilePrompt = $ToSVmFile.Id
        }
        catch {
          Write-Error -Message "Import of TimeoutSharedVoicemailAudioFile: '$ToSVmFileName' failed." -Category InvalidData -RecommendedAction 'Please check file size and compression ratio. If in doubt, provide WAV'
          return
        }
      }
      else {
        Write-Verbose -Message "'$Name' TimeoutSharedVoicemailAudioFile: Not processing Parameter as it is not valid for selected or current TimeoutAction" -Verbose
      }
    }
    #endregion
 
    #region TimeoutAction SharedVoicemail - Processing TimeoutSharedVoicemailTextToSpeechPrompt
    if ($PSBoundParameters['TimeoutSharedVoicemailTextToSpeechPrompt']) {
      if ($TimeoutAction -eq 'SharedVoicemail' -or $CallQueue.TimeoutAction -eq 'SharedVoicemail') {
        if ( $TimeoutSharedVoicemailTextToSpeechPrompt.length -gt 1000 ) {
          $TimeoutSharedVoicemailTextToSpeechPrompt = $TimeoutSharedVoicemailTextToSpeechPrompt.Trim().Substring(0, 999)
          Write-Warning -Message 'TimeoutSharedVoicemailTextToSpeechPrompt: Message too long, cutting string at 1000 characters. Please validate!'
        }
        $Parameters.TimeoutSharedVoicemailTextToSpeechPrompt = "$TimeoutSharedVoicemailTextToSpeechPrompt"
      }
      else {
        Write-Verbose -Message "'$Name' TimeoutSharedVoicemailTextToSpeechPrompt: Not processing Parameter as it is not valid for selected or current TimeoutAction" -Verbose
      }
    }
    #endregion
 
    #region TimeoutAction SharedVoicemail - Processing EnableTimeoutSharedVoicemailTranscription
    if ($PSBoundParameters['EnableTimeoutSharedVoicemailTranscription']) {
      if ($TimeoutAction -eq 'SharedVoicemail' -or $CallQueue.TimeoutAction -eq 'SharedVoicemail') {
        $Parameters.EnableTimeoutSharedVoicemailTranscription = $EnableTimeoutSharedVoicemailTranscription
      }
      else {
        Write-Verbose -Message "'$Name' TimeoutSharedVoicemailAudioFile: Not processing Parameter as it is not valid for TimeoutAction '$TimeoutAction'" -Verbose
      }
    }
    #endregion
    #endregion
 
    #region TimeoutAction Parameter cleanup
    if ($Parameters.TimeoutActionTarget -eq '') {
      [void]$Parameters.Remove('TimeoutActionTarget')
    }
    if ($Parameters['TimeoutAction'] -and (-not $Parameters['TimeoutActionTarget']) -and ($TimeoutAction -ne 'Disconnect')) {
      Write-Verbose -Message "'$Name' TimeoutAction '$TimeoutAction': Action not set as TimeoutActionTarget was not correctly enumerated" -Verbose
      [void]$Parameters.Remove('TimeoutAction')
    }
    else {
      if ($Parameters['TimeoutAction']) {
        Write-Information "INFO: Call Queue '$Name' TimeoutAction: '$TimeoutAction'"
      }
    }
    # For NEW: We remove all SharedVoicemail Parameters if no Target is present
    # For SET: Parameters may be applied individually (no removal of SharedVoicemail parameters)
    if ($Parameters.TimeoutActionTarget) {
      Write-Information "INFO: Call Queue '$Name' TimeoutActionTarget: '$TimeoutActionTarget'"
    }
    #endregion
    #>

    #endregion


    #region Call Queue Agents & Channel
    $StatusID0 = "'$($CallQueue.Name)' - Call Queue Agents incl. Channel"
    #region Users - Parsing and verifying Users
    $CurrentOperationID0 = 'Parsing Users'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['Users']) {
      Write-Verbose -Message "'$Name' Parsing Users"
      if ( $null -eq $Users ) {
        Write-Warning -Message "Call Queue '$Name' Users Lists will be removed from the Queue"
        $Parameters.Users = $null
      }
      else {
        [System.Collections.Generic.List[object]]$UserIdList = @()
        foreach ($User in $Users) {
          $Assertion = $null
          $CallTarget = $null
          $CallTarget = Get-TeamsCallableEntity -Identity "$User"
          if ( $CallTarget.ObjectType -ne 'User') {
            Write-Warning -Message "'$Name' Object '$User' is not a User, omitting Object!"
            continue
          }
          try {
            # Asserting Object - Validation of Type
            $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
            if ( $Assertion ) {
              Write-Information "INFO: '$($CallTarget.Entity)' will be added to CallQueue"
              [void]$UserIdList.Add($CallTarget.Identity)
            }
            else {
              Write-Warning -Message "'$Name' Object '$User' not found or in usable state, omitting Object!"
              continue
            }
          }
          catch {
            Write-Warning -Message "'$Name' Object '$User' not in correct state or not enabled for Enterprise Voice, omitting Object!"
            Write-Debug "Exception: $($_.Exception.Message)"
            continue
          }
        }
        if ($UserIdList.Count -gt 0 -and $UserIdList -ne '') {
          # If Object is an Empty String, Count will be 1
          Write-Verbose -Message "'$Name' Users: Adding $($UserIdList.Count) Users as Agents to the Queue" -Verbose
          $Parameters.Users = if ( $UserIdList.Count -eq 1 ) { "$UserIdList" } else { @($UserIdList) }
        }
      }
    }
    #endregion

    #region Groups - Parsing Distribution Lists and their Users
    $CurrentOperationID0 = 'Parsing Distribution Lists'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['DistributionLists']) {
      Write-Verbose -Message "'$Name' Parsing Distribution Lists" -Verbose
      if ( $null -eq $DistributionLists ) {
        Write-Warning -Message "Call Queue '$Name' Distribution Lists will be removed from the Queue"
        $Parameters.DistributionLists = $null
      }
      else {
        [System.Collections.Generic.List[object]]$DLIdList = @()
        foreach ($DL in $DistributionLists) {
          $DLObject = $null
          $DLObject = Get-TeamsCallableEntity -Identity "$DL"
          if ($DLObject) {
            Write-Information "INFO: Call Queue '$Name' Group '$DL' will be added to the Call Queue"
            # Test whether Users in DL are enabled for EV and/or licensed?
            # Add to List
            [void]$DLIdList.Add($DLObject.Identity)
          }
          else {
            Write-Warning -Message "Group '$DL' not found or not unique in Graph, omitting Group!"
          }
        }
        if ($DLIdList.Count -gt 0 -and $DLIdList -ne '') {
          # If Object is an Empty String, Count will be 1
          Write-Verbose -Message "'$Name' Groups: Adding $($DLIdList.Count) Groups to the Queue" -Verbose
          $Parameters.DistributionLists = if ( $DLIdList.Count -eq 1 ) { "$DLIdList" } else { @($DLIdList) }
          Write-Information 'INFO: Group members are parsed by the subsystem and are not validated regarding Licensing or EV-Enablement'
        }
      }
    }
    #endregion

    #region ChannelOwner - Parsing and verifying ChannelOwner
    $CurrentOperationID0 = 'Parsing ChannelOwner'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    #TEST Works but can pot. apply a User that isn't even part of the channel to be the owner.
    #TODO Rework to thread in Get-TeamChannelUser
    if ($PSBoundParameters['ChannelOwner']) {
      Write-Verbose -Message "'$Name' Parsing ChannelOwner"
      if ( $null -eq $ChannelOwner ) {
        Write-Warning -Message "Call Queue '$Name' Channel Owner will be removed from the Queue"
        $Parameters.ChannelUserObjectId = $null
      }
      else {
        $Assertion = $null
        $CallTarget = $null
        $CallTarget = Get-TeamsCallableEntity -Identity "$ChannelOwner"
        if ( $CallTarget.ObjectType -ne 'User') {
          Write-Warning -Message "'$Name' Object '$ChannelOwner' is not a User, omitting Object!"
          continue
        }
        try {
          # Asserting Object - Validation of Type
          $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
          if ( $Assertion ) {
            Write-Information "INFO: '$ChannelOwner' will be added to CallQueue"
            $Parameters.ChannelUserObjectId = "$($CallTarget.Identity)"
          }
          else {
            Write-Warning -Message "'$Name' Object '$ChannelOwner' not found or in usable state, omitting Object!"
            continue
          }
        }
        catch {
          Write-Warning -Message "'$Name' Object '$ChannelOwner' not in correct state or not enabled for Enterprise Voice, omitting Object!"
          Write-Debug "Exception: $($_.Exception.Message)"
          continue
        }
      }
    }
    #endregion

    #region Channel
    $CurrentOperationID0 = 'Parsing Channel'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['TeamAndChannel']) {
      Write-Verbose -Message "'$Name' Parsing Team and Channel" -Verbose
      if ( $null -eq $TeamAndChannel ) {
        Write-Warning -Message "Call Queue '$Name' Channel will be removed from the Queue"
        $Parameters.ChannelId = $null
      }
      else {
        try {
          $TeamString, $ChannelString = $TeamAndChannel.split('`\')
          $Team, $Channel = $null
          $Team, $Channel = Get-TeamsTeamAndChannel -String "$TeamAndChannel" -ErrorAction Stop
          #$Team, $Channel = Get-TeamAndChannel -String "$TeamAndChannel" -ErrorAction Stop
          if ( -not $Team ) { throw "Team '$TeamString' could not be determined." }
          if ( -not $Channel ) { throw "Channel '$ChannelString' could not be determined." }
          Write-Verbose "Call Queue '$Name' TeamAndChannel: Determining Distribution Group" -Verbose
          $ChannelGroups = Get-MgGroup -Search "$($Team.DisplayName)"
          $ChannelGroup = $ChannelGroups | Where-Object DisplayName -EQ "$($Team.DisplayName)"
          if ( -not $ChannelGroup ) { throw 'Channel Distribution Group cannot be determined' }
          Write-Information "INFO: Call Queue '$Name' TeamAndChannel: Distribution Group '$($Team.DisplayName)' used"
          $Parameters.DistributionLists = $ChannelGroup.ObjectId

          if ( -not $PSBoundParameters['ChannelOwner'] ) {
            Write-Verbose "Call Queue '$Name' TeamAndChannel: Determining Channel Owner" -Verbose
            #TEST Works but Get-TeamChannelUser is designed to provide an Owner rather than any member
            #$ChannelOwnerObjectId = Get-GraphGroupMember -ObjectId $ChannelGroup.ObjectId | Select-Object ObjectId -First 1 -ExpandProperty ObjectId
            $ChannelOwnerObjectId = Get-TeamChannelUser -GroupId $Team.GroupId -DisplayName $Channel.DisplayName -Role Owner | Select-Object UserId -First 1 -ExpandProperty UserId
            if ( $null -eq $ChannelOwnerObjectId ) { throw "No Channel Owner discovered in channel '$ChannelString'" }
            Write-Information "INFO: Call Queue '$Name' TeamAndChannel: ChannelOwner '$ChannelOwnerObjectId' determined and used"
            $Parameters.ChannelUserObjectId = "$ChannelOwnerObjectId"
          }

          if ( -not $Parameters['ChannelUserObjectId']  ) {
            throw "Channel Owner could not be determined for Channel '$ChannelString'"
          }
          else {
            Write-Information "INFO: Call Queue '$Name' TeamAndChannel: Team '$($Team.DisplayName)' - Channel '$($Channel.DisplayName)' will be added to the Call Queue"
            $Parameters.ChannelId = $Channel.Id
          }
        }
        catch {
          Write-Warning -Message "TeamAndChannel: Error parsing Object. Target will not be added to the Call Queue. Exception: $($_.Exception.Message)"
        }
      }
    }
    #endregion
    #endregion

    #region CallerId Accounts & Managers
    $StatusID0 = "$($CallQueue.Name)' - Caller Id & Managers"
    #region ResourceAccountsForCallerId - Parsing and verifying Parsing Resource Accounts for Caller Id
    $CurrentOperationID0 = 'Parsing Resource Accounts for Caller Id'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['ResourceAccountsForCallerId']) {
      Write-Verbose -Message "'$Name' Parsing Resource Accounts for Caller Id"
      if ( $null -eq $ResourceAccountsForCallerId ) {
        Write-Warning -Message "Call Queue '$Name' Resource Accounts for CallerId will be removed from the Queue"
        $Parameters.OboResourceAccountIds = $null
      }
      else {
        [System.Collections.Generic.List[object]]$OboResourceAccountIds = @()
        foreach ($RA in $ResourceAccountsForCallerId) {
          $Assertion = $null
          $CallTarget = $null
          $CallTarget = Get-TeamsCallableEntity -Identity "$RA"
          if ( $CallTarget.ObjectType -ne 'ResourceAccount') {
            Write-Warning -Message "'$Name' Object '$RA' is not a Resource Account, omitting Object!"
            continue
          }
          try {
            # Asserting Object - Validation of Type
            $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
            if ( $Assertion ) {
              Write-Information "INFO: Call Queue '$Name' Resource Account '$RA' will be added to CallQueue"
              [void]$OboResourceAccountIds.Add($CallTarget.Identity)
            }
            else {
              Write-Warning -Message "'$Name' Object '$RA' not found or in usable state, omitting Object!"
              continue
            }
          }
          catch {
            Write-Warning -Message "'$Name' Object '$RA' not in correct state or not enabled for Enterprise Voice, omitting Object!"
            Write-Debug "Exception: $($_.Exception.Message)"
            continue
          }
        }

        if ($OboResourceAccountIds.Count -gt 0 -and $OboResourceAccountIds -ne '') {
          # If Object is an Empty String, Count will be 1
          Write-Verbose -Message "'$Name' Resource Account: Adding $($OboResourceAccountIds.Count) Resource Accounts for Caller Id to the Queue" -Verbose
          $Parameters.OboResourceAccountIds = if ( $OboResourceAccountIds.Count -eq 1 ) { "$OboResourceAccountIds" } else { @($OboResourceAccountIds) }
        }
      }
    }
    #endregion

    #region AuthorizedUsers - Parsing and verifying AuthorizedUsers & HideAuthorizedUsers
    $CurrentOperationID0 = 'Parsing AuthorizedUsers'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['AuthorizedUsers']) {
      Write-Verbose -Message "'$Name' - Parsing AuthorizedUsers"
      [System.Collections.Generic.List[object]]$AuthorizedUserIdList = @()
      if ( $null -eq $AuthorizedUsers ) {
        Write-Warning -Message "Call Queue '$Name' Authorized Users will be removed from the Queue"
        $Parameters.AuthorizedUsers = $null
      }
      else {
        foreach ($User in $AuthorizedUsers) {
          $Assertion = $null
          $CallTarget = $null
          $CallTarget = Get-TeamsCallableEntity -Identity "$User"
          if ( $CallTarget.ObjectType -ne 'User') {
            Write-Warning -Message "'$Name' Object '$User' is not a User, omitting Object!"
            continue
          }
          try {
            # Asserting Object - Validation of Type
            $Assertion = Assert-TeamsCallableEntity -UserPrincipalName "$($CallTarget.Entity)" -RequireEV -WarningAction SilentlyContinue -ErrorAction Stop
            if ( $Assertion ) {
              Write-Information "INFO: User '$User' will be added as an Authorized User"
              [void]$AuthorizedUserIdList.Add($CallTarget.Identity)
            }
            else {
              Write-Warning -Message "'$Name' Object '$User' not found or in usable state, omitting Object!"
              continue
            }
          }
          catch {
            Write-Warning -Message "'$Name' Object '$User' not in correct state or not enabled for Enterprise Voice, omitting Object!"
            Write-Debug "Exception: $($_.Exception.Message)"
            continue
          }
        }

        if ($AuthorizedUserIdList.Count -gt 0 -and $AuthorizedUserIdList -ne '') {
          # If Object is an Empty String, Count will be 1
          Write-Verbose -Message "'$Name' AuthorizedUsers: Adding $($AuthorizedUserIdList.Count) Users as Managers to the Queue" -Verbose
          $Parameters.AuthorizedUsers = if ( $AuthorizedUserIdList.Count -eq 1 ) { "$AuthorizedUserIdList" } else { @($AuthorizedUserIdList) }
        }
      }
    }

    # Processing HideAuthorizedUsers
    if ($PSBoundParameters['HideAuthorizedUsers']) {
      Write-Verbose -Message "'$Name' - Parsing HideAuthorizedUsers"
      $Parameters.HideAuthorizedUsers = $HideAuthorizedUsers
    }
    #endregion
    #endregion


    #region Common parameters
    $Parameters.WarningAction = 'SilentlyContinue'
    $Parameters.ErrorAction = 'Stop'
    #endregion
    #endregion


    #region ACTION
    $StatusID0 = 'Applying settings'
    $CurrentOperationID0 = "Changing Call Queue: '$Name'"
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') {
      " Function: $($MyInvocation.MyCommand.Name) - Parameters (Set-CsCallQueue)", ($Parameters | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
    }

    # Set the Call Queue with all Parameters provided
    if ($PSCmdlet.ShouldProcess("$Name", 'Set-CsCallQueue')) {
      $null = (Set-CsCallQueue @Parameters)
      Write-Verbose -Message "SUCCESS: '$Name' Call Queue settings applied"
    }
    #endregion


    #region OUTPUT
    # Re-query output
    $CallQueueFinal = $null
    if ( $PassThru ) {
      $CurrentOperationID0 = 'Preparing Output'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $CallQueueFinal = Get-TeamsCallQueue -Name "$Name" -WarningAction SilentlyContinue
      $CallQueueFinal = $CallQueueFinal | Where-Object Name -EQ "$Name"
    }
    Write-Progress -Id 0 -Activity $ActivityID0 -Completed
    Write-Output $CallQueueFinal
    #endregion

  } #process

  end {
    Write-Verbose -Message "[END ] $($MyInvocation.MyCommand)"

  } #end
} #Set-TeamsCallQueue