Public/Support/VoiceConfig/Set-TeamsPhoneNumber.ps1

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

#TODO For Later: When adding -Location, catch "Contact your operator to change the emergency address for this number." - OperatorConnect numbers cannot be assigned a location - display this error (warning?) instead?


function Set-TeamsPhoneNumber {
  <#
  .SYNOPSIS
    Applies a Phone Number to a User Object or Resource Account
  .DESCRIPTION
    Applies a Microsoft Calling Plans Number OR a Direct Routing Number to a User or Resource Account
  .PARAMETER UserPrincipalName
    Required for Parameterset UserPrincipalName. UserPrincipalName of the Object to be assigned the PhoneNumber.
    This can be a UPN of a User Account (CsOnlineUser Object) or a Resource Account (CsOnlineApplicationInstance Object)
  .PARAMETER Object
    Required for Parameterset Object. CsOnlineUser Object passed to the function to reduce query time.
    This can be a UPN of a User Account (CsOnlineUser Object) or a Resource Account (CsOnlineApplicationInstance Object)
  .PARAMETER PhoneNumber
    A Microsoft Calling Plans Number or a Direct Routing Number
    Requires the Account to be licensed. Able to enable PhoneSystem and the Account for Enterprise Voice
    Required format is E.164 or LineUri, starting with a '+' and 10-15 digits long.
  .PARAMETER Force
    Suppresses confirmation prompt unless -Confirm is used explicitly
    Scavenges Phone Number from all accounts the PhoneNumber is currently assigned to including the current User
  .EXAMPLE
    Set-TeamsPhoneNumber -UserPrincipalName John@domain.com -PhoneNumber +15551234567
 
    Applies the Phone Number +1 (555) 1234-567 to the Account John@domain.com
  .INPUTS
    System.String
  .OUTPUTS
    System.Void - If called directly
    Boolean - If called by another CmdLet
  .NOTES
    Simple helper function to assign a Phone Number to any User or Resource Account
    Returns boolean result and less communication if called by another function
    Can be used providing either the UserPrincipalName or the already queried CsOnlineUser Object
  .COMPONENT
    VoiceConfiguration
  .FUNCTIONALITY
    Enables a User for Enterprise Voice in order to apply a valid Voice Configuration
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Set-TeamsPhoneNumber.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(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'UserPrincipalName')]
  [OutputType([Boolean])]
  param(
    [Parameter(Mandatory, Position = 0, ParameterSetName = 'Object', ValueFromPipeline)]
    [Object[]]$Object,

    [Parameter(Mandatory, Position = 0, ParameterSetName = 'UserPrincipalName', ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('ObjectId', 'Identity')]
    [ValidateScript( {
        If ($_ -match '@' -or $_ -match $script:TFMatchGuid) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$UserPrincipalName,

    [Parameter(Mandatory, Position = 1, HelpMessage = 'Telephone Number to assign')]
    [AllowNull()]
    [AllowEmptyString()]
    [Alias('Tel', 'Number', 'TelephoneNumber')]
    [string]$PhoneNumber,

    [Parameter(HelpMessage = 'Suppresses confirmation prompt unless -Confirm is used explicitly')]
    [switch]$Force
  ) #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' }
    if ( $PSBoundParameters.ContainsKey('ErrorAction')) { $InformationPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ErrorAction') } else { $ErrorActionPreference = 'Stop' }

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

    if ( -not $global:TeamsFunctionsMSTeamsModule) { $global:TeamsFunctionsMSTeamsModule = Get-Module MicrosoftTeams }

    if ( [String]::IsNullOrEmpty($PhoneNumber) ) {
      $PhoneNumber = $null
    }
    else {
      If ($PhoneNumber -notmatch '^(tel:\+|\+)?([0-9]?[-\s]?(\(?[0-9]{3}\)?)[-\s]?([0-9]{3}[-\s]?[0-9]{4})|[0-9]{8,15})((;ext=)([0-9]{3,8}))?$') {
        throw [System.Management.Automation.ValidationMetadataException] 'Not a valid phone number. Must be 8 to 15 digits long'
      }
    }

    # Preparing Splatting Object
    $parameters = $null
    $Parameters = @{
      'PhoneNumber' = $PhoneNumber
      'Called'      = $Called
      'Force'       = $Force
      'ErrorAction' = 'Stop'
    }
    #endregion
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    try {
      switch ($PSCmdlet.ParameterSetName) {
        'UserprincipalName' {
          foreach ($User in $UserPrincipalName) {
            Write-Verbose -Message "[PROCESS] Processing provided UserPrincipalName '$User'"
            try {
              $CsUser = Get-CsOnlineUser -Identity "$User" -WarningAction SilentlyContinue -ErrorAction Stop
              $UserLicense = Get-AzureAdUserLicense "$User"
            }
            catch {
              Write-Error "'$User' not found" -Category ObjectNotFound
              continue
            }
            if ($Force -or $PSCmdlet.ShouldProcess("$($CsUser.UserPrincipalName)", 'Set Phone Number')) {
              Set-TFPhoneNumber -UserObject $CsUser -UserLicense $UserLicense @Parameters
            }
          }
        }
        'Object' {
          foreach ($O in $Object) {
            Write-Verbose -Message "[PROCESS] Processing provided CsOnlineUser Object for '$($O.UserPrincipalName)'"
            $UserLicense = Get-AzureAdUserLicense "$($O.UserPrincipalName)"
            if ($Force -or $PSCmdlet.ShouldProcess("$($CsUser.UserPrincipalName)", 'Set Phone Number')) {
              Set-TFPhoneNumber -UserObject $O -UserLicense $UserLicense @Parameters
            }
          }
        }
      }
    }
    catch {
      Write-Error -Message $($_.Exception.Message) -ErrorAction $ErrorActionPreference
    }
  } #process

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