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 |