Public/UserManagement/TeamsUserCallingSettings/Set-TeamsUserCallGroup.ps1

# Module: TeamsFunctions
# Function: VoiceConfig
# Author: David Eberhardt
# Updated: 16-OCT-2022
# Status: RC




function Set-TeamsUserCallGroup {
  <#
  .SYNOPSIS
    Sets CallingSettings (Call Group) for one or more Users
  .DESCRIPTION
    Adds, removes or sets Call Group Targets, Sets Call Group Order and Notification as well as Notification Override
    for one or more users. Call Group settings are applied to all users (removing targeting itself).
    This is useful to apply the same Call Group settings to multiple users
  .PARAMETER UserPrincipalName
    Required. UserPrincipalName (UPN) of the User(s) to set Call Group settings for.
  .PARAMETER CallGroupTargets
    ParameterSet Option. Replaces Call Group with the defined targets excluding self.
  .PARAMETER Add
    ParameterSet Option. Adds Call Group target to defined targets excluding self.
  .PARAMETER Remove
    ParameterSet Option. Removes Call Group target from defined targets.
  .PARAMETER RemoveAll
    ParameterSet Option. Removes Call Group entirely.
  .PARAMETER NotificationSetting
    Optional. Updates the NotificationSetting for all Members of this users Call Group.
  .PARAMETER GroupNotificationOverride
    Optional. Updates the NotificationSetting for this user in any CallGroup this user is a member in.
  .PARAMETER TargetInOrder
    Optional Switch. Only available for Call Groups with 5 members or less.
    If the Call Group has more than five members after prossing parameters Add, Remove or CallGroupTargets,
    this switch is ignored. The CallGroupOrder is set to Simultaneous in this case.
  .PARAMETER WriteErrorLog
    Optional. If Errors are encountered, writes log to C:\Temp
  .PARAMETER PassThru
    Optional. Displays object after applying settings
  .EXAMPLE
    Set-TeamsUserCallGroup [-UserPrincipalName] John@domain.com -RemoveAll -WriteErrorLog
 
    Removes the Call Group Targets from the User and removes the Group from being used.
    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-TeamsUserCallGroup [-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-TeamsUserCallGroup -CallGroupTargets 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-TeamsUserCallGroup -CallGroupTargets 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-TeamsUserCallGroup 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-TeamsUserCallGroup 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-CsUserCallingSettings that targets Call Group settings.
    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/TeamsFunctions/tree/main/docs/Set-TeamsUserCallGroup.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_VoiceConfiguration.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/
  #>


  [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Set', PositionalBinding = $false )]
  [Alias('Set-TeamsUCG')]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('ObjectId', 'Identity', 'SipUri')]
    [ValidateScript( {
        If ($_ -match '@') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN'
        } })]
    [string[]]$UserPrincipalName,

    [Parameter(Mandatory, ParameterSetName = 'Set', HelpMessage = 'Targets for setting up a Call Group')]
    [ValidateScript( {
        If ($_ -match '@') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN'
        } })]
    [string[]]$CallGroupTargets,

    [Parameter(Mandatory, ParameterSetName = 'Add', HelpMessage = 'Targets for setting up a Call Group')]
    [Parameter(ParameterSetName = 'Remove', HelpMessage = 'Targets for setting up a Call Group')]
    [ValidateScript( {
        If ($_ -match '@') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN'
        } })]
    [string[]]$Add,

    [Parameter(Mandatory, ParameterSetName = 'Remove', HelpMessage = 'Targets for setting up a Call Group')]
    [Parameter(ParameterSetName = 'Add', HelpMessage = 'Targets for setting up a Call Group')]
    [ValidateScript( {
        If ($_ -match '@') { $True } else {
          throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN'
        } })]
    [string[]]$Remove,

    [Parameter(Mandatory, ParameterSetName = 'RemoveAll', HelpMessage = 'Removes the CallGroup entirely')]
    [switch]$RemoveAll,


    [Parameter(HelpMessage = 'NotificationSetting for Call Group Targets')]
    [ValidateSet('Ring', 'Mute', 'Banner')]
    [string]$NotificationSetting,

    [Parameter(HelpMessage = 'NotificationSetting Override for all Call Groups this user is a member in')]
    [ValidateSet('Ring', 'Mute', 'Banner')]
    [Alias('NotificationOverride')]
    [string]$GroupNotificationOverride,

    [Parameter(ParameterSetName = 'Set', HelpMessage = 'Tries to apply CallGroupOrder InOrder rather than Simultaneous')]
    [Parameter(ParameterSetName = 'Add', HelpMessage = 'Tries to apply CallGroupOrder InOrder rather than Simultaneous')]
    [Parameter(ParameterSetName = 'Remove', HelpMessage = 'Tries to apply CallGroupOrder InOrder rather than Simultaneous')]
    [switch]$TargetInOrder,

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

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

  ) #param

  begin {
    Show-FunctionStatus -Level RC
    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' }

    # Adding Types
    Add-Type -AssemblyName Microsoft.Open.AzureAD16.Graph.Client
    Add-Type -AssemblyName Microsoft.Open.Azure.AD.CommonLibrary

    # Worker Functions
    function AvailableCallGroupOrder ($User, $Count) {
      if ( $Count -gt 5 ) {
        Write-Warning -Message "User '$User' - CallGroupOrder 'InOrder' cannot be applied, too many CallGroupTargets detected. Applying 'Simultaneous'"
        return 'Simultaneous'
      }
      else {
        Write-Information "INFO: User '$User' - CallGroupOrder 'InOrder' can be applied ($Count Targets present)"
        return 'InOrder'
      }
    }
  } #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 not found"
          Write-Verbose $ErrorMessage
        }
        else {
          $ErrorMessage = "'$User' - Error: $($_.Errordetails.Message)"
          Write-Error -Message $ErrorMessage
        }
        if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorMessage -Artifact "$User" }
        continue
      }
      #endregion


      #region SETTINGS
      #region RemoveAll
      if ( $PSBoundParameters.ContainsKey('RemoveAll') ) {
        if ($PSCmdlet.ShouldProcess("Calling 'Set-CsUserCallingSettings' to REMOVE ALL CallGroupTargets for '$User'", "$User", 'Set-CsUserCallingSettings')) {
          #TEST REMOVE Command requires -IsUnansweredEnabled to be FALSE? - Run if needed.
          if ( $UserCallingSettings.UnansweredTargetType -eq 'Group' ) {
            $null = Set-CsUserCallingSettings -Identity "$User" -IsUnansweredEnabled $false
            Write-Verbose -Message "User '$User' - IsUnansweredEnabled was set to 'Group', This setting was reset"
          }
          if ( $UserCallingSettings.ForwardingTargetType -eq 'Group' ) {
            $null = Set-CsUserCallingSettings -Identity "$User" -IsForwardingEnabled $false
            Write-Verbose -Message "User '$User' - IsForwardingEnabled was set to 'Group', This setting was reset"
          }
          try {
            $CsUserCallingSettings = @{
              CallGroupOrder   = $UserCallingSettings.CallGroupOrder
              CallGroupTargets = @()
              ErrorAction      = 'Stop'
            }
            $null = Set-CsUserCallingSettings -Identity "$User" @CsUserCallingSettings
            Write-Information "INFO: User '$User' - CallGroupTargets removed; Call Group reset"
          }
          catch {
            $ErrorLog = $_
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorLog -Artifact "$User" }
          }
        }
        continue
      }
      #endregion


      #region Set
      if ( $PSBoundParameters.ContainsKey('CallGroupTargets') ) {
        Write-Verbose -Message "User '$User' - CallGroupTargets - Testing Call Targets to be added" -Verbose
        #$VerifiedCallGroupTargets = [Management.Automation.PSSerializer]::DeSerialize([Management.Automation.PSSerializer]::Serialize($CallGroupTargets))
        $VerifiedCallGroupTargets = @()
        $CallGroupTargets | ForEach-Object {
          $Target = $_
          if ( $user -eq $($Target -replace 'sip:', '') ) {
            Write-Verbose -Message "User '$User' - CallGroupTargets - Cannot add itself as a Call Target - omitting Target" -Verbose
          }
          else {
            try {
              if ($Target | Test-TeamsUserVoiceConfig -ErrorAction Stop) {
                $CallTarget = $(if ( $Target -notmatch '^sip:') { 'sip:' }) + $Target
                $VerifiedCallGroupTargets += $CallTarget
                Write-Verbose -Message "User '$User' - Target '$Target' is eligible as a call target and will be added"
              }
              else {
                throw
              }
            }
            catch {
              $ErrorMessage = "User '$User' - Target '$Target' is not eligible as a call target - omitting Target"
              Write-Warning -Message $ErrorMessage
              if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorMessage -Artifact "$User" }
            }
          }
        }

        $CallGroupOrder = if ( $PSBoundParameters.ContainsKey('TargetInOrder') ) {
          AvailableCallGroupOrder $User $VerifiedCallGroupTargets.Count
        }
        else {
          $UserCallingSettings.CallGroupOrder
        }

        if ($PSCmdlet.ShouldProcess("Calling 'Set-CsUserCallingSettings' to SET CallGroupTargets for '$User'", "$User", 'Set-CsUserCallingSettings')) {
          try {
            $CsUserCallingSettings = @{
              CallGroupOrder   = $CallGroupOrder
              CallGroupTargets = $VerifiedCallGroupTargets
              ErrorAction      = 'Stop'
            }
            $null = Set-CsUserCallingSettings -Identity "$User" @CsUserCallingSettings
            Write-Information "INFO: User '$User' - CallGroupTargets set"
          }
          catch {
            $ErrorLog = $_
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorLog -Artifact "$User" }
          }
        }
      }
      #endregion


      #region Add & Remove
      # Preparing UserCallGroupTargets Object for Add & Remove
      if ( $PSBoundParameters.ContainsKey('Add') -or $PSBoundParameters.ContainsKey('Remove') ) {
        $UserCallGroupTargets = { $UserCallingSettings.CallGroupTargets }.Invoke()
        if ($PSBoundParameters.ContainsKey('Debug') -or $DebugPreference -eq 'Continue') {
          "Function: $($MyInvocation.MyCommand.Name) - UserCallGroupTargets", ( $UserCallGroupTargets | Format-List | Out-String).Trim() | Write-Debug
        }
      }

      #region Add
      if ( $PSBoundParameters.ContainsKey('Add') ) {
        Write-Verbose -Message "User '$User' - Add - Testing Call Targets to be added" -Verbose
        $Add | ForEach-Object {
          $Target = $_
          if ( $user -eq $($Target -replace 'sip:', '') ) {
            Write-Verbose -Message "User '$User' - Add - Cannot add itself as a Call Target - omitting Target" -Verbose
          }
          else {
            try {
              if ($Target | Test-TeamsUserVoiceConfig -ErrorAction Stop) {
                $CallTarget = $(if ( $Target -notmatch '^sip:') { 'sip:' }) + $Target
                if ( $CallTarget -notin $UserCallGroupTargets ) {
                  [void]$UserCallGroupTargets.Add($CallTarget)
                  Write-Verbose -Message "User '$User' - Target '$Target' is eligible as a call target and will be added"
                }
                else {
                  Write-Verbose -Message "User '$User' - Target '$Target' already part of CallGroupTargets" -Verbose
                }
              }
              else {
                throw
              }
            }
            catch {
              $ErrorMessage = "User '$User' - Target '$Target' is not eligible as a call target - omitting Target"
              Write-Warning -Message $ErrorMessage
              if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorMessage -Artifact "$User" }
            }
          }
        }
        if ($PSBoundParameters.ContainsKey('Debug') -or $DebugPreference -eq 'Continue') {
          "Function: $($MyInvocation.MyCommand.Name) - UserCallGroupTargets after ADD", ( $UserCallGroupTargets | Format-List | Out-String).Trim() | Write-Debug
        }

        $CallGroupOrder = if ( $PSBoundParameters.ContainsKey('TargetInOrder') ) {
          AvailableCallGroupOrder $User $UserCallGroupTargets.Count
        }
        else {
          $UserCallingSettings.CallGroupOrder
        }

        if ($PSCmdlet.ShouldProcess("Calling 'Set-CsUserCallingSettings' to ADD CallGroupTargets for '$User'", "$User", 'Set-CsUserCallingSettings')) {
          try {
            $CsUserCallingSettings = @{
              CallGroupOrder   = $CallGroupOrder
              CallGroupTargets = $UserCallGroupTargets
              ErrorAction      = 'Stop'
            }
            $null = Set-CsUserCallingSettings -Identity "$User" @CsUserCallingSettings
            Write-Information "INFO: User '$User' - CallGroupTargets added"
          }
          catch {
            $ErrorLog = $_
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorLog -Artifact "$User" }
          }
        }
      }
      #endregion

      #region Remove
      if ( $PSBoundParameters.ContainsKey('Remove') ) {
        $Remove | ForEach-Object {
          $CallTarget = $(if ( $_ -notmatch '^sip:') { 'sip:' }) + $_
          if ( $CallTarget -in $UserCallGroupTargets) {
            [void]$UserCallGroupTargets.Remove($CallTarget)
            Write-Verbose -Message "User '$User' - Call Target '$CallTarget' removed to CallGroupTargets"
          }
          else {
            Write-Verbose -Message "User '$User' - Call Target '$CallTarget' is not part of the Call Group" -Verbose
          }
        }
        if ($PSBoundParameters.ContainsKey('Debug') -or $DebugPreference -eq 'Continue') {
          "Function: $($MyInvocation.MyCommand.Name) - UserCallGroupTargets after REMOVE", ( $UserCallGroupTargets | Format-List | Out-String).Trim() | Write-Debug
        }

        $CallGroupOrder = if ( $PSBoundParameters.ContainsKey('TargetInOrder') ) {
          AvailableCallGroupOrder $User $UserCallGroupTargets.Count
        }
        else {
          $UserCallingSettings.CallGroupOrder
        }

        if ($PSCmdlet.ShouldProcess("Calling 'Set-CsUserCallingSettings' to REMOVE CallGroupTargets for '$User'", "$User", 'Set-CsUserCallingSettings')) {
          try {
            $CsUserCallingSettings = @{
              CallGroupOrder   = $CallGroupOrder
              CallGroupTargets = $UserCallGroupTargets
              ErrorAction      = 'Stop'
            }
            $null = Set-CsUserCallingSettings -Identity "$User" @CsUserCallingSettings
            Write-Information "INFO: User '$User' - CallGroupTargets removed"
          }
          catch {
            $ErrorLog = $_
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorLog -Artifact "$User" }
          }
        }
      }
      #endregion
      #endregion

      # Other Settings
      #region GroupNotificationOverride
      if ( $PSBoundParameters.ContainsKey('GroupNotificationOverride') ) {
        if ($PSCmdlet.ShouldProcess("Calling 'Set-CsUserCallingSettings' to set CallGroupTargets for '$User'", "$User", 'Set-CsUserCallingSettings')) {
          try {
            $CsUserCallingSettings = @{
              GroupNotificationOverride = $GroupNotificationOverride
              ErrorAction               = 'Stop'
            }
            $null = Set-CsUserCallingSettings -Identity "$User" @CsUserCallingSettings
            Write-Information "INFO: User '$User' - GroupNotificationOverride set to $GroupNotificationOverride"
          }
          catch {
            $ErrorLog = $_
            Write-Error -Message $ErrorLog
            if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorLog -Artifact "$User" }
          }
        }
      }
      #endregion

      #region NotificationSetting
      if ( $PSBoundParameters.ContainsKey('NotificationSetting') ) {
        #TEST Whether CalLGroupTargets or GroupMemberShip is to be plugged in here - Fairly certain it is the CGT
        $UserCallingSettings.CallGroupTargets | ForEach-Object {
          Write-Verbose -Message "User '$User' - Processing CallGroupTarget '$_' - Querying UserCallingSettings"
          $GMD = (Get-CsUserCallingSettings -Identity $_).GroupMembershipDetails
          $GMD[[array]::IndexOf($GMD.CallGroupOwnerId, "sip:$User")].NotificationSetting = $NotificationSetting

          if ($PSCmdlet.ShouldProcess("Calling 'Set-CsUserCallingSettings' to set GroupMembershipDetails for '$_'", "$_", 'Set-CsUserCallingSettings')) {
            try {
              $CsUserCallingSettings = @{
                GroupMembershipDetails = $GMD
                ErrorAction            = 'Stop'
              }
              $null = Set-CsUserCallingSettings -Identity "$User" @CsUserCallingSettings
              Write-Information "INFO: User '$_' - GroupMembershipDetails for User '$User' - NotificationSetting set to $NotificationSetting"
            }
            catch {
              $ErrorLog = $_
              Write-Error -Message $ErrorLog
              if ( $WriteErrorLog.IsPresent ) { Write-TFErrorLog -ErrorLog $ErrorLog -Artifact "$User" }
            }
          }
        }
      }
      #endregion
      #endregion


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

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