Public/AutoAttendant/Update-TeamsAutoAttendant.ps1

# Module: Orbit.Teams
# Function: AutoAttendant
# Author: David Eberhardt
# Updated: 03-SEP 2022
# Status: Live


#VALIDATE EnableTranscription and EnableSharedVoicemailSystemPromptSuppression should be processed as BOOLs

function Update-TeamsAutoAttendant {
  <#
  .SYNOPSIS
    Changing, amending or replacing the Business Hours Call Flow on existing Auto Attendants; Calling Set-CsAutoAttendant
  .DESCRIPTION
    Editing exiting Auto Attendants with Set-CsAutoAttendant requires manual creation of objects and surgical replacement.
    This script tries to simplify editing Auto Attendants by providing four functions:
    Update-TeamsAutoAttendant covers general Settings of the Auto Attendnat
    Update-TeamsAutoAttendantBusinessHours covers the Default Call Flow
    Update-TeamsAutoAttendantAfterHours covers the After Hours Call Flow
    Update-TeamsAutoAttendantHoliday covers the Holiday Call Flow
    Each individual Script can replace the full call flow with a previously created object,
    or amend some specific parts of the respective flow
  .PARAMETER Name
    Name of the Auto Attendant. Required to locate the Auto Attendant.
    Alternatively ID of the Auto Attendant for more precise location
  .PARAMETER Name
    Name of the Auto Attendant. Required to locate the Auto Attendant.
  .PARAMETER DisplayName
    Optional. Updates the Name of the Auto Attendant. Name will be normalised (unsuitable characters are filtered)
  .PARAMETER TimeZone
    Optional. TimeZone Identifier based on Get-CsAutoAttendantSupportedTimeZone, but abbreviated for easier input.
    Warning: Due to multiple time zone names with in the same relative difference to UTC this MAY produce incongruent output
    The time zone will be correct, but only specifying "UTC+01:00" for example will select the first entry.
    Default Value: "UTC"
  .PARAMETER TimeZoneId
    Optional. TimeZone Id (Name) of the Time Zone as Get-CsAutoAttendantSupportedTimeZone provides.
    This yields a more precise result as the TimeZone parameter above.
    If both are provided, TimeZone is ignored as TimeZoneId is an exact result
  .PARAMETER LanguageId
    Optional. Language Identifier indicating the language that is used to play text and identify voice prompts.
    Default Value: "en-US"
  .PARAMETER Operator
    Optional. Creates a Callable entity for the Operator
    Expected are UserPrincipalName (User, ResourceAccount), a TelURI (ExternalPstn), an Office 365 Group Name (SharedVoicemail)
  .PARAMETER InclusionScope
    Optional. DialScope Object to pass to Set-CsAutoAttendant
    Object created with New-TeamsAutoAttendantDialScope or Set-CsAutoAttendantDialScope
  .PARAMETER ExclusionScope
    Optional. DialScope Object to pass to Set-CsAutoAttendant
    Object created with New-TeamsAutoAttendantDialScope or Set-CsAutoAttendantDialScope
  .PARAMETER EnableVoiceResponse
    Optional Switch to be passed to Set-CsAutoAttendant
  .PARAMETER EnableTranscription
    Optional. Where possible, tries to enable Voicemail Transcription.
    Effective only for SharedVoicemail Targets as an Operator or MenuOption. Otherwise has no effect.
  .PARAMETER EnableSharedVoicemailSystemPromptSuppression
    Optional. Where possible, tries to suppress System Prompts.
    Effective only for SharedVoicemail Targets as an Operator or MenuOption. Otherwise has no effect.
  .PARAMETER ForceListenMenuEnabled
    Optional. Toggles ForceListenMenuEnabled on Call Flow Objects. Only has an effect for Menus with MenuOptions.
  .PARAMETER AuthorizedUsers
    Optional. Users allowed to change certain aspects of the Auto Attendant (like Greetings or AudioFiles)
    These settings are governed by the assigned CsTeamsVoiceApplicationsPolicy (assigned to the User)
  .PARAMETER VoiceId
    Optional. Gender of the Voice for VoiceResponses
    Instructs the speech interpreter to use a specific gender. Dependent on availability for the selected Language.
  .PARAMETER PassThru
    Optional. Displays Auto Attendant Object after action.
  .EXAMPLE
    Update-TeamsAutoAttendant -Name "My Auto Attendant" -DisplayName "Main Number"
 
    Updates the Auto Attendant "My Auto Attendant" Name to "Main Number"
  .EXAMPLE
    Update-TeamsAutoAttendant -Name "My Auto Attendant" -TimeZone UTC-05:00 -LanguageId pt-BR -EnableVoiceResponse
 
    Updates the Auto Attendant "My Auto Attendant" and sets the TimeZone to UTC-5 and the language to Portuguese (Brazil)
    Enables VoiceResponses if available not yet enabled.
  .EXAMPLE
    Update-TeamsAutoAttendant -Name "My Auto Attendant" -Operator "tel:+1555123456"
 
    Updates the Auto Attendant "My Auto Attendant" with an Operator as a Callable Entity (Forward to Pstn)
  .EXAMPLE
    Update-TeamsAutoAttendant -Name "My Auto Attendant" -InclusionScope $InGroups -ExclusionScope $OutGroups
 
    Updates the Auto Attendant "My Auto Attendant" and passes through all objects provided.
    The InclusionScope and ExclusionScope parameters are set with the Scope defined prior.
  .INPUTS
    System.String
  .OUTPUTS
    System.Object
  .NOTES
    InclusionScope and ExclusionScope Objects can be created with New-TeamsAutoAttendantDialScope and the Group Names
    This was deliberately not integrated into this CmdLet
  .COMPONENT
    TeamsAutoAttendant
  .FUNCTIONALITY
    Updates an Auto Attendant with custom settings and friendly names as input
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Teams/Update-TeamsAutoAttendant.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_TeamsAutoAttendant.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/
  #>


  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', PositionalBinding = $false)]
  [Alias('Update-TeamsAA')]
  [OutputType([System.Void])]
  param(
    [Parameter(Mandatory, Position = 0, ValueFromPipeline, HelpMessage = 'Name of the Auto Attendant')]
    [string]$Name,

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

    [Parameter(HelpMessage = 'TimeZone Identifier from Get-CsAutoAttendantSupportedTimeZone')]
    [ValidateScript( {
        if ($_ -in $( Get-OrbitAcSbSupportedTimeZone @args )) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a supported TimeZone. Use Intellisense for options'
        } })]
    # [ArgumentCompleter({ Get-OrbitAcSbSupportedTimeZone @args })]
    [string]$TimeZone,

    [Parameter(HelpMessage = 'TimeZone Identifier from Get-CsAutoAttendantSupportedTimeZone')]
    [ValidateScript( {
        if ("'$_'" -in $( Get-OrbitAcSbSupportedTimeZoneName @args )) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a supported TimeZone. Use Intellisense for options'
        } })]
    # [ArgumentCompleter({ Get-OrbitAcSbSupportedTimeZoneName @args })]
    [string]$TimeZoneId,

    [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 = 'Target String for the Operator (UPN, Group Name or Tel URI')]
    [string]$Operator,

    [Parameter(HelpMessage = 'Groups defining the Inclusion Scope')]
    [object]$InclusionScope,

    [Parameter(HelpMessage = 'Groups defining the Exclusion Scope')]
    [object]$ExclusionScope,

    [Parameter(HelpMessage = 'Voice Responses')]
    [boolean]$EnableVoiceResponse,

    [Parameter(HelpMessage = 'Tries to Enable Transcription wherever possible')]
    [boolean]$EnableTranscription,

    [Parameter(HelpMessage = 'Tries to Suppress System Prompts wherever possible')]
    [boolean]$EnableSharedVoicemailSystemPromptSuppression,

    [Parameter(HelpMessage = 'Enables ForceListen on CallFlow Objects')]
    [boolean]$ForceListenMenuEnabled,

    [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 = 'Gender of the Voice')]
    [ValidateSet('Female', 'Male')]
    [string]$VoiceId,

    [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)"
    Write-Verbose -Message "Need help? Online: $global:OrbitHelpURLBase$($MyInvocation.MyCommand)`.md"

    # 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

    # Preparing Splatting Object for New-TeamsCallableEntity
    $TeamsCallableEntityParams = @{ ErrorAction = 'Stop' }
    if ( $PSBoundParameters['EnableTranscription'] ) {
      $TeamsCallableEntityParams.EnableTranscription = $EnableTranscription
    }
    if ( $PSBoundParameters['EnableSharedVoicemailSystemPromptSuppression'] ) {
      $TeamsCallableEntityParams.EnableSharedVoicemailSystemPromptSuppression = $EnableSharedVoicemailSystemPromptSuppression
    }

  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    #region PREPARATION
    $ActivityID0 = 'Validation'
    $StatusID0 = 'Querying Object'
    # preparing Splatting Object
    $Parameters = $null

    #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'"
      $AAInstance = Get-CsAutoAttendant -Identity "$Name" -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
    }
    else {
      #Name
      Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand) - Name - '$Name'"
      # Initial Query to determine unique result (single object)
      $AAInstance = Get-CsAutoAttendant -NameFilter "$Name" -WarningAction SilentlyContinue
      $AAInstance = $AAInstance | Where-Object Name -EQ "$Name"
    }

    if ($null -eq $AAInstance) {
      Write-Error "'$Name' No Object found" -Category ParserError -RecommendedAction "Please check 'Name' provided" -ErrorAction Stop
    }
    elseif ($AAInstance.GetType().BaseType.Name -eq 'Array') {
      Write-Error "'$Name' Multiple Results found! Cannot determine unique result." -Category ParserError -RecommendedAction 'Please provide Auto Attendant GUID instead of name' -ErrorAction Stop
    }
    else {
      $AAID = $AAInstance.Identity
      Write-Information "INFO: '$Name' Auto Attendant found: Identity: $AAID"
      if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') {
        " Function: $($MyInvocation.MyCommand.Name) - Auto Attendant", ($AAInstance | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
      }
    }
    #endregion

    #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'] ) {
      $NameNormalised = Format-StringForUse -InputString "$DisplayName" -As DisplayName
      $AAInstance.Name = "$NameNormalised"
      Write-Information "INFO: Pending Application: DisplayName changed from '$Name' to '$NameNormalised'"
      Write-Warning -Message 'Future Calls to this Auto Attendant must be against this name (or using the Identity)'
    }
    #endregion

    # Defining Name for reference below
    $AAName = "$($AAInstance.Name)"
    #endregion


    #region SETTINGS
    #NOTE All Options in Region Settings use ID 0 for showing progress
    #region TimeZone
    $CurrentOperationID0 = 'Processing TimeZone'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ( $PSBoundParameters['TimeZone'] -or $PSBoundParameters['TimeZoneId'] ) {
      if ( $PSBoundParameters['TimeZone'] -and $PSBoundParameters['TimeZoneId'] ) {
        Write-Information "INFO: TimeZone and TimeZoneId are both provided. TimeZone is ignored: TimeZoneId '$TimeZoneId' is used as it is more precise"
        [void]$PSBoundParameters.Remove('TimeZone')
      }

      if ( $PSBoundParameters['TimeZone'] ) {
        Write-Verbose -Message "TimeZone - Parsing TimeZone '$TimeZone'"
        if ($TimeZone -eq 'UTC') {
          $TimeZoneId = $TimeZone
        }
        else {
          # Query of Supported TimeZones based on previously calculated global Variable
          if ( -not $global:CsAutoAttendantSupportedTimeZone ) { $global:CsAutoAttendantSupportedTimeZone = Get-CsAutoAttendantSupportedTimeZone -WarningAction SilentlyContinue }
          $TimeZoneId = ($global:CsAutoAttendantSupportedTimeZone | Where-Object DisplayName -Like "($TimeZone)*" | Select-Object -First 1).Id
          Write-Verbose -Message "TimeZone '$TimeZone' - Using: '$TimeZoneId' - This is a correct match for the value of the Time Zone, but might not be exact. - Please fine-tune Time Zone in the Admin Center if needed or provide TimeZoneId parameter" -Verbose
        }
      }

      # Applying TimeZoneId
      if ( $TimeZoneId ) {
        $AAInstance.TimeZoneId = $TimeZoneId
        Write-Information "INFO: Pending Application: TimeZone set to: '$TimeZoneId'"
      }
      else {
        Write-Warning -Message 'TimeZone - Error determining TimeZoneId - skipped'
      }
    }
    #endregion

    #region Langauge
    $CurrentOperationID0 = 'Processing Language'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ( $PSBoundParameters['Language'] ) {
      # Language has to be normalised as the Id is case sensitive. Default value: en-US
      $Language = $($LanguageId.Split('-')[0]).ToLower() + '-' + $($LanguageId.Split('-')[1]).ToUpper()
      Write-Verbose "LanguageId '$LanguageId' normalised to '$Language'"

      $AAInstance.LanguageId = $Language
      Write-Information "INFO: Pending Application: Language set to: '$Language'"
    }
    else {
      $Language = $AAInstance.LanguageId
    }
    #endregion

    #region Voice capabilities
    # Query of Supported Languages based on previously calculated global Variable
    if ( -not $global:CsAutoAttendantSupportedLanguage ) { $global:CsAutoAttendantSupportedLanguage = Get-CsAutoAttendantSupportedLanguage -WarningAction SilentlyContinue }
    $LanguageRecord = $global:CsAutoAttendantSupportedLanguage | Where-Object Id -EQ "$Language"

    $CurrentOperationID0 = 'Processing EnableVoiceResponse, VoiceId'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['EnableVoiceResponse'] -and $EnableVoiceResponse ) {
      $VoiceResponsesSupported = $LanguageRecord.VoiceResponseSupported

      # EnableVoiceResponse
      if ( $VoiceResponsesSupported ) {
        Write-Verbose -Message "'$AAName' EnableVoiceResponse - Voice Responses are supported with Language '$Language' and EnableVoiceResponse will be set to: $EnableVoiceResponse"
        $AAInstance.VoiceResponseEnabled = $EnableVoiceResponse
        Write-Information "INFO: Pending Application: VoiceResponseEnabled set to: '$EnableVoiceResponse'"
      }
      else {
        Write-Warning -Message "'$AAName' EnableVoiceResponse - Voice Responses are not supported for Language '$Language' and cannot be activated (Switch 'EnableVoiceResponse' will be omitted)"
      }
    }

    # VoiceId
    if ( $PSBoundParameters['VoiceId'] ) {
      $Voices = ($LanguageRecord.Voices | Where-Object Id -EQ "$VoiceId")
      if ( $Voices.Count -gt 0 ) {
        Write-Verbose -Message "'$AAName' VoiceId - Voice with gender '$VoiceId' found for '$Language'" -Verbose
        $AAInstance.VoiceId = $Voices[0]
        Write-Information "INFO: Pending Application: VoiceId set to: '$($Voices[0].Name)' (NOTE: This is experimental in v4.7.0)"
      }
      else {
        Write-Verbose -Message "'$AAName' VoiceId - No Voice with this gender found for '$Language' (using default Voice)" -Verbose
      }
    }
    #endregion

    #region Operator
    $CurrentOperationID0 = 'Operator'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSBoundParameters['Operator']) {
      try {
        $OperatorEntity = New-TeamsCallableEntity -Identity "$Operator" @TeamsCallableEntityParams
        if ($OperatorEntity) {
          $AAInstance.Operator = $OperatorEntity
          Write-Information "INFO: Pending Application: Operator set to: '$Operator'"
        }
      }
      catch {
        Write-Warning -Message 'Operator - Error creating Call Target - skipped'
      }
    }
    #endregion

    #region Inclusion and Exclusion Scope
    $CurrentOperationID0 = 'Dial Scopes - Inclusion & Exclusion Scope'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    # Inclusion Scope
    if ($PSBoundParameters['InclusionScope']) {
      Write-Verbose -Message "'$AAName' InclusionScope provided. Using as-is"
      $AAInstance.DirectoryLookupScope.InclusionScope = $InclusionScope
      Write-Information "INFO: Pending Application: InclusionScope set to Type: '$($InclusionScope.Type)'"
    }
    else {
      #Scope is optional
      Write-Verbose -Message "'$AAName' InclusionScope not defined. To create one, please run New-TeamsAutoAttendantDialScope or New-CsAutoAttendantDialScope"
    }

    # Exclusion Scope
    if ($PSBoundParameters['ExclusionScope']) {
      Write-Verbose -Message "'$AAName' ExclusionScope provided. Using as-is"
      $AAInstance.DirectoryLookupScope.ExclusionScope = $ExclusionScope
      Write-Information "INFO: Pending Application: ExclusionScope set to Type: '$($ExclusionScope.Type)'"
    }
    else {
      #Scope is optional
      Write-Verbose -Message "'$AAName' ExclusionScope not defined. To create one, please run New-TeamsAutoAttendantDialScope or New-CsAutoAttendantDialScope"
    }
    #endregion

    #region AuthorizedUsers - Parsing and verifying AuthorizedUsers
    $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 "'$AAName' - Parsing AuthorizedUsers"
      [System.Collections.Generic.List[object]]$AuthorizedUserIdList = @()
      foreach ($User in $AuthorizedUsers) {
        $Assertion = $null
        $CallTarget = $null
        $CallTarget = Get-TeamsCallableEntity -Identity "$User"
        if ( $CallTarget.ObjectType -ne 'User') {
          Write-Warning -Message "'$AAName' 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 "'$AAName' Object '$User' not found or in unusable state, omitting Object!"
            continue
          }
        }
        catch {
          Write-Warning -Message "'$AAName' 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 "'$AAName' AuthorizedUsers: Adding $($AuthorizedUserIdList.Count) Users as Managers to the Auto Attendant" -Verbose
        #TEST AuthorizedUsers may suffer from 'single object trauma'
        #$Parameters.AuthorizedUsers = if ( $AuthorizedUserIdList.Count -eq 1 ) { "$AuthorizedUserIdList" } else { @($AuthorizedUserIdList) }
        $AAInstance.AuthorizedUsers = $AuthorizedUserIdList
        Write-Information "INFO: Pending Application: AuthorizedUsers changed: $($AuthorizedUserIdList.Count) Users set"

      }
    }
    #endregion

    #region Switches
    $CurrentOperationID0 = 'Processing Switches (ForceListen, Transcription, Skip System Message)'
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    # ForceListenMenuEnabled
    if ( $PSBoundParameters['ForceListenMenuEnabled'] ) {
      $AAInstance.DefaultCallFlow.ForceListenMenuEnabled = $ForceListenMenuEnabled
      Write-Information "INFO: Pending Application: Default CallFlow changed: ForceListenMenuEnabled set to: $ForceListenMenuEnabled"

      $AAInstance.CallFlows | ForEach-Object {
        $_.ForceListenMenuEnabled = $ForceListenMenuEnabled
        Write-Information "INFO: Pending Application: CallFlow '$($_.Name)' changed: ForceListenMenuEnabled set to: $ForceListenMenuEnabled"
      }
    }

    # Shared Voicemail Transcription & Skip System Prompt
    $AAInstance.DefaultCallFlow.Menu.MenuOptions | ForEach-Object {
      if ($_.CallTarget.Type -eq 'SharedVoicemail') {
        $SharedVoiceMailCallTarget = $_.Calltarget.Id | Get-TeamsCallableEntity
        # EnableTranscription
        if ( $PSBoundParameters['EnableTranscription'] ) {
          $_.CallTarget.EnableTranscription = $EnableTranscription
          Write-Information "INFO: Pending Application: DefaultCallFlow changed: Transcription set for MenuOption $($_.DtmfResponse -replace 'Tone', ''): '$($SharedVoiceMailCallTarget.Entity)' ($EnableTranscription)"
        }
        # EnableSharedVoicemailSystemPromptSuppression
        if ( $PSBoundParameters['EnableSharedVoicemailSystemPromptSuppression'] ) {
          $_.CallTarget.EnableSharedVoicemailSystemPromptSuppression = $EnableSharedVoicemailSystemPromptSuppression
          Write-Information "INFO: Pending Application: DefaultCallFlow changed: SkipSystemMessage set for MenuOption $($_.DtmfResponse -replace 'Tone', ''): '$($SharedVoiceMailCallTarget.Entity)' ($EnableSharedVoicemailSystemPromptSuppression)"
        }
      }
    }

    $AAInstance.Callflows.Menu.MenuOptions | ForEach-Object {
      if ($_.CallTarget.Type -eq 'SharedVoicemail') {
        $SharedVoiceMailCallTarget = $_.Calltarget.Id | Get-TeamsCallableEntity
        # EnableTranscription
        $_.CallTarget.EnableTranscription = $EnableTranscription
        Write-Information "INFO: Pending Application: CallFlow changed: Transcription set for MenuOption $($_.DtmfResponse -replace 'Tone', ''): '$($SharedVoiceMailCallTarget.Entity)' ($EnableTranscription)"

        # EnableSharedVoicemailSystemPromptSuppression
        $_.CallTarget.EnableSharedVoicemailSystemPromptSuppression = $EnableSharedVoicemailSystemPromptSuppression
        Write-Information "INFO: Pending Application: CallFlow changed: SkipSystemMessage set for MenuOption $($_.DtmfResponse -replace 'Tone', ''): '$($SharedVoiceMailCallTarget.Entity)' ($EnableSharedVoicemailSystemPromptSuppression)"
      }
    }
    #endregion
    #endregion


    #region ACTION
    #Preparing Splatting Object
    $Parameters = @{
      'Instance'      = $AAInstance
      'WarningAction' = if ( $PSBoundParameters['WarningAction'] ) { $PSCmdlet.SessionState.PSVariable.GetValue('WarningAction') } else { 'Continue' }
      'ErrorAction'   = if ( $PSBoundParameters['ErrorAction'] ) { $PSCmdlet.SessionState.PSVariable.GetValue('ErrorAction') } else { 'Stop' }
    }

    Write-Verbose -Message '[PROCESS] Updating Auto Attendant'
    if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') {
      " Function: $($MyInvocation.MyCommand.Name) - Parameters (Set-CsAutoAttendant)", ($Parameters | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
    }

    # Updating Auto Attendant (Set-CsAutoAttendant)
    $StatusID0 = 'Updating Auto Attendant'
    $CurrentOperationID0 = "'$AAName'"
    Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
    if ($PSCmdlet.ShouldProcess("$AAName", 'Set-CsAutoAttendant')) {
      try {
        # Create the Auto Attendant with all enumerated Parameters passed through splatting
        $null = (Set-CsAutoAttendant @Parameters)
        #Write-Information "INFO: All pending changes applied to Auto Attendant Instance for '$AAName'"
        Write-Information "INFO: All pending changes applied to Auto Attendant Instance for '$AAName'" -ForegroundColor Magenta
      }
      catch {
        Write-Error -Message "Error updating the Auto Attendant: $($_.Exception.Message)" -Category InvalidResult
        return
      }
    }
    else {
      return
    }
    #endregion


    #region OUTPUT
    if ( $PassThru ) {
      $StatusID0 = 'Validation/PassThru'
      $CurrentOperationID0 = 'Re-Querying Object'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $AAFinal = Get-TeamsAutoAttendant -Name $AAInstance.Identity -WarningAction SilentlyContinue
      Write-Output $AAFinal
    }
    #endregion
    Write-Progress -Id 0 -Activity $ActivityID0 -Completed

  } #process

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

  } #end
} # Update-TeamsAutoAttendant