Private/Set-TFPhoneNumber.ps1

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




function Set-TFPhoneNumber {
  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
  param(
    [Parameter(Mandatory)]
    $UserObject,

    [Parameter(Mandatory)]
    $UserLicense,

    [Parameter(Mandatory)]
    [AllowNull()]
    [AllowEmptyString()]
    [string]$PhoneNumber,

    [Parameter()]
    $Called = $false,

    [Parameter()]
    $Force = $false
  ) #param

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

    #region Worker Function
    function SetNumber {
      [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
      param(
        [Parameter(Mandatory)]
        [string]$UserPrincipalName,

        [Parameter(Mandatory)]
        [AllowNull()]
        [AllowEmptyString()]
        [string]$PhoneNumber,

        [Parameter(Mandatory)]
        [string]$PhoneNumberType
      ) #param

      if ( $null -eq $PhoneNumber -or $PhoneNumber -eq '' ) {
        $PhoneNumber = $null
      }
      else {
        # This is necessary as Set-CsPhoneNumberAssignment expects the phoneNumber in the format "+1234567890;ext=1234"
        $PhoneNumber = $($PhoneNumber | Format-StringForUse -As LineUri) -replace 'tel:', ''
      }

      try {
        $CsPhoneNumberAssignmentParams = @{
          'Identity'        = "$UserPrincipalName"
          'PhoneNumber'     = "$PhoneNumber"
          'PhoneNumberType' = $PhoneNumberType
        }
        if ($PSBoundParameters.ContainsKey('Debug')) {
          " Function: $($MyInvocation.MyCommand.Name) - CsPhoneNumberAssignmentParams:", ($CsPhoneNumberAssignmentParams | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
        }

        if ( [String]::IsNullOrEmpty($PhoneNumber) ) {
          # If phonenumber is empty, this needs to call Remove-CsPhoneNumberAssignment
          # Removal of all Numbers required
          if ($PSCmdlet.ShouldProcess("$UserPrincipalName", 'Remove-CsPhoneNumberAssignment -RemoveAll')) {
            $null = Remove-CsPhoneNumberAssignment -Identity $UserPrincipalName -RemoveAll -ErrorAction STOP
          }
        }
        else {
          # Direct Routing Number
          if ($PSCmdlet.ShouldProcess("$UserPrincipalName", "Set-CsPhoneNumberAssignment -PhoneNumber $PhoneNumber")) {
            $null = Set-CsPhoneNumberAssignment @CsPhoneNumberAssignmentParams -ErrorAction STOP
          }
        }
      }
      catch {
        #Writing error of CsPhoneNumberAssignment to debug stream
        " Function: $($MyInvocation.MyCommand.Name) - CsPhoneNumberAssignment-CmdLet threw exception:", ($($_.Exception.Message) | Out-String).Trim() | Write-Debug
        Write-Error -Message "Assignment with 'CsPhoneNumberAssignment' failed. $($_.Exception.Message)" -ErrorAction Stop
      }
    } #SetNumber
    #endregion
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    #region Validating Object
    # Catching non-supported User types and setting ID
    switch -regex ( $UserObject.InterpretedUserType ) {
      'User' {
        $Id = $($UserObject.SipAddress)
      }
      'ApplicationInstance' {
        $Id = $($UserObject.UserPrincipalName)
      }
      Default {
        $Id = if ( $UserObject.ObjectId.IsPresent ) { $UserObject.ObjectId } else { $UserObject.Identity }
        $Message = "Object '$Id' is not a User or a ResourceAccount!"
        throw [System.InvalidOperationException]::New("$Message")
      }
    }

    # Object Location (OnPrem VS Online)
    if ( $UserObject.InterpretedUserType -match 'OnPrem' ) {
      $Message = "'$Id' is not hosted in Teams!"
      Write-Warning -Message $Message
      #Deactivated as Object is able to be used/enabled even if in Islands mode and Object in Skype!
      #throw [System.InvalidOperationException]::New("$Message")
    }
    #endregion

    #region Validating License
    if ( $UserObject.UserPrincipalName -ne $UserLicense.UserPrincipalName ) {
      $Message = "UserObject '$($UserObject.UserPrincipalName)' does not match LicenseObject $($UserLicense.UserPrincipalName)"
      throw [System.InvalidOperationException]::New("$Message")
    }
    if ( -not $UserLicense.PhoneSystem -and -not $UserLicense.PhoneSystemVirtualUser ) {
      $Message = "'$Id' Enterprise Voice Status: User is not licensed correctly (PhoneSystem required)!"
      throw [System.InvalidOperationException]::New("$Message")
    }

    if ( -not [string]$UserLicense.PhoneSystemStatus.contains('Success') ) {
      Write-Information "TRYING: '$Id' - Phone System: Not enabled, trying to enable"
      Set-AzureAdUserLicenseServicePlan -UserPrincipalName $UserObject.UserPrincipalName -Enable MCOEV
      $i = 0
      $iMax = 60
      Write-Information "INFO: User '$Id' - Phone System: Enabled; Waiting for AzureAd to write object ($iMax s)"
      $StatusID1 = 'Azure Active Directory is propagating Object. Please wait'
      $CurrentOperationID1 = 'Waiting for Get-AzureAdUserLicense to return a Result'
      Write-Verbose -Message "$StatusID1 - $CurrentOperationID1"
      do {
        if ($i -gt $iMax) {
          Write-Error -Message "Could not find Object in AzureAD in the last $iMax Seconds" -Category ObjectNotFound -RecommendedAction 'Please verify Object has been created (UserPrincipalName); Continue with Set-TeamsResourceAccount'
          return
        }
        Write-Progress -Id 1 -ParentId 0 -Activity $ActivityID1 -Status $StatusID1 -CurrentOperation $CurrentOperationID1 -SecondsRemaining $($iMax - $i) -PercentComplete (($i * 100) / $iMax)
        Start-Sleep -Milliseconds 1000
        $i++
        $UserLicense = Get-AzureAdUserLicense "$Id"
      }
      while ( -not [string]$UserLicense.PhoneSystemStatus.contains('Success') )
    }
    #endregion

    #region Enterprise Voice
    <# not required anymore as Set-CsPhoneNumberAssignment does this by itself.
    if ( $UserObject.EnterpriseVoiceEnabled ) {
      $EVenabled = $true
    }
    else {
      try {
        Set-CsPhoneNumberAssignment -Identity $UserObject.SipAddress -EnterpriseVoiceEnabled $true -ErrorAction Stop
        Start-Sleep -Seconds 1
        $EVenabled = (Get-CsOnlineUser -Identity "$($UserObject.SipAddress)" -WarningAction SilentlyContinue -ErrorAction Stop).EnterpriseVoiceEnabled
      }
      catch {
        if ($PSBoundParameters.ContainsKey('Debug')) {
          " Function: $($MyInvocation.MyCommand.Name) - Set-CsPhoneNumberAssignment: Error", ($_.Exception.Message | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
        }
        $EVenabled = $false
      }
    }
    if ( -not $EVenabled ) {
      $Message = "'$Id' Enterprise Voice: User could not be enabled for Enterprise Voice!"
      throw [System.InvalidOperationException]::New("$Message")
    }
    #>

    #endregion

    #region Validating Phone Number
    # Querying CurrentPhoneNumber
    try {
      $CurrentPhoneNumber = $CsUser.LineUri
      Write-Verbose -Message "Object '$Id' - Phone Number assigned currently: '$CurrentPhoneNumber'"
    }
    catch {
      $CurrentPhoneNumber = $null
      Write-Verbose -Message "Object '$Id' - Phone Number assigned currently: NONE"
    }

    if ( [String]::IsNullOrEmpty($PhoneNumber) ) {
      if ($CurrentPhoneNumber) {
        Write-Warning -Message "Object '$Id' - PhoneNumber is NULL or Empty. The Existing Number '$CurrentPhoneNumber' will be removed"
      }
      else {
        Write-Verbose -Message "Object '$Id' - PhoneNumber is NULL or Empty, but no Number is currently assigned. No Action taken"
      }
      $PhoneNumber = $null
      $TeamsPhoneNumberObject = $null
      $UserWithThisNumberExceptSelf = $null
    }
    else {
      # New method based on Get-CsPhoneNumberassignment
      $E164Number = $PhoneNumber | Format-StringForUse -As E164
      # Determining Number Type
      Write-Verbose -Message "Object '$Id' - Parsing Business Voice Directory for current assignments for '$E164Number'"
      $TeamsPhoneNumberObject = $null
      $TeamsPhoneNumberObject = Get-TeamsPhoneNumber -PhoneNumber $E164Number -InformationAction SilentlyContinue
      if ( $TeamsPhoneNumberObject ) {
        Write-Verbose -Message "Provisioning for $($TeamsPhoneNumberObject.NumberType)"
        # Previous Assignments
        $UserWithThisNumber = $TeamsPhoneNumberObject | Where-Object Assigned
        $UserWithThisNumberExceptSelf = $null
        $UserWithThisNumberExceptSelf = $UserWithThisNumber | Where-Object AssignedPstnTargetId -NE $UserObject.Identity
      }
      else {
        Write-Verbose -Message 'Provisioning for DirectRouting (no current assignments found)'
        $UserWithThisNumber = $UserWithThisNumberExceptSelf = $null
      }
      if ( $UserWithThisNumberExceptSelf ) {
        foreach ($UserWTN in $UserWithThisNumberExceptSelf) {
          if ($Force) {
            Write-Warning -Message "Object '$Id' - Number '$PhoneNumber' is currently assigned to User '$($UserWTN.AssignedTo)'. This assignment will be removed!"
          }
          else {
            Write-Error -Message "Object '$Id' - Number '$PhoneNumber' is already assigned to another Object: '$($UserWTN.AssignedTo)'" -Category NotImplemented -RecommendedAction 'Please specify a different Number or use -Force to re-assign' -ErrorAction Stop
          }
        }
      }
    }
    #endregion

    #region ACTION
    # Scavenging Phone Number
    if ( $Force ) {
      Write-Warning -Message 'Parameter Force - Scavenging Phone Number from all Objects where number is assigned. Validate carefully'
      foreach ($UserWTN in $UserWithThisNumberExceptSelf) {
        Write-Verbose -Message "Object '$($UserWTN.AssignedTo)' - Scavenging Phone Number"
        try {
          $SetNumberParams = @{
            'UserPrincipalName' = $($UserWTN.AssignedTo)
            'PhoneNumber'       = $null
            'PhoneNumberType'   = $UserWTN.NumberType
            'ErrorAction'       = 'Stop'
          }
          SetNumber @SetNumberParams
          Write-Information "INFO: '$($UserWTN.AssignedTo)' - Phone Number '$($UserWTN.PhoneNumber)' removed" -InformationAction Continue
        }
        catch {
          $Message = "'$Id' - Error scavenging Phone Number: $($_.Exception.Message)"
          Write-Error $Message -ErrorAction $ErrorActionPreference
        }
      }
    }

    #Removing Phone Number
    if ( $Force -or ([String]::IsNullOrEmpty($PhoneNumber)) ) {
      Write-Verbose -Message "Object '$Id' - Removing Phone Number"
      try {
        $SetNumberParams = @{
          'UserPrincipalName' = $Id
          'PhoneNumber'       = $null
          'PhoneNumberType'   = 'DirectRouting' # Not used, just required to pass something to SetNumber
          'ErrorAction'       = 'Stop'
        }
        SetNumber @SetNumberParams
      }
      catch {
        $Message = "'$Id' - Error removing Phone Number: $($_.Exception.Message)"
        Write-Error $Message -ErrorAction $ErrorActionPreference
      }
    }

    #Setting Phone Number
    if ( -not ([String]::IsNullOrEmpty($PhoneNumber)) ) {
      Write-Verbose -Message "Object '$Id' - Applying Phone Number"
      try {
        $SetNumberParams = @{
          'UserPrincipalName' = $Id
          'PhoneNumber'       = $PhoneNumber
          'PhoneNumberType'   = $($TeamsPhoneNumberObject.NumberType)
          'ErrorAction'       = 'Stop'
        }
        SetNumber @SetNumberParams
      }
      catch {
        $Message = "'$Id' - Error applying Phone Number: $($_.Exception.Message)"
        Write-Error $Message -ErrorAction $ErrorActionPreference
      }
    }
    #endregion
  } #process

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