Public/UserManagement/TeamsCommonAreaPhone/Set-TeamsCommonAreaPhone.ps1

# Module: TeamsFunctions
# Function: VoiceConfig
# Author: David Eberhardt
# Updated: 24-MAY-2021
# Status: RC




function Set-TeamsCommonAreaPhone {
  <#
  .SYNOPSIS
    Changes settings for a Common Area Phone
  .DESCRIPTION
    Applies settings relevant to a Common Area Phone.
    This includes DisplayName, UsageLocation, License, IP Phone Policy, Calling Policy and Call Park Policy can be applied.
  .PARAMETER UserPrincipalName
    Required for Parameterset UserPrincipalName. UserPrincipalName of a CsOnlineUser Object to be provisioned.
  .PARAMETER Object
    Required for Parameterset Object. CsOnlineUser Object passed to the function to reduce query time.
  .PARAMETER DisplayName
    Optional for Parameterset UserPrincipalName. The Name it will show up as in Teams.
    Invalid characters are stripped from the provided string.
    DisplayName is only allowed for singular objects or via PipelineByPropertyName
  .PARAMETER UsageLocation
    Required. Two Digit Country Code of the Location of the entity.
    Before a License can be assigned, the account needs a Usage Location populated.
  .PARAMETER License
    Optional. Specifies the License to be assigned: PhoneSystem or PhoneSystem_VirtualUser
    If not provided, will default to PhoneSystem_VirtualUser
    Unlicensed Objects can exist, but cannot be assigned a phone number
    PhoneSystem is an add-on license and cannot be assigned on its own. it has therefore been deactivated for now.
  .PARAMETER IPPhonePolicy
    Optional. Adds an IP Phone Policy to the User
  .PARAMETER TeamsCallingPolicy
    Optional. Adds a Calling Policy to the User
  .PARAMETER TeamsCallParkPolicy
    Optional. Adds a Call Park Policy to the User
  .PARAMETER PassThru
    Optional. Displays the Object after execution.
  .PARAMETER WriteErrorLog
    If Errors are encountered, writes log to C:\Temp
  .EXAMPLE
    Set-TeamsCommonAreaPhone -UserPrincipalName MyLobbyPhone@TenantName.onmicrosoft.com -Displayname "Lobby {Phone}"
 
    Changes the Object MyLobbyPhone@TenantName.onmicrosoft.com. DisplayName will be normalised to "Lobby Phone" and applied.
  .EXAMPLE
    Set-TeamsCommonAreaPhone -UserPrincipalName MyLobbyPhone@TenantName.onmicrosoft.com -UsageLocation US -License CommonAreaPhone
 
    Changes the Object MyLobbyPhone@TenantName.onmicrosoft.com. Usage Location is set to 'US' and the CommonAreaPhone License is assigned.
  .EXAMPLE
    Set-TeamsCommonAreaPhone -UserPrincipalName MyLobbyPhone@TenantName.onmicrosoft.com -License Office365E3,PhoneSystem
 
    Changes the Object MyLobbyPhone@TenantName.onmicrosoft.com. Usage Location is required to be set. Assigns the Office 365 E3 License as well as PhoneSystem
  .EXAMPLE
    Set-TeamsCommonAreaPhone -UserPrincipalName "MyLobbyPhone@TenantName.onmicrosoft.com" -IPPhonePolicy "My IPP" -TeamsCallingPolicy "CallP" -TeamsCallParkPolicy "CallPark" -PassThru
 
    Applies IPPhonePolicy, TeamsCallingPolicy and TeamsCallParkPolicy to the Common Area Phone
    Displays the Common Area Phone Object afterwards
  .EXAMPLE
    Set-TeamsCommonAreaPhone -UserPrincipalName "MyLobbyPhone@TenantName.onmicrosoft.com" -IPPhonePolicy "My IPP" -WriteErrorLog
 
    Applies IPPhonePolicy to the Common Area Phone.
    If Errors are encountered, they are written to C:\Temp as well as on screen
  .INPUTS
    System.String
  .OUTPUTS
    System.Void - Default Behaviour
    System.Object - With Switch PassThru
    System.File - With Switch WriteErrorLog
  .NOTES
    Execution requires User Admin Role in Azure AD
    This CmdLet deliberately does not apply a Phone Number to the Object. To do so, please run Set-TeamsPhoneNumber
    or Set-TeamsUserVoiceConfig for a full Voice Configuration apply a Calling Plan or Online Voice Routing Policy
    a Phone Number and optionally a Tenant Dial Plan.
    This Script only covers relevant elements for Common Area Phones themselves.
  .COMPONENT
    UserManagement
  .FUNCTIONALITY
    Changes a Common Area Phone in AzureAD for use in Teams
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Set-TeamsCommonAreaPhone.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/
  #>


  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'UserPrincipalName')]
  [Alias('Set-TeamsCAP')]
  [OutputType([System.Void])]
  param (
    #TEST Whether both Object and Identity work
    [Parameter(Mandatory, Position = 0, ParameterSetName = 'Object', ValueFromPipeline)]
    [Object[]]$Object,

    #[Parameter(Mandatory, Position = 0, ParameterSetName = 'UserPrincipalName', ValueFromPipelineByPropertyName, HelpMessage = 'UPN of the Object to query.')]
    [Parameter(Mandatory, Position = 0, ParameterSetName = 'UserPrincipalName', ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = 'UPN of the Object to query.')]
    [ValidateScript( {
        If ($_ -match '@') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN'
        } })]
    [Alias('ObjectId', 'Identity')]
    [string[]]$UserPrincipalName,

    [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'UserPrincipalName', HelpMessage = 'Display Name for this Object')]
    [string]$DisplayName,

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Usage Location to assign')]
    [ValidateScript( {
        if ($_ -in $(&$global:TfAcSbTwoLetterCountryCode)) { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid ISO3166-alpha2 Two-Letter CountryCode. Use Intellisense for options'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbTwoLetterCountryCode })]
    [string]$UsageLocation,

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'License to be assigned')]
    [ValidateScript( {
        if ($_ -in $(&$global:TfAcSbAzureAdLicense)) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid License. Use Intellisense for options or Get-AzureAdLicense (ParameterName)'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbAzureAdLicense })]
    [string[]]$License,

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'IP Phone Policy')]
    [ValidateScript( {
        if ($_ -in $(&$global:TfAcSbTeamsIPPhonePolicy)) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid Policy in the Tenant. Use Intellisense for options'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbTeamsIPPhonePolicy })]
    [string]$IPPhonePolicy,

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Teams Calling Policy')]
    [ValidateScript( {
        if ($_ -in $(&$global:TfAcSbTeamsCallingPolicy)) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid Policy in the Tenant. Use Intellisense for options'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbTeamsCallingPolicy })]
    [string]$TeamsCallingPolicy,

    [Parameter(ValueFromPipelineByPropertyName, HelpMessage = 'Teams Call Park Policy')]
    [ValidateScript( {
        if ($_ -in $(&$global:TfAcSbTeamsCallParkPolicy)) { return $true } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid Policy in the Tenant. Use Intellisense for options'
        } })]
    [ArgumentCompleter({ &$global:TfAcSbTeamsCallParkPolicy })]
    [string]$TeamsCallParkPolicy,

    [Parameter(HelpMessage = 'No output is written by default, Switch PassThru will return changed object')]
    [switch]$PassThru,

    [Parameter(HelpMessage = 'Writes a Log File to C:\Temp')]
    [switch]$WriteErrorLog

  ) #param

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

    # Asserting AzureAD Connection
    if ( -not $script:TFPSSA) { $script:TFPSSA = Assert-AzureADConnection; if ( -not $script:TFPSSA ) { break } }

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

    #Initialising Counters
    $private:StepsID0, $private:StepsID1 = Get-WriteBetterProgressSteps -Code $($MyInvocation.MyCommand.Definition) -MaxId 1
    $private:ActivityID0 = $($MyInvocation.MyCommand.Name)
    [int] $private:CountID0 = [int] $private:CountID1 = 1

    $StatusID0 = 'Verifying input'
    #region Validating Licenses to be applied result in correct Licensing (contains Teams & PhoneSystem)
    $PlansToTest = 'TEAMS1', 'MCOEV'
    if ( $PSBoundParameters.ContainsKey('License') ) {
      $CurrentOperationID0 = 'Validating Licenses to be applied result in correct Licensing'
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      $IncludesTeams = 0
      $IncludesPhoneSystem = 0
      foreach ($L in $License) {
        if (Test-AzureAdLicenseContainsServicePlan -License "$L" -ServicePlan $PlansToTest[0]) {
          $IncludesTeams++
          Write-Verbose -Message "License '$L' ServicePlan '$($PlansToTest[0])' - Included: OK"
        }
        else {
          Write-Verbose -Message "License '$L' ServicePlan '$($PlansToTest[0])' - NOT included"
        }
        if (Test-AzureAdLicenseContainsServicePlan -License "$L" -ServicePlan $PlansToTest[1]) {
          $IncludesPhoneSystem++
          Write-Verbose -Message "License '$L' ServicePlan '$($PlansToTest[1])' - Included: OK"
        }
        else {
          Write-Verbose -Message "License '$L' ServicePlan '$($PlansToTest[1])' - NOT included"
        }
      }
      if ( $IncludesTeams -lt 1 -and $IncludesPhoneSystem -lt 1 ) {
        Write-Warning -Message "ServicePlan validation - None of the Licenses include both of the required ServicePlans '$PlansToTest' - Account may not be operational!"
      }
    }

    if ( $PSBoundParameters.ContainsKey('DisplayName')) {
      if ( $Object.IsArray -or $UserPrincipalName.IsArray ) {
        Write-Error -Message 'Usage of DisplayName is not allowed in conjunction with an Array Object to avoid issues. Please provide input via Pipeline or for individual objects' -ErrorAction Stop
        $PSBoundParameters.Remove('DisplayName')
      }
    }
    #endregion

    # Worker function to apply settings once accounts have been ascertained
    function SetTeamsCAP {
      [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
      param (
        [Parameter(Mandatory, HelpMessage = 'CsOnlineUser Object')]
        [Object[]]$UserObject,

        [Parameter(HelpMessage = 'Display Name for this Object')]
        [string]$DisplayName,

        [Parameter(HelpMessage = 'Usage Location to assign')]
        [string]$UsageLocation,

        [Parameter(HelpMessage = 'License to be assigned')]
        [string[]]$License,

        [Parameter(HelpMessage = 'IP Phone Policy')]
        [string]$WFIPPhonePolicy,

        [Parameter(HelpMessage = 'Teams Calling Policy')]
        [string]$WFTeamsCallingPolicy,

        [Parameter(HelpMessage = 'Teams Call Park Policy')]
        [string]$WFTeamsCallParkPolicy,

        [Parameter(HelpMessage = 'No output is written by default, Switch PassThru will return changed object')]
        [switch]$PassThru
      )

      begin {
        Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)"
        #Creating splatting object - Common Parameters
        $SetAzureADUserParams = $Parameters = @{
          'ErrorAction' = 'STOP'
        }
      } #begin

      process {
        Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
        #region PREPARATION
        $StatusID0 = 'Verifying input'
        #region Normalising DisplayName
        $CurrentOperationID0 = 'Normalising DisplayName'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        if ($PSBoundParameters.ContainsKey('DisplayName')) {
          $Name = Format-StringForUse -InputString $DisplayName -As DisplayName
          Write-Verbose -Message "DisplayName normalised to: '$Name'"
          $SetAzureADUserParams += @{ 'DisplayName' = "$Name" }
        }
        else {
          $Name = $UserObject.DisplayName
        }
        #endregion

        #region UsageLocation
        $CurrentOperationID0 = 'UsageLocation'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        $CurrentUsageLocation = $UserObject.UsageLocation
        if ($PSBoundParameters.ContainsKey('UsageLocation')) {
          if ($Usagelocation -eq $CurrentUsageLocation) {
            Write-Verbose -Message "'$Name' Usage Location already set to: $CurrentUsageLocation"
          }
          else {
            Write-Verbose -Message "'$Name' Usage Location will be set to: $Usagelocation"
            $SetAzureADUserParams += @{ 'UsageLocation' = "$UsageLocation" }
          }
        }
        else {
          if ($null -ne $CurrentUsageLocation) {
            Write-Verbose -Message "'$Name' Usage Location currently set to: $CurrentUsageLocation"
          }
          else {
            if ($PSBoundParameters.ContainsKey('License')) {
              Write-Error -Message "'$Name' Usage Location not set!" -Category ObjectNotFound -RecommendedAction 'Please run command again and specify -UsageLocation'# -ErrorAction Stop
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog "'$Name' Usage Location not set!" -Artifact $UserPrincipalName
              }
              return
            }
            else {
              Write-Warning -Message "'$Name' Usage Location not set! This is a requirement for License assignment and Phone Number"
            }
          }
        }
        #endregion

        #region Current License
        $CurrentOperationID0 = 'Querying current License and Testing Licensing Scope (Should contain Teams and PhoneSystem)'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        $IsLicensed = $false
        # Determining license Status of Object
        $UserLicense = Get-AzureAdUserLicense -Identity "$($UserObject.UserPrincipalName)"
        if ( $UserLicense ) {
          $ServicePlan1 = $UserLicense.ServicePlans | Where-Object ServicePlanName -EQ "$($PlansToTest[0])"
          $ServicePlan2 = $UserLicense.ServicePlans | Where-Object ServicePlanName -EQ "$($PlansToTest[1])"
          if ($ServicePlan1.Provisioningstatus -eq 'Success' -and $ServicePlan2.Provisioningstatus -eq 'Success' ) {
            Write-Verbose -Message "'$Name' Service Plans for Teams & PhoneSystem are enabled successfully"
            $IsLicensed = $true
          }
        }
        else {
          Write-Verbose -Message "'$Name' Current License assigned: NONE"
        }
        #endregion
        #endregion

        #region ACTION
        $StatusID0 = 'Applying AzureAdUser Settings'
        #region Setting AzureAd Object
        $CurrentOperationID0 = 'Applying Settings'
        Write-BetterProgress -Id 1 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        try {
          if ($PSBoundParameters.ContainsKey('Debug') -or $DebugPreference -eq 'Continue') {
            "Function: $($MyInvocation.MyCommand.Name) - Parameters (Set-AzureADUser)", ($Parameters | Format-Table -AutoSize | Out-String).Trim() | Write-Debug
          }
          if ($PSCmdlet.ShouldProcess("$($UserObject.UserPrincipalName)", 'Set-AzureAdUser')) {
            $null = Set-AzureADUser @SetAzureADUserParams
          }
          else {
            return
          }
        }
        catch {
          # Catching anything
          throw [System.Management.Automation.SetValueException] "Application of settings failed: $($_.Exception.Message)"
          return
        }
        #endregion

        #region Licensing
        $CurrentOperationID0 = 'Processing License assignment'
        Write-BetterProgress -Id 1 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        if ($PSBoundParameters.ContainsKey('License')) {
          if ( $License -in $UserLicense.Licenses.ParameterName -and $IsLicensed ) {
            # No action required
            Write-Information "INFO: User '$Name ($($UserObject.UserPrincipalName))' License '$License' already assigned."
            $IsLicensed = $true
          }
          else {
            try {
              if ($PSCmdlet.ShouldProcess("$($UserObject.UserPrincipalName)", "Set-TeamsUserLicense -Add $License")) {
                $null = (Set-TeamsUserLicense -Identity "$($UserObject.UserPrincipalName)" -Add $License -ErrorAction STOP)
                Write-Information "INFO: User '$Name' License assignment - '$License' SUCCESS"
                $IsLicensed = $true
              }
            }
            catch {
              $ErrorLog = "'$Name' License assignment failed for '$License' with Exception: '$($_.Exception.Message)'"
              Write-Error -Message $ErrorLog
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserPrincipalName
              }
            }
          }
        }
        #endregion

        $StatusID0 = 'Applying CsOnlineUser Policies'
        #region Policy application
        if ( -not $IsLicensed ) {
          $ErrorLog = 'Policies can only be assigned to licensed objects. Please wait for propagation or apply a license before assigning policies.'
          Write-Error -Message $ErrorLog -Category ResourceUnavailable -RecommendedAction 'Please apply a license before assigning any Policy.'
          if ( $WriteErrorLog.IsPresent ) {
            Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserPrincipalName
          }
        }
        else {
          #region Teams IP Phone Policy
          $PolicyName = 'Teams IP Phone Policy'
          $CurrentOperationID0 = "Processing $PolicyName"
          Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
          if ($PSBoundParameters.ContainsKey('IPPhonePolicy')) {
            try {
              Grant-CsTeamsIPPhonePolicy -Identity $UserObject.Identity -PolicyName $IPPhonePolicy -ErrorAction Stop
            }
            catch {
              $ErrorLog = $_.Exception.Message
              Write-Error -Message $ErrorLog
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserObject.UserPrincipalName
              }
            }
          }
          elseif ( $UserObject.TeamsIPPhonePolicy ) {
            Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $PolicyName '$($UserObject.TeamsIPPhonePolicy)' present"
          }
          else {
            Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $PolicyName not assigned - Dynamic configuration may be present (undetermined)"
          }
          #endregion

          #region Teams Calling Policy
          $PolicyName = 'Teams Calling Policy'
          $CurrentOperationID0 = "Processing $PolicyName"
          Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
          if ($PSBoundParameters.ContainsKey('TeamsCallingPolicy')) {
            try {
              Grant-CsTeamsCallingPolicy -Identity $UserObject.Identity -PolicyName $TeamsCallingPolicy -ErrorAction Stop
            }
            catch {
              $ErrorLog = $_.Exception.Message
              Write-Error -Message $ErrorLog
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserObject.UserPrincipalName
              }
            }
          }
          elseif ( $UserObject.TeamsCallingPolicy ) {
            Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $PolicyName '$($UserObject.TeamsCallingPolicy)' present"
          }
          else {
            Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $PolicyName not assigned - Dynamic configuration may be present (undetermined)"
          }
          #endregion

          #region Teams Call Park Policy
          $PolicyName = 'Teams Call Park Policy'
          $CurrentOperationID0 = "Processing $PolicyName"
          Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
          if ($PSBoundParameters.ContainsKey('TeamsCallParkPolicy')) {
            try {
              Grant-CsTeamsCallParkPolicy -Identity $UserObject.Identity -PolicyName $TeamsCallParkPolicy -ErrorAction Stop
            }
            catch {
              $ErrorLog = $_.Exception.Message
              Write-Error -Message $ErrorLog
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $UserObject.UserPrincipalName
              }
            }
          }
          elseif ( $UserObject.TeamsCallParkPolicy ) {
            Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $PolicyName '$($UserObject.TeamsCallParkPolicy)' present"
          }
          else {
            Write-Verbose -Message "Object '$($UserObject.UserPrincipalName)' - $PolicyName not assigned - Dynamic configuration may be present (undetermined)"
          }
          #endregion
        }
        #endregion
        #endregion

        #region OUTPUT
        $CurrentOperationID0 = 'Validation & Output'
        Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
        $TeamsUVCObject = $null
        if ( $PassThru ) {
          $TeamsUVCObject = Get-TeamsUserVoiceConfig -UserPrincipalName "$($UserObject.UserPrincipalName)" -DiagnosticLevel 1 -WarningAction SilentlyContinue
          Write-Output $TeamsUVCObject | Select-Object UserPrincipalName, Identity, InterpretedUserType, TeamsEmergencyCallingPolicy, TeamsEmergencyCallRoutingPolicy, TeamsEmergencyAddress
        }
        Write-Progress -Id 0 -Activity $ActivityID0 -Completed
        #endregion

      } #process

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

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

    #TODO Check whether this can be passed with $Args (removing the UPN or Object respectively!)
    if ( $PSBoundParameters.ContainsKey('DisplayName') ) { $Parameters += @{ 'DisplayName' = $DisplayName } }
    if ( $PSBoundParameters.ContainsKey('UsageLocation') ) { $Parameters += @{ 'UsageLocation' = $UsageLocation } }
    if ( $PSBoundParameters.ContainsKey('License') ) { $Parameters += @{ 'License' = $License } }
    if ( $PSBoundParameters.ContainsKey('IPPhonePolicy') ) { $Parameters += @{ 'WFIPPhonePolicy' = $IPPhonePolicy } }
    if ( $PSBoundParameters.ContainsKey('TeamsCallingPolicy') ) { $Parameters += @{ 'WFTeamsCallingPolicy' = $TeamsCallingPolicy } }
    if ( $PSBoundParameters.ContainsKey('TeamsCallParkPolicy') ) { $Parameters += @{ 'WFTeamsCallParkPolicy' = $TeamsCallParkPolicy } }
    if ( $PassThru.IsPresent ) { $Parameters += @{ 'PassThru' = $PassThru } }

  } #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
            }
            catch {
              $ErrorLog = "'$User' not found"
              Write-Error -Message $ErrorLog -Category ObjectNotFound
              if ( $WriteErrorLog.IsPresent ) {
                Write-TFErrorLog -ErrorLog $ErrorLog -Artifact $User
              }
              continue
            }
            if ($Force -or $PSCmdlet.ShouldProcess("$($CsUser.UserPrincipalName)", 'Set Common Area Phone')) {
              SetTeamsCAP -UserObject $CsUser @Parameters
            }
          }
        }
        'Object' {
          foreach ($O in $Object) {
            Write-Verbose -Message "[PROCESS] Processing provided CsOnlineUser Object for '$($O.UserPrincipalName)'"
            if ($Force -or $PSCmdlet.ShouldProcess("$($CsUser.UserPrincipalName)", 'Set Common Area Phone')) {
              SetTeamsCAP -UserObject $O @Parameters
            }
          }
        }
      }
    }
    catch {
      Write-Error -Message $($_.Exception.Message) -ErrorAction $ErrorActionPreference
    }
  } #process

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