Public/AutoAttendant/Artefacts/New-TeamsAutoAttendantMenu.ps1

# Module: Orbit.Teams
# Function: AutoAttendant
# Author: David Eberhardt
# Updated: 12-DEC-2020
# Status: Live




function New-TeamsAutoAttendantMenu {
  <#
  .SYNOPSIS
    Creates a Menu Object to be used in Auto Attendants
  .DESCRIPTION
    Creates a Menu Object with Prompt and/or MenuOptions to be used in Auto Attendants
    Wrapper for New-CsAutoAttendantMenu with friendly names
    Combines New-CsAutoAttendantMenu, New-CsAutoAttendantPrompt and New-CsAutoAttendantMenuOption
  .PARAMETER Name
    Optional. Name of the Menu if desired. Otherwise generated automatically.
  .PARAMETER Action
    Required. TransferToMenu, Disconnect, TransferToCallTarget.
    Determines the type of Menu to be created.
  .PARAMETER Prompts
    Required for Action "TransferToMenu" only. A Prompts Object, String or Full path to AudioFile.
    A Prompts Object will be used as is, otherwise it will be created dependent of the provided String
    A String will be used as Text-to-Voice. A File path ending in .wav, .mp3 or .wma will be used to create a recording.
  .PARAMETER MenuOptions
    Required for Action "TransferToMenu" only. Mutually exclusive with CallTargetsInOrder.
    MenuOptions objects created with either New-TeamsAutoAttendantMenuOption or New-CsAutoAttenantMenuOption.
  .PARAMETER CallTargetsInOrder
    Required for Action "TransferToMenu" only. Mutually exclusive with MenuOptions. Call Targets for Menu Options.
    Expected UserPrincipalName (User, ResourceAccount), Group Name (Shared Voicemail), Tel Uri (ExternalPstn)
    Allows to skip options with empty strings ("") or "$null". See Examples for details
    DTMF tones are assigned in order with 0 being TransferToOperator
  .PARAMETER CallTarget
    Required for Action "TransferToCallTarget" only. Single Call Target to redirect Calls to.
    UserPrincipalName (User, ResourceAccount), Group Name (Shared Voicemail), Tel Uri (ExternalPstn)
  .PARAMETER AddOperatorOnZero
    Optional for Action "TransferToMenu" when used with CallTargetsInOrder only.
    This switch is ignored if more than nine (9) CallTargetsInOrder are specified
    Adds one more menu option to Transfer the Call to the Operator on pressing 0.
    The AutoAttendant which will receive a Menu with this option, must have an Operator defined.
    Creating or Updating an Auto Attendant with an Operator that is not defined will lead to errors.
  .PARAMETER EnableDialByName
    Boolean. Required for Action "TransferToMenu" only. Enables Dial by Name for the Menu.
  .PARAMETER DirectorySearchMethod
    String. Required for Action "TransferToMenu" only. Sets the Directory Search Method: None, ByName, ByExtension
  .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.
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Name "My Menu" -Action MenuOptions -Prompts $Prompts -MenuOptions $MenuOptions [-EnableDialByName $True] [-DirectorySearchMethod ByName]
 
    Classic behaviour, mostly synonymous with functionality provided by New-CsAutoAttendantMenu. Please see parameters there.
    Creates Menu with the MenuOptions Objects provided and applies the Prompts Object as the Greeting.
    Parameters EnableDialByName and DirectorySearchMethod can be used as outlined in New-CsAutoAttendantMenu
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Action MenuOptions -Prompts "Press 1 for Sales..." -MenuOptions $MenuOptions -DirectorySearchMethod ByExtension
 
    Creates a Menu with a Prompt and MenuOptions. Creates a Prompts Object with the provided Text-to-voice string.
    Creates Menu with the MenuOptions Objects provided. DirectorySearchMethod is set to ByExtension
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Action MenuOptions -Prompts "C:\temp\Menu.wav" -CallTargetsInOrder "MyCQ@domain.com","MyAA@domain.com","$null","tel:+15551234567","Please listen for our opening hours..."
 
    Creates a Menu with a Prompt and MenuOptions. Creates a Prompts Object with the provided Path to the Audio File.
    Creates a Menu Object with the Call Targets provided in order of application depending on identified ObjectType:
    Option 1 and 2 will be TransferToCallTarget (ResourceAccount), Call Queue and Auto Attendant respectively.
    Option 3 will not be assigned ($null), Option 4 will be TransferToCallTarget (ExternalPstn)
    Option 5 will be an Announcement (Text-to-Voice) - returns to main menu afterwards.
    This method does not allow specifying User Object that are intended to forward to the Users Voicemail!
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Action MenuOptions -Prompts "Press 1 for John, Press 3 for My Group" -CallTargetsInOrder "John@domain.com","","My Group","C:\temp\OpeningHours.wav"
 
    Creates a Menu with a Prompt and MenuOptions. Creates a Prompts Object with the provided Text-to-voice string.
    Creates a Menu with MenuOptions Objects provided in order with the Parameter CallTargetsInOrder depending on identified ObjectType:
    Option 1 will be TransferToCallTarget (User), Option 2 is unassigned (empty string),
    Option 3 is TransferToCallTarget (Shared Voicemail)
    Option 4 will be an Announcement (AudioFile) - returns to main menu afterwards.
    Maximum 12 options are supported, if more than 9 are provided, the Switch AddOperatorOnZero (if used) is ignored.
    This method does not allow specifying User Object that are intended to forward to the Users Voicemail!
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Action Disconnect
 
    Creates a default Menu, disconnecting the Call
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Action TransferToOperator
 
    Creates a default Menu, transferring the Call to the Operator
  .EXAMPLE
    New-TeamsAutoAttendantMenu -Action TransferToCallTarget -CallTarget "John@domain.com"
 
    Creates a default Menu, transferring the Call to the Call target.
    Expected UserPrincipalName (User, ResourceAccount), Group Name (Shared Voicemail), Tel Uri (ExternalPstn)
  .INPUTS
    System.String
  .OUTPUTS
    System.Object
  .NOTES
    Limitations: CallTargetsInOrder are Menu Options integrated and their type is parsed with Get-TeamsCallableEntity
    This provides the following limitation:
    Provided UPNs that are found to be GraphUsers are limited to be used as "Person in the Organisation"
    If forwarding to the Users Voicemail is required, please change this in the Admin Center afterwards.
    Alternatively, please define MenuOptions yourself and use with the MenuOptions Parameter.
 
    CallTargetsInOrder now supports a maximum 12 options. If Position 10 of the Object contains a valid target and
    a Menu Option is created for 'Press 0', the switch AddOperatorOnZero (if used) is ignored.
    Position 11 is mapped to Pound ('#'), Position 12 is mapped to Star ('*')
 
    To define Menu Options manually, please see:
    https://docs.microsoft.com/en-us/powershell/module/skype/new-csautoattendantmenuoption?view=skype-ps
 
    Please see 'Set up an auto attendant' for details:
    https://docs.microsoft.com/en-us/MicrosoftTeams/create-a-phone-system-auto-attendant?WT.mc_id=TeamsAdminCenterCSH
  .COMPONENT
    TeamsAutoAttendant
  .FUNCTIONALITY
    Creates a Menu Object to be used in Auto Attendants
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Teams/New-TeamsAutoAttendantMenu.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_TeamsAutoAttendant.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/
  .LINK
    https://docs.microsoft.com/en-us/MicrosoftTeams/create-a-phone-system-auto-attendant?WT.mc_id=TeamsAdminCenterCSH
  .LINK
    https://docs.microsoft.com/en-us/powershell/module/skype/new-csautoattendantmenuoption?view=skype-ps
  #>


  [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Disconnect', ConfirmImpact = 'Low')]
  [Alias('New-TeamsAAMenu')]
  [OutputType([System.Object])]
  param(
    [Parameter(HelpMessage = 'Optional Name of the Menu')]
    [ValidateLength(5, 63)]
    [string]$Name,

    [Parameter(Mandatory, HelpMessage = 'Action determines Type of Menu to be built')]
    [ValidateSet('TransferToMenu', 'Disconnect', 'TransferToCallTarget')]
    [string]$Action,

    [Parameter(Mandatory, ParameterSetName = 'MenuOptions', HelpMessage = 'Prompt object, Text-To-Voice String or Full path to AudioFile')]
    [Parameter(Mandatory, ParameterSetName = 'CallTargetsToProcess', HelpMessage = 'Prompt object, Text-To-Voice String or Full path to AudioFile')]
    [ArgumentCompleter( { '<Prompts-Object>', '<Your Text-to-speech-string>', 'C:\Temp\' })]
    $Prompts,

    [Parameter(Mandatory, ParameterSetName = 'MenuOptions', HelpMessage = 'MenuOptions Object')]
    [object[]]$MenuOptions,

    [Parameter(Mandatory, ParameterSetName = 'CallTargetsToProcess', HelpMessage = 'Up to 9 Call Targets in order to be applied as MenuOptions')]
    [AllowNull()]
    [AllowEmptyString()]
    [Alias('MenuOptionsInOrder')]
    [string[]]$CallTargetsInOrder,

    [Parameter(Mandatory, ParameterSetName = 'TransferToCallTarget', HelpMessage = 'Up to 9 Call Targets in order to be applied as MenuOptions')]
    [string]$CallTarget,

    [Parameter(ParameterSetName = 'CallTargetsToProcess', HelpMessage = 'Adds a Menu Option for option 0 to Transfer to Operator')]
    [switch]$AddOperatorOnZero,

    [Parameter(HelpMessage = 'Enables directory search by recipient name and get transferred to the party')]
    [switch]$EnableDialByName,

    [Parameter(HelpMessage = 'Directory Search Method for the Auto Attendant menu')]
    [ValidateSet('None', 'ByName', 'ByExtension')]
    [string]$DirectorySearchMethod,

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

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

  ) #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' }

    # Preparing Splatting Object
    $Parameters = @{}

  } #begin

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

    #region Routing - Menu Options
    switch ($Action) {
      'TransferToMenu' {
        #region Prompt
        Write-Verbose -Message 'TransferToMenu - Creating Greeting'
        $PromptsType = ($Prompts | Get-Member | Select-Object TypeName -First 1).TypeName
        switch ($PromptsType) {
          'Deserialized.Microsoft.Rtc.Management.Hosted.OAA.Models.Prompt' {
            Write-Verbose -Message 'Call Flow - Prompts provided is a Prompt Object'
            $Parameters.Prompts = $Prompts

          }
          'Microsoft.Rtc.Management.Hosted.OAA.Models.Prompt' {
            Write-Verbose -Message 'Call Flow - Prompts provided is a Prompt Object'
            $Parameters.Prompts = $Prompts

          }
          'System.String' {
            Write-Verbose -Message 'Call Flow - Greeting provided as a String'
            # Process Greeting
            try {
              $PromptsObject = New-TeamsAutoAttendantPrompt -String "$Prompts"
              if ($PromptsObject) {
                Write-Verbose -Message 'Prompts - Adding 1 Prompt (Greeting)'
                $Parameters.Prompts = $PromptsObject
              }
            }
            catch {
              Write-Warning -Message "Call Flow - Menu - Greeting - Error creating prompt. Omitting Greeting. Exception Message: $($_.Exception.Message)"
            }
          }

          default {
            Write-Error -Message 'Type not accepted as a Greeting/Prompt, please provide a Prompts Object or a String' -ErrorAction Stop
          }
        }
        #endregion


        #region MenuOptions or CallTargetsInOrder
        switch ($PSCmdlet.ParameterSetName) {
          'MenuOptions' {
            # No Action required
          }
          'CallTargetsToProcess' {
            # Process Ordered
            Write-Verbose -Message 'CallTargetsToProcess - Creating Menu Options for Call Targets as provided (CallTargetsInOrder)'
            $Option = 1
            $MaxOptions = 12
            #$MaxOptions = if ($AddOperatorOnZero) { 9 } else { 10 }
            if ( $CallTargetsInOrder.Count -gt 0 -and $DirectorySearchMethod -eq 'ByExtension' ) {
              Write-Warning -Message 'MenuOptions - No Menu Options are supported if DirectorySearchMethod DialbyExtension is selected. Objects are ignored (CallTargetsInOrder)'
              $CallTargetsInOrder = $null
            }
            if ( $CallTargetsInOrder.Count -gt 1 -and $DirectorySearchMethod -eq 'ByName') {
              Write-Warning -Message 'MenuOptions - Only one Menu Option is supported if DirectorySearchMethod DialbyName is selected. Objects are ignored (CallTargetsInOrder)'
              $CallTargetsInOrder = $CallTargetsInOrder | Select-Object -First 1
            }
            if ($CallTargetsInOrder.Count -gt 12) {
              Write-Warning -Message 'MenuOptions - Max 12 options are supported as Call Targets. Additional Objects are ignored (CallTargetsInOrder)'
            }

            [System.Collections.Generic.List[object]]$CreatedMenuOptions = @()
            foreach ($Target in $CallTargetsInOrder) {
              Write-Verbose -Message "CallTargetsToProcess - Processing '$Target'"
              if ($Option -le $CallTargetsInOrder.Count -and $Option -le $MaxOptions) {
                $Selection = switch ( $Option ) {
                  10 { '0' }
                  11 { 'Pound' }
                  12 { 'Star' }
                  default { $Option }
                }

                if ( $Target ) {
                  $MenuOptionToAdd = $null
                  #Identifying the Target is delegated to New-TeamsAutoAttendantMenuOption for all but Announcement
                  try {
                    $CallableEntity = Get-TeamsObjectType "$Target"
                    if ($CallableEntity -eq 'Unknown') {
                      # Assume it is an announcement
                      $MenuOptionToAdd = New-TeamsAutoAttendantMenuOption -Press $Selection -Announcement "$Target" -ErrorAction Stop
                    }
                    else {
                      # Process as a Call Target
                      $TeamsAutoAttendantMenuOption = @{
                        Press       = $Selection
                        CallTarget  = "$Target"
                        ErrorAction = 'Stop'
                        Verbose     = $Verbosepreference
                      }
                      if ( $PSBoundParameters['EnableTranscription'] ) { $TeamsAutoAttendantMenuOption.EnableTranscription = $true }
                      if ( $PSBoundParameters['EnableSharedVoicemailSystemPromptSuppression'] ) { $TeamsAutoAttendantMenuOption.EnableSharedVoicemailSystemPromptSuppression = $true }
                      $MenuOptionToAdd = New-TeamsAutoAttendantMenuOption @TeamsAutoAttendantMenuOption
                      #$MenuOptionToAdd = New-TeamsAutoAttendantMenuOption -Press $Selection -CallTarget "$Target" -ErrorAction Stop
                    }
                    if ($MenuOptionToAdd) {
                      Write-Information "INFO: MenuOptions - Creating Option 'Press $Selection' to '$Target' - OK"
                      [void]$CreatedMenuOptions.Add($MenuOptionToAdd)
                    }
                  }
                  catch {
                    Write-Warning -Message "MenuOptions - Creating Option 'Press $Selection' to '$Target' - Creation unsuccessful! Omitting Call Target"
                  }
                }
                else {
                  Write-Verbose -Message "MenuOptions - Creating Option 'Press $Selection' not provided, is empty or NULL - OK (omitted)" -Verbose
                }
                $Option++
              }
            }

            # AddOperatorOnZero
            if ($AddOperatorOnZero) {
              if ( $DirectorySearchMethod -eq 'None' ) {
                if ( $null -eq ($CreatedMenuOptions | Where-Object DtmfResponse -EQ Tone0) ) {
                  # Create Menu Option on "Press 0" to forward to Operator
                  Write-Verbose -Message 'MenuOptions - Creating additional Option for Operator (AddOperatorOnZero)'
                  $MenuOptionToAdd = $null
                  $MenuOptionToAdd = New-TeamsAutoAttendantMenuOption -Press 0 -TransferToOperator
                  if ($MenuOptionToAdd) {
                    Write-Information "INFO: MenuOptions - Creating Option 'Press 0' to 'Operator' - OK"
                    [void]$CreatedMenuOptions.Add($MenuOptionToAdd)
                  }
                }
                else {
                  Write-Warning -Message "MenuOptions - AddOperatorOnZero is not parsed as CallTargetsInOrder already contains an Option for 'Press 0'"
                }
              }
            }

            $MenuOptions = $CreatedMenuOptions
          }
        }
        #endregion

      } #MenuOption

      'Disconnect' {
        $MenuOptions = New-TeamsAutoAttendantMenuOption -DisconnectCall
      }

      'TransferToCallTarget' {
        Write-Verbose -Message 'TransferToCallTarget - Creating Menu Option'
        $TeamsAutoAttendantMenuOption = @{
          CallTarget = "$CallTarget"
          Verbose    = $Verbosepreference
        }
        if ( $PSBoundParameters['EnableTranscription'] ) { $TeamsAutoAttendantMenuOption = @{ EnableTranscription = $true } }
        if ( $PSBoundParameters['EnableSharedVoicemailSystemPromptSuppression'] ) { $TeamsAutoAttendantMenuOption = @{ EnableSharedVoicemailSystemPromptSuppression = $true } }
        $MenuOptions = New-TeamsAutoAttendantMenuOption @TeamsAutoAttendantMenuOption
      }
    }

    # Adding MenuOptions
    if ( -not $MenuOptions -and $DirectorySearchMethod -ne 'None' ) {
      Write-Verbose -Message "MenuOptions - Action '$Action' - DirectorySearchMethod '$DirectorySearchMethod' - Omiting Menu Options"
    }
    else {
      Write-Verbose -Message "MenuOptions - Action '$Action' - Adding $($MenuOptions.Count) Menu Options"
      $Parameters.MenuOptions = $MenuOptions
    }
    #endregion


    #region Other Parameters
    if ( -not $Name) {
      $Name = "Menu with $($Parameters.MenuOptions.Count) Options" + $(if ($Parameters.Prompts) { ' and Greeting' })
    }
    $Parameters.Name = "$Name"
    Write-Information "INFO: Menu Name selected: '$Name'"

    #VALIDATE EnableDialByName may not be needed if DirectorySearchMethod is not 'None' - simplify?
    if ( $EnableDialByName ) {
      $Parameters.EnableDialByName = $true
    }

    if ( $DirectorySearchMethod ) {
      $Parameters.DirectorySearchMethod = $DirectorySearchMethod
    }
    #endregion


    # Create Menu
    Write-Verbose -Message '[PROCESS] Creating Menu'
    if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') {
      " Function: $($MyInvocation.MyCommand.Name) - Parameters (New-CsAutoAttendantMenu)", ($Parameters | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
    }

    if ($PSCmdlet.ShouldProcess("$($Menu.Name)", 'New-CsAutoAttendantMenu')) {
      New-CsAutoAttendantMenu @Parameters
    }
  }

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