Public/UserCallingSettings/Set-TeamsUserDelegate.ps1

# Module: Orbit.Teams
# Function: VoiceConfig
# Author: David Eberhardt
# Updated: 20-OCT-2022
# Status: RC




function Set-TeamsUserDelegate {
  <#
  .SYNOPSIS
    Sets CallingSettings (Delegates) for one or more Users
  .DESCRIPTION
    Adds or sets one or more Delegates for one or more Users and applies the specified settings for all Delegates listed.
    Wrapper for Set-CsUserCallingDelegate that allows adding delegates (which is normally performed by New-CsUserCallingDelegate)
  .PARAMETER UserPrincipalName
    Required. UserPrincipalName (UPN) of the User(s) to add a delegate for. Can be specified using the ObjectId or the SIP address.
  .PARAMETER Delegate
    Required. The Identity of one or more delegates to add. Can be specified using the ObjectId or the SIP address.
    Selected settings will be set for all Delegates
  .PARAMETER MakeCalls
    Optional. Specifies whether delegate is allowed to make calls on behalf of the specified user
  .PARAMETER ReceiveCalls
    Optional. Specifies whether delegate is allowed to receive calls on behalf of the specified user
    If the Delegate does not exist and will be added, this parameter will be set to TRUE by this command.
  .PARAMETER ManageSettings
    Optional. Specifies whether delegate is allowed to change the delegate and calling settings for the specified user.
  .PARAMETER PassThru
    Optional. Displays object after applying settings
  .PARAMETER Force
    Suppresses confirmation prompt unless -Confirm is used explicitly.
    Tries to enable users and delegates for Enterprise Voice if they are not yet enabled.
  .PARAMETER WriteErrorLog
    Optional. If Errors are encountered, writes log to C:\Temp
  .EXAMPLE
    Set-TeamsUserDelegate [-UserPrincipalName] John@domain.com -Delegate Jill@domain.com -MakeCalls $true -WriteErrorLog
 
    Adds Jill as a Delegate to John or updates the existing settings
    Redirection to the Group will be disabled if it is used as an UnansweredTarget or as an AlsoRing/ForwardingTarget
    If errors are encountered, will write an Error-Log to C:\Temp
  .EXAMPLE
    Set-TeamsUserDelegate [-UserPrincipalName] John@domain.com -Add Jill@domain.com -Remove Jeff@domain.com
 
    Adds Jill as a member of John's Call Group and removes Jeff
  .EXAMPLE
    "John@domain.com" | Set-TeamsUserDelegate -DelegateTargets Jill@domain.com,Jeff@domain.com -TargetInOrder
 
    Replaces all members of John's Call Group (if any) and only adds Jill & Jeff. Also activates Call Group targeting in Order.
    InOrder is only available for Call Groups with 5 or less members.
  .EXAMPLE
    "John@domain.com","Jill@domain.com","Jeff@domain.com" | Set-TeamsUserDelegate -DelegateTargets Jill@domain.com,Jeff@domain.com,John@domain.com
 
    Replaces all members of John's, Jill's & Jeff's Call Group (if any) and only adds John's, Jill's & Jeff's.
    Users cannot be added to their own call group, so they will be removed. John will have Jeff & Jill as members,
    Jill will have John & Jeff and Jeff will have John & Jill.
    This is useful for bigger teams that all should have the same Call Group settings
  .EXAMPLE
    Set-TeamsUserDelegate Jill@domain.com -NotificationSetting Banner
 
    Sets Calling Settings for Jill in ALL Call Groups Jill is a member in to Banner. This is not the same as the override
  .EXAMPLE
    Set-TeamsUserDelegate Jeff@domain.com -GroupNotificationOverride Banner
 
    Sets Calling Settings for Jeff to override all Call Group Notifications for Jeff with Banner
    This overrides settings made by other userss in their Call Group as Jeff does not want to be rang for Call Group Calls.
  .INPUTS
    System.String
  .OUTPUTS
    System.Void
    System.Object
  .NOTES
    Wrapper for Set-CsUserCallingDelegate with a twist.
    Also replaces New-CsUserCallingDelegate as it adds the delegate if it is not needed.
    Allows for adding multiple delegates with the same settings.
    Default value for ReceiveCalls is changed: If not provided, defaults to true for newly added Delegates.
    For existing Delegates, value is only changed if provided to retain consistency. Other parameters are unchanged.
    For CallingSettings invoking AlsoRing, IfUnanswered or Forwarding please call Set-TeamsUserCallingSettings.
  .COMPONENT
    VoiceConfiguration
  .FUNCTIONALITY
    Applies User Calling Settings for Call Groups
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Teams/Set-TeamsUserDelegate.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_VoiceConfiguration.md
  .LINK
    https://github.com/DEberhardt/Orbit/tree/main/docs/
  #>


  [CmdletBinding( SupportsShouldProcess, PositionalBinding = $false )]
  [Alias('Set-TeamsUD')]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('ObjectId', 'Identity', 'Delegator', 'SipUri')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_) -or $_ -match '^[0-9a-f]{8}-([0-9a-f]{4}\-){3}[0-9a-f]{12}$') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$UserPrincipalName,

    [Parameter(Mandatory, ValueFromPipelineByPropertyName, HelpMessage = 'The Identity of the delegate to add. Can be specified using the ObjectId or the SIP address.')]
    [Alias('Id')]
    [ValidateScript( {
        If ($script:OrbitRegexUPN.isMatch($_) -or $_ -match '^[0-9a-f]{8}-([0-9a-f]{4}\-){3}[0-9a-f]{12}$') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId'
        } })]
    [string[]]$Delegate,

    [Parameter(HelpMessage = 'Specifies whether delegate is allowed to make calls on behalf of the specified user.')]
    [boolean]$MakeCalls,

    [Parameter(HelpMessage = 'Specifies whether delegate is allowed to receive calls on behalf of the specified user.')]
    [boolean]$ReceiveCalls,

    [Parameter(HelpMessage = 'Specifies whether delegate is allowed to change the delegate and calling settings for the specified user.')]
    [boolean]$ManageSettings,

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

    [Parameter(HelpMessage = 'Tries to bring enable users for Enterprise Voice if they are not enabled.')]
    [switch]$Force,

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

  ) #param

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

    # Asserting MicrosoftTeams Connection
    if ( -not (Assert-MicrosoftTeamsConnection) ) { throw 'Connection to Microsoft Teams not established. Please validate connection' }

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

  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    foreach ($User in $UserPrincipalName) {
      #region Querying Identity
      $UserCallingSettings = $null
      try {
        $UserCallingSettings = Get-CsUserCallingSettings -Identity $User -ErrorAction Stop
      }
      catch {
        if ( $_.Exception.Message.Contains('Could not resolve user') -or $_.Errordetails.Message.Contains('was not found') ) {
          $ErrorMessage = "User '$User' - User not found"
          Write-Verbose $ErrorMessage
        }
        else {
          $ErrorMessage = "User '$User' - Error: $($_.Errordetails.Message)"
          Write-Error -Message $ErrorMessage
        }
        if ( $WriteErrorLog.IsPresent ) { Write-OrbitErrorLog -ErrorLog $ErrorMessage -Artifact "$User" }
        continue
      }

      $CurrentDelegates = $null
      $CurrentDelegates = $UserCallingSettings.Delegates
      #endregion

      #region Processing Delegates
      foreach ( $Dele in $Delegate ) {
        #region Querying Delegate calling settings - if this fails - this indicates that the user is not enabled
        try {
          $DelegateUser = Get-CsOnlineUser -Identity $Dele -ErrorAction Stop
          #$DelegateCallingSettings = Get-CsUserCallingSettings -Identity $Dele -ErrorAction Stop

          # If delegate is not found in the current list of Delegates, it will be added as a new delegate with default settings (ReceiveCalls)
          $NewDelegate = ($DelegateUser.SipAddress -in $CurrentDelegates.Id)
        }
        catch {
          if ( $_.Exception.Message.Contains('Could not resolve user') -or $_.Errordetails.Message.Contains('was not found') ) {
            $ErrorMessage = "Delegate '$User' - User not found"
            Write-Verbose $ErrorMessage
          }
          else {
            $ErrorMessage = "Delegate '$User' - Error: $($_.Errordetails.Message)"
            Write-Error -Message $ErrorMessage
          }
          if ( $WriteErrorLog.IsPresent ) { Write-OrbitErrorLog -ErrorLog $ErrorMessage -Artifact "$User" }
          continue
        }
        #endregion

        #region Asserting Delegate state
        try {
          $Assertion = $null
          $Assertion = Assert-TeamsCallableEntity -Object $DelegateUser -RequireEV -InformationAction SilentlyContinue -WarningAction SilentlyContinue -ErrorAction Stop
          if ($Assertion) {
            # Preparing Splatting Object
            $CsUserCallingDelegate = $null
            $CsUserCallingDelegate = @{
              Identity    = $UserCallingSettings.SipUri
              Delegate    = $DelegateUser.SipAddress
              ErrorAction = 'Stop'
            }
          }
          else {
            Write-Warning -Message "Delegate '$($DelegateUser.UserPrincipalName)' could not be enabled for EnterpriseVoice - Omitting Delegate"
            continue
          }
        }
        catch {
          Write-Warning -Message "Delegate '$($DelegateUser.UserPrincipalName)' - Assertion failed with Error: $($_.Exception.Message)"
          continue
        }
        #endregion

        #region SETTINGS
        if ( -not $PSBoundParameters['MakeCalls'] -and `
            -not $PSBoundParameters['ReceiveCalls'] -and `
            -not $PSBoundParameters['ManageSettings']
        ) {
          Write-Error -Message "User '$User' - No Settings to be applied."
          break
        }
        else {
          if ( $NewDelegate ) {
            $CsUserCallingDelegate.MakeCalls = if ( $PSBoundParameters['MakeCalls'] ) { $CsUserCallingDelegate.MakeCalls = $MakeCalls } else { $false }
            $CsUserCallingDelegate.ReceiveCalls = if ( $PSBoundParameters['ReceiveCalls'] ) { $CsUserCallingDelegate.ReceiveCalls = $ReceiveCalls } else { $true }
            $CsUserCallingDelegate.MakeCalls = if ( $PSBoundParameters['ManageSettings'] ) { $CsUserCallingDelegate.ManageSettings = $ManageSettings } else { $false }
          }
          else {
            if ( $PSBoundParameters['MakeCalls'] ) { $CsUserCallingDelegate.MakeCalls = $MakeCalls }
            if ( $PSBoundParameters['ReceiveCalls'] ) { $CsUserCallingDelegate.ReceiveCalls = $ReceiveCalls }
            if ( $PSBoundParameters['ManageSettings'] ) { $CsUserCallingDelegate.ManageSettings = $ManageSettings }
          }
        }
        #endregion

        #region ACTION
        try {
          if ( $NewDelegate ) {
            # New Delegate will be added
            Write-Verbose -Message "User '$($CsUserCallingDelegate.Identity)'; Delegate '$($CsUserCallingDelegate.Delegate) - Delegate not present, adding delegate" -Verbose
            if ($Force -or $PSCmdlet.ShouldProcess("Calling New-CsUserCallingDelegate adding $($CsUserCallingDelegate.Delegate)")) {
              New-CsUserCallingDelegate @CsUserCallingDelegate -ErrorAction Stop
            }
          }
          else {
            # Re-apply/change
            Write-Verbose -Message "User '$($CsUserCallingDelegate.Identity)'; Delegate '$($CsUserCallingDelegate.Delegate) - Delegate already set, updating record"
            if ($Force -or $PSCmdlet.ShouldProcess("Calling Set-CsUserCallingDelegate adding $($CsUserCallingDelegate.Delegate)")) {
              Set-CsUserCallingDelegate @CsUserCallingDelegate -ErrorAction Stop
            }
          }
        }
        catch {
          #REST Errors behave differently. $_.Exception.Message only shows "Exception of type 'System.Exception' was thrown."
          $ExceptionMessage = $_.Errordetails.Message
          if ( $ExceptionMessage.Contains('Object not in correct state') ) {
            $ErrorMessage = "User '$($CsUserCallingDelegate.Identity)'; Delegate '$($CsUserCallingDelegate.Delegate) - Delegate not in the correct state: $ExceptionMessage "
            Write-Verbose $ErrorMessage
          }
          else {
            $ErrorMessage = "User '$($CsUserCallingDelegate.Identity)'; Delegate '$($CsUserCallingDelegate.Delegate) - Error applying settings: $ExceptionMessage "
            Write-Error -Message $ErrorMessage
          }
          if ( $WriteErrorLog.IsPresent ) { Write-OrbitErrorLog -ErrorLog $ErrorMessage -Artifact "$User" }
          continue
        }
        #endregion
      }
      #endregion


      # OUTPUT
      $OutputObject = $null
      if ( $PassThru ) {
        $OutputObject = Get-TeamsUserCallingSettings -UserPrincipalName $User -Show Delegates
      }
      Write-Output $OutputObject
    }
  } #process

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