Public/Support/VoiceConfig/Enable-TeamsUserForEnterpriseVoice.ps1

# Module: TeamsFunctions
# Function: Teams User Voice Configuration
# Author: David Eberhardt
# Updated: 01-DEC-2020
# Status: Live




function Enable-TeamsUserForEnterpriseVoice {
  <#
  .SYNOPSIS
    Enables a User for Enterprise Voice
  .DESCRIPTION
    Enables a User for Enterprise Voice and verifies its status
  .PARAMETER UserPrincipalName
    Required for Parameterset UserPrincipalName. UserPrincipalName of the User to be enabled.
  .PARAMETER Object
    Required for Parameterset Object. CsOnlineUser Object passed to the function to reduce query time.
  .PARAMETER Force
    Suppresses confirmation prompt unless -Confirm is used explicitly
  .EXAMPLE
    Enable-TeamsUserForEnterpriseVoice John@domain.com
 
    Enables John for Enterprise Voice
  .INPUTS
    System.String
  .OUTPUTS
    System.Void - If called directly
    Boolean - If called by another CmdLet
  .NOTES
    Simple helper function to enable and verify a User is enabled for Enterprise Voice
    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/Enable-TeamsUserForEnterpriseVoice.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')]
  [Alias('Enable-Ev')]
  [OutputType([Boolean])]
  param(
    [Parameter(Mandatory, Position = 0, ParameterSetName = 'Object', HelpMessage = 'CsOnlineUser Object')]
    [Object]$UserObject,

    [Parameter(ParameterSetName = 'Object', HelpMessage = 'AzureAdUserLicense Object')]
    [Object]$LicenseObject,

    [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(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' }

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

    # Preparing Splatting Object
    $parameters = $null
    $Parameters = @{
      'Called' = $Called
      'Force'  = $Force
    }

    #region Worker Function
    function EnableEV ($UserObject, $LicenseObject, $Called, $Force) {
      Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
      #TEST Id - may need to use SipAddress for better compatibility (users only)
      #$Id = $($UserObject.SipAddress)
      $Id = $($UserObject.UserPrincipalName)
      Write-Verbose -Message "[PROCESS] Enabling User '$Id' for Enterprise Voice"

      if ( $UserObject.InterpretedUserType -notmatch 'User' ) {
        $Message = "Object '$Id' is not a User!"
        if ($Called) {
          Write-Warning -Message $Message
          return $false
        }
        else {
          throw [System.InvalidOperationException]::New("$Message")
        }
      }
      elseif ( -not $LicenseObject.PhoneSystem ) {
        $Message = "'$Id' Enterprise Voice Status: User is not licensed correctly (PhoneSystem required)!"
        if ($Called) {
          Write-Warning -Message $Message
          return $false
        }
        else {
          throw [System.InvalidOperationException]::New("$Message")
        }
        return $(if ($Called) { $false })
      }
      elseif ( -not [string]$LicenseObject.PhoneSystemStatus.contains('Success') ) {
        $Message = "'$Id' Enterprise Voice Status: User is not licensed correctly (PhoneSystem required to be enabled)!"
        if ($Called) {
          Write-Warning -Message $Message
          return $false
        }
        else {
          throw [System.InvalidOperationException]::New("$Message")
        }
      }
      elseif ( $UserObject.EnterpriseVoiceEnabled -and -not $Force ) {
        if ($Called) {
          return $true
        }
        else {
          Write-Verbose -Message "'$Id' Enterprise Voice Status: User is already enabled!" -Verbose
          Set-CsPhoneNumberAssignment -Identity $Id -EnterpriseVoiceEnabled $TRUE -ErrorAction SilentlyContinue
        }
      }
      else {
        if ( $UserObject.EnterpriseVoiceEnabled ) {
          Write-Information "TRYING: '$Id' - Enterprise Voice Status: Enabled, trying to re-enable with FORCE"
        }
        else {
          Write-Information "TRYING: '$Id' - Enterprise Voice Status: Not enabled, trying to enable"
        }
        try {
          if ($Force -or $PSCmdlet.ShouldProcess("$Id", 'Enabling User for EnterpriseVoice')) {
            try {
              Set-CsPhoneNumberAssignment -Identity $Id -EnterpriseVoiceEnabled $TRUE -ErrorAction STOP
            }
            catch {
              #Writing error of Set-CsPhoneNumberAssignment to debug stream
              Write-Warning -Message "Enablement with 'Set-CsPhoneNumberAssignment' did not work, trying legacy method!"
              " Function: $($MyInvocation.MyCommand.Name) - Set-CsPhoneNumberAssignment threw exception:", ($($_.Exception.Message) | Out-String).Trim() | Write-Debug
              Set-CsUser -Identity $Id -EnterpriseVoiceEnabled $TRUE -HostedVoiceMail $TRUE -ErrorAction STOP
            }
            $i = 0
            $iMax = 20
            $Activity = 'Enable User For Enterprise Voice'
            $Status = 'Azure Active Directory is propagating Object. Please wait'
            $Operation = 'Waiting for Get-CsOnlineUser to return a Result'
            Write-Verbose -Message "$Status - $Operation"
            do {
              if ($i -gt $iMax) {
                Write-Error -Message "'$Id' - Enterprise Voice Status: FAILED (User status has not changed in the last $iMax Seconds" -Category LimitsExceeded -RecommendedAction 'Please verify Object has been enabled (EnterpriseVoiceEnabled)'
                return $false
              }
              Write-Progress -Id 0 -Activity $Activity -Status $Status -CurrentOperation $Operation -SecondsRemaining $($iMax - $i) -PercentComplete (($i * 100) / $iMax)
              Start-Sleep -Milliseconds 1000
              $i++
            }
            while ( -not $(Get-CsOnlineUser -Identity "$($UserObject.UserPrincipalName)" -WarningAction SilentlyContinue).EnterpriseVoiceEnabled )
            Write-Progress -Id 0 -Activity $Activity -Completed

            if ($Called) {
              return $true
            }
            else {
              Write-Verbose -Message "'$Id' - Enterprise Voice Status: SUCCESS" -Verbose
            }
          }
        }
        catch {
          $Message = "'$Id' - Error enabling user for Enterprise Voice: $($_.Exception.Message)"
          if ($Called) {
            Write-Warning -Message $Message
            return $false
          }
          else {
            throw $_
          }
        }
      }
    }
    #endregion
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    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
            $LicenseObject = Get-AzureAdUserLicense "$User"
          }
          catch {
            Write-Error "'$User' not found" -Category ObjectNotFound
            continue
          }
          EnableEV -UserObject $CsUser -LicenseObject $LicenseObject @Parameters
        }
      }
      'Object' {
        Write-Verbose -Message "[PROCESS] Processing provided CsOnlineUser Object for '$($UserObject.UserPrincipalName)'"
        if ( -not $LicenseObject.IsPresent ) { $LicenseObject = Get-AzureAdUserLicense "$($UserObject.UserPrincipalName)" }
        EnableEV -UserObject $UserObject -LicenseObject $LicenseObject @Parameters
      }
    }
  } #process

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