Public/UserManagement/TeamsUserCallingSettings/Get-TeamsUserCallingSettings.ps1

# Module: TeamsFunctions
# Function: VoiceConfig
# Author: David Eberhardt
# Updated: 08-OCT-2022
# Status: Live

#FIX - Settings output is unordered when exported
#TODO - Add mock object for if SHOW is present and no Delegates, or delegators are present (unsure about allgroup)

function Get-TeamsUserCallingSettings {
  <#
  .SYNOPSIS
    Displays Voice Configuration Parameters for one or more Users
  .DESCRIPTION
    Displays Voice Configuration Parameters with different Diagnostic Levels
    ranging from basic Voice Configuration up to Policies, Account Status & DirSync Information
  .PARAMETER UserPrincipalName
    Required. UserPrincipalName (UPN) of the User to parse
  .PARAMETER Show
    Optional. Will filter the output for the object and only show Settings, Delegates, Delegators or GroupMembershipDetails
    Default is Settings. If not used, will show the tweaked CsUserCallingSettings object with all nested objects
    For Delegates, will show a newly created object showing Delegate and Delegator (instead of a list of Delegates only)
    For Delegator, will show a tweaked object: Added the User to the List allowing for usable CSV Output
    For CallGroupTargets, will show a tweaked object: Added the User to the List allowing for usable CSV Output
    For GroupMembershipDetails, will show a tweaked object: Added the User to the List allowing for usable CSV Output
  .PARAMETER ReturnObjectIfNotFound
    Optional. Returns an empty object for when no CsOnlineUser Object can be found.
    This is useful for bulk-operations exporting this information to CSV
  .PARAMETER WriteErrorLog
    Optional. If Errors are encountered, writes log to C:\Temp
  .EXAMPLE
    Get-TeamsUserCallingSettings -UserPrincipalName John@domain.com [-Show Settings]
 
    Shows Calling Settings for John. Parameter Show is Optional, Settings is the default.
    The Output Object is the same as for Get-CsUserCallingSettings, though nested objects are tweaked
  .EXAMPLE
    Get-TeamsUserCallingSettings -UserPrincipalName John@domain.com -Show Delegates
 
    Shows Delegate Object of the Calling Settings for John. Delegator (John) and Delegate are shown.
  .EXAMPLE
    "John@domain.com" | Get-TeamsUserCallingSettings -Show Delegators
 
    Shows Delegators Object of the Calling Settings for User via Pipeline. Delegate (John), Id (Delegator) as well as
    allowed settings for the Delegate (MakeCalls, ReceiveCalls, ManageSettings) are shown.
  .EXAMPLE
    Get-CsOnlineUser | Get-TeamsUserCallingSettings -Show GroupMembershipDetails | Export-Csv "C:\Temp\CallingSettings.csv" -Append
 
    Shows GroupMembershipDetails Object for Calling Settings for all CsOnlineUsers and exports it as a CSV file to C:\temp
    The Object will include CallGroupMemberId (CsOnlineUser), CallGroupOwnerId (User owning the call Group), NotificationSetting
  .EXAMPLE
    $ListOfUsers | Get-TeamsUserCallingSettings -WriteErrorLog -ReturnObjectIfNotFound | Export-Csv "C:\Temp\CallingSettings.csv" -Append
 
    Assuming the user does not exist, will write an error log to C:\Temp and returns an empty object
    The Output is written to a CSV file containing all parsed objects, whether found or not.
  .INPUTS
    System.String
  .OUTPUTS
    System.Object
  .NOTES
    Exporting PowerShell Objects that contain Nested Objects as CSV results in this parameter being shown as "System.Object[]".
    Get-CsUseCallingSettings, with a tweak that enhances usability for Export via CSV (User is the provided UserPrincipalName)
    - Delegates is now an Object containing Delegator (the User) and the Delegate
    - Delegators is an Object and now also shows the Delegate (the User) alongside the Id (Delegate) & their allowed settings
    - GroupMembershipDetails is an Object and now also shows the CallGroupMemberId (the User) alongside the CallGroupOwnerId
    and the NotificationSetting for the Member
 
    Parameter Show will influence what Objects are shown
    - Settings (default): CsUserCallingSetting with all nested (and tweaked) Objects
    - Delegates: Newly created CsUserCallingSetting.Delegates Object
    - Delegators: Tweaked CsUserCallingSetting.Delegators Object
    - GroupMembershipDetails: Tweaked CsUserCallingSetting.GroupMembershipDetails Object
  .COMPONENT
    VoiceConfiguration
  .FUNCTIONALITY
    Returns User Calling Settings Objects depending on desired output
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Get-TeamsUserCallingSettings.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_VoiceConfiguration.md
  .LINK
    https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/
  #>


  [CmdletBinding()]
  [Alias('Get-TeamsUCS')]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('ObjectId', 'Identity')]
    [string[]]$UserPrincipalName,

    [Parameter(HelpMessage = 'Improves performance by not performing a License Check on the User')]
    [Validateset('Settings', 'Delegates', 'Delegators', 'CallGroupTargets', 'GroupMembershipDetails')]
    [string]$Show = 'Settings',

    [Parameter(HelpMessage = 'Returns an empty Object instead of terminating if no Object has been found')]
    [Alias('ReturnEmptyObject')]
    [switch]$ReturnObjectIfNotFound,

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

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

    #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

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

    # preparing Output Field Separator
    $OFS = ', ' # do not remove - Automatic variable, used to separate elements!

    #region Classes
    class TFCallingSettingsDelegate {
      [string]$Id
      [string]$Delegator
      [bool]$ReceiveCalls
      [bool]$MakeCalls
      [bool]$ManageSettings

      TFCallingSettingsDelegate(
        [string]$Id,
        [string]$Delegator,
        [bool]$ReceiveCalls,
        [bool]$MakeCalls,
        [bool]$ManageSettings
      ) {
        $this.Id = $Id
        $this.Delegator = $Delegator
        $this.ReceiveCalls = $ReceiveCalls
        $this.MakeCalls = $MakeCalls
        $this.ManageSettings = $ManageSettings
      }
    }

    class TFCallingSettingsDelegator {
      [string]$Id
      [string]$Delegate
      [bool]$ReceiveCalls
      [bool]$MakeCalls
      [bool]$ManageSettings

      TFCallingSettingsDelegator(
        [string]$Id,
        [string]$Delegate,
        [bool]$ReceiveCalls,
        [bool]$MakeCalls,
        [bool]$ManageSettings
      ) {
        $this.Id = $Id
        $this.Delegate = $Delegate
        $this.ReceiveCalls = $ReceiveCalls
        $this.MakeCalls = $MakeCalls
        $this.ManageSettings = $ManageSettings
      }
    }

    class TFCallingSettingsCallGroupTarget {
      [string]$CallGroupMemberId
      [string]$CallGroupOwnerId

      TFCallingSettingsCallGroupTarget(
        [string]$CallGroupMemberId,
        [string]$CallGroupOwnerId
      ) {
        $this.CallGroupMemberId = $CallGroupMemberId
        $this.CallGroupOwnerId = $CallGroupOwnerId
      }
    }

    class TFCallingSettingsGroupMembershipDetails {
      [string]$CallGroupOwnerId
      [string]$CallGroupMemberId
      [string]$NotificationSetting

      TFCallingSettingsGroupMembershipDetails(
        [string]$CallGroupOwnerId,
        [string]$CallGroupMemberId,
        [string]$NotificationSetting
      ) {
        $this.CallGroupOwnerId = $CallGroupOwnerId
        $this.CallGroupMemberId = $CallGroupMemberId
        $this.NotificationSetting = $NotificationSetting
      }
    }
    #endregion
  } #begin

  process {
    Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)"
    foreach ($User in $UserPrincipalName) {
      [int] $private:CountID0 = 1
      #region Information Gathering
      $StatusID0 = "Processing '$User' - Information Gathering"
      #region Querying Identity
      $UserCallingSettings = $null
      try {
        $UserCallingSettings = Get-CsUserCallingSettings -Identity $User -ErrorAction Stop
        $ObjectUPN = $UserCallingSettings.SipUri
      }
      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" }
        if ( $ReturnObjectIfNotFound.IsPresent ) {
          $ObjectUPN = $User
        }
        else {
          continue
        }
      }

      # Re-Serializing Object
      #BODGE Results in the order being Alphabethical once returned. May require manual run of Select-Object before output
      $CallingSettingsObject = [Management.Automation.PSSerializer]::DeSerialize([Management.Automation.PSSerializer]::Serialize($UserCallingSettings))
      #endregion

      $StatusID0 = "Processing '$ObjectUPN' - Tweaking Object"
      #region Tweaking Object
      #region Delegates
      $CurrentOperationID0 = 'Parsing Delegates' # Creating new Object containing Delegate & Delegator
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      [System.Collections.Generic.List[object]]$Delegates = @()
      if ( $UserCallingSettings.Delegates.Count -gt 0 ) {
        $UserCallingSettings.Delegates | ForEach-Object {
          [void]$Delegates.Add([TFCallingSettingsDelegate]::new($_.Id, $ObjectUPN, $_.ReceiveCalls, $_.MakeCalls, $_.ManageSettings))
        }
      }
      else {
        if ( $ReturnObjectIfNotFound ) {
          [void]$Delegates.Add([TFCallingSettingsDelegate]::new($null, $ObjectUPN, $null, $null, $null))
        }
        else {
          $Delegates = $null
        }
      }
      $CallingSettingsObject.Delegates = $Delegates
      #endregion

      #region Delegators
      $CurrentOperationID0 = 'Parsing Delegators' # Adding User to Delegators Object
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      [System.Collections.Generic.List[object]]$Delegators = @()
      if ( $UserCallingSettings.Delegators.Count -gt 0 ) {
        $UserCallingSettings.Delegators | ForEach-Object {
          [void]$Delegators.Add([TFCallingSettingsDelegator]::new($_.Id, $ObjectUPN, $_.ReceiveCalls, $_.MakeCalls, $_.ManageSettings))
        }
      }
      else {
        if ( $ReturnObjectIfNotFound ) {
          [void]$Delegators.Add([TFCallingSettingsDelegator]::new($null, $ObjectUPN, $null, $null, $null))
        }
        else {
          $Delegators = $null
        }
      }
      $CallingSettingsObject.Delegators = $Delegators
      #endregion

      #region CallGroupTargets
      $CurrentOperationID0 = 'Parsing CallGroupTargets' # Creating new Object containing Delegate & Delegator
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      [System.Collections.Generic.List[object]]$CallGroupTargets = @{}
      if ( $UserCallingSettings.CallGroupTargets.Count -gt 0 ) {
        $UserCallingSettings.CallGroupTargets | ForEach-Object {
          [void]$CallGroupTargets.Add([TFCallingSettingsCallGroupTarget]::new($_, $ObjectUPN))
        }
      }
      else {
        if ( $ReturnObjectIfNotFound ) {
          [void]$CallGroupTargets.Add([TFCallingSettingsCallGroupTarget]::new($null, $ObjectUPN))
        }
        else {
          $CallGroupTargets = $null
        }
      }
      $CallingSettingsObject.CallGroupTargets = $CallGroupTargets
      #endregion

      #region GroupMembershipDetails
      $CurrentOperationID0 = 'Parsing GroupMembershipDetails' # Adding User to GroupMembershipDetails Object
      Write-BetterProgress -Id 0 -Activity $ActivityID0 -Status $StatusID0 -CurrentOperation $CurrentOperationID0 -Step ($private:CountID0++) -Of $private:StepsID0
      [System.Collections.Generic.List[object]]$GroupMembershipDetails = @{}
      if ( $UserCallingSettings.GroupMembershipDetails.Count -gt 0 ) {
        $UserCallingSettings.GroupMembershipDetails | ForEach-Object {
          [void]$GroupMembershipDetails.Add([TFCallingSettingsGroupMembershipDetails]::new($_.CallGroupOwnerId, $ObjectUPN, $_.NotificationSetting))
        }
      }
      else {
        if ( $ReturnObjectIfNotFound ) {
          [void]$GroupMembershipDetails.Add([TFCallingSettingsGroupMembershipDetails]::new($null, $ObjectUPN, $null))
        }
        else {
          $GroupMembershipDetails = $null
        }
      }
      $CallingSettingsObject.GroupMembershipDetails = $GroupMembershipDetails
      #endregion
      #endregion


      # OUTPUT
      $OutputObject = switch ($Show) {
        'Settings' {
          # This fixes order output when exported, but is a crutch
          $CallingSettingsObject | Select-Object SipUri, IsForwardingEnabled, ForwardingType, ForwardingTarget, ForwardingTargetType, IsUnansweredEnabled, UnansweredTarget, UnansweredTargetType, UnansweredDelay, Delegates, Delegators, CallGroupOrder, CallGroupTargets, GroupMembershipDetails, GroupNotificationOverride
        }
        'Delegates' { $Delegates | Select-Object Id, Delegator, ReceiveCalls, MakeCalls, ManageSettings }
        'Delegators' { $Delegators | Select-Object Id, Delegate, ReceiveCalls, MakeCalls, ManageSettings }
        'CallGroupTargets' { $CallGroupTargets | Select-Object CallGroupOwnerId, CallGroupMemberId }
        'GroupMembershipDetails' { $GroupMembershipDetails | Select-Object CallGroupMemberId, CallGroupOwnerId, NotificationSetting }
      }

      Write-Progress -Id 0 -Activity $ActivityID0 -Completed
      Write-Output $OutputObject

    }
  } #process

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