Private/Set-TFPhoneNumber.ps1
# Module: TeamsFunctions # Function: Teams User Voice Configuration # Author: David Eberhardt # Updated: 02-JUN-2022 # Status: Live function Set-TFPhoneNumber { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory)] $UserObject, [Parameter(Mandatory)] $UserLicense, [Parameter(Mandatory)] [AllowNull()] [AllowEmptyString()] [string]$PhoneNumber, [Parameter()] $Called = $false, [Parameter()] $Force = $false ) #param begin { Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)" #region Worker Function function SetNumber { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory)] [string]$UserPrincipalName, [Parameter(Mandatory)] [AllowNull()] [AllowEmptyString()] [string]$PhoneNumber, [Parameter(Mandatory)] [string]$PhoneNumberType ) #param if ( $null -eq $PhoneNumber -or $PhoneNumber -eq '' ) { $PhoneNumber = $null } else { # This is necessary as Set-CsPhoneNumberAssignment expects the phoneNumber in the format "+1234567890;ext=1234" $PhoneNumber = $($PhoneNumber | Format-StringForUse -As LineUri) -replace 'tel:', '' } try { $CsPhoneNumberAssignmentParams = @{ 'Identity' = "$UserPrincipalName" 'PhoneNumber' = "$PhoneNumber" 'PhoneNumberType' = $PhoneNumberType } if ($PSBoundParameters.ContainsKey('Debug')) { " Function: $($MyInvocation.MyCommand.Name) - CsPhoneNumberAssignmentParams:", ($CsPhoneNumberAssignmentParams | Format-Table -AutoSize | Out-String).Trim() | Write-Debug } if ( [String]::IsNullOrEmpty($PhoneNumber) ) { # If phonenumber is empty, this needs to call Remove-CsPhoneNumberAssignment # Removal of all Numbers required if ($PSCmdlet.ShouldProcess("$UserPrincipalName", 'Remove-CsPhoneNumberAssignment -RemoveAll')) { $null = Remove-CsPhoneNumberAssignment -Identity $UserPrincipalName -RemoveAll -ErrorAction STOP } } else { # Direct Routing Number if ($PSCmdlet.ShouldProcess("$UserPrincipalName", "Set-CsPhoneNumberAssignment -PhoneNumber $PhoneNumber")) { $null = Set-CsPhoneNumberAssignment @CsPhoneNumberAssignmentParams -ErrorAction STOP } } } catch { #Writing error of CsPhoneNumberAssignment to debug stream " Function: $($MyInvocation.MyCommand.Name) - CsPhoneNumberAssignment-CmdLet threw exception:", ($($_.Exception.Message) | Out-String).Trim() | Write-Debug Write-Error -Message "Assignment with 'CsPhoneNumberAssignment' failed. $($_.Exception.Message)" -ErrorAction Stop } } #SetNumber #endregion } #begin process { Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)" #region Validating Object # Catching non-supported User types and setting ID switch -regex ( $UserObject.InterpretedUserType ) { 'User' { $Id = $($UserObject.SipAddress) } 'ApplicationInstance' { $Id = $($UserObject.UserPrincipalName) } Default { $Id = if ( $UserObject.ObjectId.IsPresent ) { $UserObject.ObjectId } else { $UserObject.Identity } $Message = "Object '$Id' is not a User or a ResourceAccount!" throw [System.InvalidOperationException]::New("$Message") } } # Object Location (OnPrem VS Online) if ( $UserObject.InterpretedUserType -match 'OnPrem' ) { $Message = "'$Id' is not hosted in Teams!" Write-Warning -Message $Message #Deactivated as Object is able to be used/enabled even if in Islands mode and Object in Skype! #throw [System.InvalidOperationException]::New("$Message") } #endregion #region Validating License if ( $UserObject.UserPrincipalName -ne $UserLicense.UserPrincipalName ) { $Message = "UserObject '$($UserObject.UserPrincipalName)' does not match LicenseObject $($UserLicense.UserPrincipalName)" throw [System.InvalidOperationException]::New("$Message") } if ( -not $UserLicense.PhoneSystem -and -not $UserLicense.PhoneSystemVirtualUser ) { $Message = "'$Id' Enterprise Voice Status: User is not licensed correctly (PhoneSystem required)!" throw [System.InvalidOperationException]::New("$Message") } if ( -not [string]$UserLicense.PhoneSystemStatus.contains('Success') ) { Write-Information "TRYING: '$Id' - Phone System: Not enabled, trying to enable" Set-AzureAdUserLicenseServicePlan -UserPrincipalName $UserObject.UserPrincipalName -Enable MCOEV $i = 0 $iMax = 60 Write-Information "INFO: User '$Id' - Phone System: Enabled; Waiting for AzureAd to write object ($iMax s)" $StatusID1 = 'Azure Active Directory is propagating Object. Please wait' $CurrentOperationID1 = 'Waiting for Get-AzureAdUserLicense to return a Result' Write-Verbose -Message "$StatusID1 - $CurrentOperationID1" do { if ($i -gt $iMax) { Write-Error -Message "Could not find Object in AzureAD in the last $iMax Seconds" -Category ObjectNotFound -RecommendedAction 'Please verify Object has been created (UserPrincipalName); Continue with Set-TeamsResourceAccount' return } Write-Progress -Id 1 -ParentId 0 -Activity $ActivityID1 -Status $StatusID1 -CurrentOperation $CurrentOperationID1 -SecondsRemaining $($iMax - $i) -PercentComplete (($i * 100) / $iMax) Start-Sleep -Milliseconds 1000 $i++ $UserLicense = Get-AzureAdUserLicense "$Id" } while ( -not [string]$UserLicense.PhoneSystemStatus.contains('Success') ) } #endregion #region Enterprise Voice <# not required anymore as Set-CsPhoneNumberAssignment does this by itself. if ( $UserObject.EnterpriseVoiceEnabled ) { $EVenabled = $true } else { try { Set-CsPhoneNumberAssignment -Identity $UserObject.SipAddress -EnterpriseVoiceEnabled $true -ErrorAction Stop Start-Sleep -Seconds 1 $EVenabled = (Get-CsOnlineUser -Identity "$($UserObject.SipAddress)" -WarningAction SilentlyContinue -ErrorAction Stop).EnterpriseVoiceEnabled } catch { if ($PSBoundParameters.ContainsKey('Debug')) { " Function: $($MyInvocation.MyCommand.Name) - Set-CsPhoneNumberAssignment: Error", ($_.Exception.Message | Format-Table -AutoSize | Out-String).Trim() | Write-Debug } $EVenabled = $false } } if ( -not $EVenabled ) { $Message = "'$Id' Enterprise Voice: User could not be enabled for Enterprise Voice!" throw [System.InvalidOperationException]::New("$Message") } #> #endregion #region Validating Phone Number # Querying CurrentPhoneNumber try { $CurrentPhoneNumber = $CsUser.LineUri Write-Verbose -Message "Object '$Id' - Phone Number assigned currently: '$CurrentPhoneNumber'" } catch { $CurrentPhoneNumber = $null Write-Verbose -Message "Object '$Id' - Phone Number assigned currently: NONE" } if ( [String]::IsNullOrEmpty($PhoneNumber) ) { if ($CurrentPhoneNumber) { Write-Warning -Message "Object '$Id' - PhoneNumber is NULL or Empty. The Existing Number '$CurrentPhoneNumber' will be removed" } else { Write-Verbose -Message "Object '$Id' - PhoneNumber is NULL or Empty, but no Number is currently assigned. No Action taken" } $PhoneNumber = $null $TeamsPhoneNumberObject = $null $UserWithThisNumberExceptSelf = $null } else { # New method based on Get-CsPhoneNumberassignment $E164Number = $PhoneNumber | Format-StringForUse -As E164 # Determining Number Type Write-Verbose -Message "Object '$Id' - Parsing Business Voice Directory for current assignments for '$E164Number'" $TeamsPhoneNumberObject = $null $TeamsPhoneNumberObject = Get-TeamsPhoneNumber -PhoneNumber $E164Number -InformationAction SilentlyContinue if ( $TeamsPhoneNumberObject ) { Write-Verbose -Message "Provisioning for $($TeamsPhoneNumberObject.NumberType)" # Previous Assignments $UserWithThisNumber = $TeamsPhoneNumberObject | Where-Object Assigned $UserWithThisNumberExceptSelf = $null $UserWithThisNumberExceptSelf = $UserWithThisNumber | Where-Object AssignedPstnTargetId -NE $UserObject.Identity } else { Write-Verbose -Message 'Provisioning for DirectRouting (no current assignments found)' $UserWithThisNumber = $UserWithThisNumberExceptSelf = $null } if ( $UserWithThisNumberExceptSelf ) { foreach ($UserWTN in $UserWithThisNumberExceptSelf) { if ($Force) { Write-Warning -Message "Object '$Id' - Number '$PhoneNumber' is currently assigned to User '$($UserWTN.AssignedTo)'. This assignment will be removed!" } else { Write-Error -Message "Object '$Id' - Number '$PhoneNumber' is already assigned to another Object: '$($UserWTN.AssignedTo)'" -Category NotImplemented -RecommendedAction 'Please specify a different Number or use -Force to re-assign' -ErrorAction Stop } } } } #endregion #region ACTION # Scavenging Phone Number if ( $Force ) { Write-Warning -Message 'Parameter Force - Scavenging Phone Number from all Objects where number is assigned. Validate carefully' foreach ($UserWTN in $UserWithThisNumberExceptSelf) { Write-Verbose -Message "Object '$($UserWTN.AssignedTo)' - Scavenging Phone Number" try { $SetNumberParams = @{ 'UserPrincipalName' = $($UserWTN.AssignedTo) 'PhoneNumber' = $null 'PhoneNumberType' = $UserWTN.NumberType 'ErrorAction' = 'Stop' } SetNumber @SetNumberParams Write-Information "INFO: '$($UserWTN.AssignedTo)' - Phone Number '$($UserWTN.PhoneNumber)' removed" -InformationAction Continue } catch { $Message = "'$Id' - Error scavenging Phone Number: $($_.Exception.Message)" Write-Error $Message -ErrorAction $ErrorActionPreference } } } #Removing Phone Number if ( $Force -or ([String]::IsNullOrEmpty($PhoneNumber)) ) { Write-Verbose -Message "Object '$Id' - Removing Phone Number" try { $SetNumberParams = @{ 'UserPrincipalName' = $Id 'PhoneNumber' = $null 'PhoneNumberType' = 'DirectRouting' # Not used, just required to pass something to SetNumber 'ErrorAction' = 'Stop' } SetNumber @SetNumberParams } catch { $Message = "'$Id' - Error removing Phone Number: $($_.Exception.Message)" Write-Error $Message -ErrorAction $ErrorActionPreference } } #Setting Phone Number if ( -not ([String]::IsNullOrEmpty($PhoneNumber)) ) { Write-Verbose -Message "Object '$Id' - Applying Phone Number" try { $SetNumberParams = @{ 'UserPrincipalName' = $Id 'PhoneNumber' = $PhoneNumber 'PhoneNumberType' = $($TeamsPhoneNumberObject.NumberType) 'ErrorAction' = 'Stop' } SetNumber @SetNumberParams } catch { $Message = "'$Id' - Error applying Phone Number: $($_.Exception.Message)" Write-Error $Message -ErrorAction $ErrorActionPreference } } #endregion } #process end { Write-Verbose -Message "[END ] $($MyInvocation.MyCommand)" } #end } |