Public/Support/VoiceConfig/Get-TeamsTelephoneNumber.ps1

# Module: TeamsFunctions
# Function: Teams User Voice Configuration
# Author: David Eberhardt
# Updated: 12-FEB-2022
# Status: Live

#CHECK: How to use -ExpandLocation? - Currently broken?
#FIXME: Function not used any longer as Get-CsOnlineTelephoneNumber is deprecated. Scrap?

function Get-TeamsTelephoneNumber {
  <#
  .SYNOPSIS
    Queries Phone Numbers in Teams and returns a concise result
  .DESCRIPTION
    Wrapper for Get-CsOnlineTelephoneNumber to ascertain the PhoneNumberType or filter by it.
    Adds value to output with PhoneNumberType interpreted from parameters returned by Get-CsOnlineTelephoneNumber.
    If a number is not found in the tenant it is assumed to be of the Type DirectRouting and an object is returned.
    For exact matches, the potential assignment is queried in addition to the boolean assignment status.
  .PARAMETER PhoneNumber
    Returns an Object abbreviated from Get-CsOnlineTelephoneNumber of the type CallingPlan or OperatorConnect.
    Returns a mocked Object for DirectRouting Numbers as these numbers are not present in the tenant.
    Expected format is E.164 or LineUri. Requires full number. For more granular search use Get-CsOnlineTelephoneNumber.
  .PARAMETER PhoneNumberType
    Valid values are CallingPlan, OperatorConnect or DirectRouting
    Using DirectRouting will return an empty object as these numbers are not present in the tenant.
  .PARAMETER Location
    Returns an Object abbreviated from Get-CsOnlineTelephoneNumber of the type CallingPlan or OperatorConnect.
    Requires the LocationId or Location Name (DisplayName).
  .PARAMETER CityCode
    Returns an Object abbreviated from Get-CsOnlineTelephoneNumber of the type CallingPlan or OperatorConnect.
    Requires the CityCode as displayed in Get-CsOnlineTelephoneNumber.
  .PARAMETER InventoryType
    Returns an Object abbreviated from Get-CsOnlineTelephoneNumber of the type CallingPlan or OperatorConnect.
    Available options are: Subscriber (User), Service (Conferencing), TollFree
  .PARAMETER IsNotAssigned
    Only returns free numbers
  .EXAMPLE
    Get-TeamsTelephoneNumber -PhoneNumber +15551234567
 
    Returns all Phone Numbers starting with this string (this can be an exact result or an array of numbers)
    The Object returned is an abbreviation and extension of the Output displayed with Get-CsOnlineTelephoneNumber.
  .EXAMPLE
    Get-TeamsTelephoneNumber -PhoneNumber +1555*
 
    Returns all Phone Number Objects found starting with this string
  .EXAMPLE
    Get-TeamsTelephoneNumber -PhoneNumberType OperatorConnect
 
    Returns all Phone Numbers added to the tenant via OperatorConnect. Also available: CallingPlan
  .EXAMPLE
    Get-TeamsTelephoneNumber -Location 00000000-0000-0000-0000-000000000000
 
    Returns all Phone Numbers present in the tenant that matches this location ID
    Available only for Numbers of PhoneNumberType CallingPlan
  .EXAMPLE
    Get-TeamsTelephoneNumber -Location "R&D Building"
 
    Returns all Phone Numbers present in the tenant that matches the location with the Description "R&D Building"
    Search by location is only possible for Numbers of PhoneNumberType CallingPlan.
    For OperatorConnect the Location is set by the Operator and cannot be changed
  .EXAMPLE
    Get-TeamsTelephoneNumber -CityCode APAC-NZ-ALL-AUK_AU
 
    Returns all Phone Numbers present in the tenant that matches the City Code provided
    The CityCode is required to be provided as displayed with Get-CsOnlineTelephoneNumber
  .EXAMPLE
    Get-TeamsTelephoneNumber -InventoryType Subscriber
 
    Returns all Phone Numbers present in the tenant with the InventoryType Subscriber
    Available options are: Subscriber (User), Service (Conferencing), TollFree
  .EXAMPLE
    Get-TeamsTelephoneNumber -InventoryType Subscriber -PhoneNumber +1555* -Location "R&D Building"
 
    Returns all Phone Numbers present in the tenant that match all criteria, i.E. with the InventoryType Subscriber,
    the phone number starting with 1555 (* performs search), and the location translated from the description "R&D Building"
  .INPUTS
    System.String
  .OUTPUTS
    System.Object
  .NOTES
    Simple helper function to query a Phone Number and receive its PhoneNumberType.
    Sister Function to Get-TeamsPhoneNumber which queries Get-CsPhoneNumberAssignment.
    Other functionality is purely wrapped around Get-CsOnlineTelephoneNumber to provide a rounded picture
    For full information, please use Get-CsOnlineTelephoneNumber
    The corresponding Set-Cmdlet Set-TeamsPhoneNumber also supports the PhoneNumbertype 'DirectRouting'.
    For searches by PhoneNumber, the CmdLet will interpret any number not found in the tenant to be of the type
    DirectRouting and return it as such.
  .COMPONENT
    VoiceConfiguration
  .FUNCTIONALITY
    Queries information about Phone Numbers in the Teams Tenant to inform Voice Configuration
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Get-TeamsTelephoneNumber.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_VoiceConfiguration.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_UserManagement.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_Supporting_Functions.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/
  #>


  [CmdletBinding(ConfirmImpact = 'Low')]
  [OutputType([System.Object])]
  param(
    [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('Number', 'TelephoneNumber')]
    [AllowNull()]
    [AllowEmptyString()]
    [string]$PhoneNumber,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateSet('CallingPlan', 'OperatorConnect', 'DirectRouting')]
    [Alias('Type')]
    [string]$PhoneNumberType,

    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('LocationId', 'LocationName')]
    [string]$Location,

    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('City')]
    [string]$CityCode,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateSet('Subscriber', 'Service', 'TollFree')]
    [string]$InventoryType,

    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]$IsNotAssigned
  ) #param

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

    # Asserting MicrosoftTeams Connection
    if ( -not (Assert-MicrosoftTeamsConnection) ) { break }

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

    $Stack = Get-PSCallStack
    $Called = ($stack.length -ge 3)

    if ( [String]::IsNullOrEmpty($PhoneNumber) ) {
      $PhoneNumber = $null
    }

    # Preparing Splatting Object
    $parameters = $null
    $Parameters = @{
      'WarningAction' = 'SilentlyContinue'
      'ErrorAction'   = 'Stop'
    }

    if ($PhoneNumber) {
      #TEST Rethinking call to Get-CsOnlineTelephoneNumber (must be in number format, not in TEL URI!)
      #Normalising Phonenumber before call
      $PhoneNumberSearchMode = if ( $PhoneNumber -match '\*$' ) { $true } else { $false }
      $PhoneNumber = $PhoneNumber | Format-StringForUse -As Number
      if ( $PhoneNumberSearchMode ) {
        Write-Verbose -Message "PhoneNumber interpreted as part of the string (searching all Phone Numbers starting with '$($PhoneNumber.Replace('*',''))'"
        $Parameters.TelephoneNumberStartsWith = $PhoneNumber
      }
      else {
        Write-Verbose -Message "PhoneNumber interpreted as full number (searching for exact match of '$($PhoneNumber.Replace('*',''))'"
        $Parameters.TelephoneNumber = $PhoneNumber
      }
    }
    if ($PSBoundParameters.ContainsKey('Location')) {
      #TEST This is currently untested as no examples were available (only valid for CallingPlans as well!)
      Write-Verbose -Message "Location '$Location' queried against Get-CsOnlineLisLocation"
      try {
        $CsOnlineLisLocation = if ( $Address -match $script:TFMatchGuid ) {
          Get-CsOnlineLisLocation -LocationId $Address -ErrorAction Stop
        }
        else {
          Get-CsOnlineLisLocation -Location "$Address" -ErrorAction Stop
        }
        $Parameters.Location = $CsOnlineLisLocation.LocationId
      }
      catch {
        throw "Location '$Location' not found (CsOnlineLisLocation)! Please provide LocationId or Address Description"
      }
    }
    if ($PSBoundParameters.ContainsKey('CityCode')) { $Parameters.CityCode = $CityCode }
    if ($PSBoundParameters.ContainsKey('InventoryType')) { $Parameters.InventoryType = $InventoryType }
    #IsNotAssigned is not processed here, but after the query

    # defining Output Object class
    Class PhoneNumberObject {
      [string]$PhoneNumber
      [ValidateSet('CallingPlan', 'OperatorConnect', 'DirectRouting')]
      [string]$PhoneNumberType
      [AllowNull()]
      [string]$Location
      [AllowNull()]
      [string]$LocationName
      [AllowNull()]
      [string]$CityCode
      [AllowNull()]
      [string]$InventoryType
      [nullable[bool]]$Assigned
    }

    # Loading Microsoft Numbers (disabled as not needed)
    #if (-not $global:TeamsFunctionsCsOnlineTelephoneNumber) { $global:TeamsFunctionsCsOnlineTelephoneNumber = Get-CsOnlineTelephoneNumber -ResultSize 10000 }

  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    try {
      if ($PSBoundParameters.ContainsKey('Debug') -or $DebugPreference -eq 'Continue') {
        "Function: $($MyInvocation.MyCommand.Name) - Parameters (Get-CsOnlineTelephoneNumber)", ($Parameters | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
      }
      $Numbers = Get-CsOnlineTelephoneNumber @Parameters

      if ($PSBoundParameters.ContainsKey('Debug') -or $DebugPreference -eq 'Continue') {
        "Function: $($MyInvocation.MyCommand.Name) - Numbers", ($Numbers | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
      }
    }
    catch {
      Write-Error -Message "Error querying Get-CsOnlineTelephoneNumber: $($_.Exception.Message)"
    }

    # Exit strategy
    if ( -not $Numbers ) {
      if ( -not $PhoneNumber ) {
        Write-Information "INFO: No numbers found with these Parameters: $($PSBoundParameters.keys)"
        continue
      }
    }

    # Output
    if ( $Numbers ) {
      # OperatorConnect or CallingPlans Number detected

      # Filter by PhoneNumberType
      if ( $PSBoundParameters.ContainsKey('PhoneNumberType') ) {
        switch ($PhoneNumberType) {
          'OperatorConnect' { $Numbers = $Numbers | Where-Object IsManagedByServiceDesk }
          'CallingPlan' { $Numbers = $Numbers | Where-Object { -not $_.IsManagedByServiceDesk } }
          'DirectRouting' {
            continue
            # no action as no numbers will be found anyway.
          }
        }
      }

      # Filtering by assignment
      if ($PSBoundParameters.ContainsKey('IsNotAssigned')) {
        $Numbers = $Numbers | Where-Object { $null -eq $_.TargetType }
      }

      foreach ($Number in $Numbers) {
        # Translating parameters
        $NumberLocationName = $null
        $NumberLocationName = if ( $Number.Location ) { (Get-CsOnlineLisLocation -LocationId $Number.Location).Description } else { $null }
        $NumberPhoneNumberType = if ( $Number.IsManagedByServiceDesk ) { 'OperatorConnect' } else { 'CallingPlan' }

        #<# Finding assignments - This was disabled due to runtime concerns!
        $NumberAssignment = $null
        #$Filter = 'LineURI -like "*{0}*"' -f $Number.Id
        $Filter = 'LineURI -like "{0}*"' -f $Number.Id
        $NumberAssignment = if ( $Number.TargetType ) { (Get-CsOnlineUser -Filter $Filter) } else { $null }
        #>
        # Creating Output Object
        $PhoneNumberObject = $null
        $PhoneNumberObject = [PhoneNumberObject]@{
          PhoneNumber     = $Number.Id
          PhoneNumberType = $NumberPhoneNumberType
          Location        = $Number.Location
          LocationName    = $NumberLocationName
          CityCode        = $Number.CityCode
          InventoryType   = $Number.InventoryType
          Assigned        = [bool]$($Number.TargetType)
        }
        if ( $NumberAssignment ) {
          $PhoneNumberObject | Add-Member -MemberType NoteProperty -Name AssignedTo -Value $NumberAssignment.UserPrincipalName
        }
        $PhoneNumberObject.Assigned = if ( $NumberAssignment ) { $true } else { $false }
        Write-Output $PhoneNumberObject
      }
    }
    else {
      # Assuming Number is not in the Tenant and therefore a DirectRouting Number
      $NumberAssignment = $null
      if ( $PhoneNumber ) {
        # Creating Output Object
        $PhoneNumberObject = $null
        $PhoneNumberObject = [PhoneNumberObject]@{
          PhoneNumber     = $PhoneNumber
          PhoneNumberType = 'DirectRouting'
          Location        = $null
          LocationName    = $null
          CityCode        = $null
          InventoryType   = 'Any'
          Assigned        = $NumberAssignment
        }

        # Finding assignments
        # Assignment is ONLY run here if a singular result is expected.
        if ( $PhoneNumber -match '\*$' ) {
          Write-Verbose "PhoneNumber contains '*' (search mode). This cannot be used to validate Assignments" -Verbose
        }
        else {
          try {
            $PhoneNumber = $PhoneNumber | Format-StringForUse -as Number
            #$Filter = 'LineURI -like "*{0}*"' -f $PhoneNumber
            $Filter = 'LineURI -like "{0}*"' -f $PhoneNumber
            $NumberAssignment = (Get-CsOnlineUser -Filter $Filter -ErrorAction Stop)
          }
          catch {
            $NumberAssignment = $null
          }
          if ( $NumberAssignment ) {
            $PhoneNumberObject | Add-Member -MemberType NoteProperty -Name AssignedTo -Value $NumberAssignment.UserPrincipalName
          }
          $PhoneNumberObject.Assigned = if ( $NumberAssignment ) { $true } else { $false }
        }
        Write-Information 'INFO: Number not found in the tenant, this is expected for Direct Routing numbers.'
        Write-Output $PhoneNumberObject
      }
    }
  } #process

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