private/New-CsFixedNumberDiversion-pipelinework.ps1
#PerformScriptSigning Function New-UcmCsFixedNumberDiversion { <# .SYNOPSIS Diverts a number associated with Microsoft Teams Via Microsoft Calling plans or Telstra Calling to an external PSTN number .DESCRIPTION Diverts a number associated with Microsoft Teams Via Microsoft Calling plans or Telstra Calling to an external PSTN number by performing the following actions - Creates a Resource Account with named "PSTN_FWD_<inboundNumber>@domain.onmicrosoft.com" by default (Configurable using -AccountPrefix) - Licences the account with a Virtual Phone System Licence - Licences the account with an appropriate calling licence (Will attempt to locate a calling licence using Locate-CsCallingLicence) - Creates an AutoAttendant with a 24 hour schedule - Configures a forward rule in the AutoAttendant Note: All accounts will be "Cloud born" and use your tenants onmicrosoft domain as syncing accounts is a PITA Warning: The script presently only supports Cloud Numbers, attempting to use Direct Routing numbers will fail. .EXAMPLE PS> New-CsFixedNumberDiversion -OriginalNumber +61370105550 -TargetNumber +61755501234 Enables Microsoft Teams for the user Button Mash PS> New-CsFixedNumberDiversion -UPN 'button.mash@contoso.com' -ServiceName 'MCOPSTNEAU' Enables Telstra Calling (Australian version of Microsoft Calling) for the user Button Mash .PARAMETER OriginalNumber The number of the new AutoAttendant. IE: The number to wish to forward FROM .PARAMETER TargetNumber The number the AutoAttendant will forward calls to. IE: the number to wish to forward TO .PARAMETER AccountPrefix This is the name that will be placed before the inbound phone number in the account name, used if you have a special naming convention for service accounts "PSTN_FWD_" by default .PARAMETER Domain This is the domain name that will be used to create the resource accounts for the diversion. This should be an "onmicrosoft" domain to minimise any directory sync issues For example "Contoso.onmicrosoft.com" .PARAMETER LicenceType How will we licence the AutoAttendant to make PSTN calls, Valid options are, MCOPSTN1, MCOPSTN2, MCOPSTNEAU2 Note, we presently dont support direct routing. I'll get there. .PARAMETER Country As we are setting licence's for the virtual users, we need to know what country to licence them in. Make sure to use upper case! .PARAMETER AADisplayName The name to assign to the AutoAttendant "<Original Number> Forward" by default .INPUTS This function accepts both parameter and pipline input .OUTPUT This Cmdet returns a PSCustomObject with multiple Keys to indicate status $Return.Status $Return.Message Return.Status can return one of three values "OK" : The Auto Attendent was created "Error" : Something went wrong creating the AA, check the output for more information. "Unknown" : Cmdlet reached the end of the function without returning anything, this shouldnt happen, if it does please log an issue on Github Return.Message returns descriptive text based on the outcome, mainly for logging or reporting .NOTES Version: 1.0 Date: 27/09/2021 .VERSION HISTORY 1.0: Initial Public Release .REQUIRED FUNCTIONS/MODULES Modules AzureAD (Install-Module AzureAD) MSOnline (Install-Module MSOnline) UcmPSTools (Install-Module UcmPsTools) Includes Cmdlets below. Cmdlets Write-UcmLog: https://github.com/Atreidae/UcmPsTools/blob/main/public/Write-UcmLog.ps1 Write-HTMLReport: https://github.com/Atreidae/UcmPsTools/blob/main/public/Write-HTMLReport.ps1 (optional) New-UcmTeamsResourceAccount New-UcmOffice365User Grant-UcmOffice365UserLicence Test-UcmO365ServicePlan .REQUIRED PERMISSIONS 'Office365 User Admin' and 'Teams Admin' or better .LINK https://www.UcMadScientist.com https://github.com/Atreidae/UcmPSTools .ACKNOWLEDGEMENTS Stack Overflow, disabling services: https://stackoverflow.com/questions/50492591/how-can-i-disable-and-enable-office-365-apps-for-all-users-at-once-using-powersh Alex Verboon, Powershell script to remove Office 365 Service Plans from a User: https://www.verboon.info/2015/12/powershell-script-to-remove-office-365-service-plans-from-a-user/ #> Param ( [Parameter(ValueFromPipelineByPropertyName=$true, mandatory=$true, Position=1)] [string]$OriginalNumber, [Parameter(ValueFromPipelineByPropertyName=$true, mandatory=$true, Position=2)] [string]$TargetNumber, [Parameter(ValueFromPipelineByPropertyName=$true, Position=3)] [string]$AccountPrefix="PSTN_FWD_", [Parameter(ValueFromPipelineByPropertyName=$true, mandatory=$true, Position=4)] [string]$Domain, [Parameter(ValueFromPipelineByPropertyName=$true, mandatory=$true, Position=5)] [string]$LicenceType, [Parameter(ValueFromPipelineByPropertyName=$true, mandatory=$true, Position=6)] [string]$Country, [Parameter(ValueFromPipelineByPropertyName=$true, Position=7)] [string]$AADisplayName ) BEGIN { $ScriptVersion = 1.0 $StartTime = Get-Date $Script:LogFileLocation = $PSCommandPath -replace '.ps1','.log' #Needing to actually figure out a better way of doing this for the module TODO Write-UcmLog -Message "$ScriptVersion script started at $StartTime" -Severity 1 -Component $Function Write-UcmLog -Message "Perfoming connection checks" -Severity 2 -Component $Function #Check to see if we are connected to SFBO $Test = (Test-SFBOConnection -reconnect) If ($Test.Status -eq "Error") { Write-UcmLog -Message "Couldnt configure diversions." -Severity 3 -Component $function Write-UcmLog -Message "Test-SFBOConnection could not locate an SFBO connection" -Severity 2 -Component $function $Return.Status = "Error" $Return.Message = "No SFBO Connection ( $($test.message) )" Return $Return } #Check to see if we are connected to MSOL $Test = (Test-MSOLConnection -reconnect) If ($Test.Status -eq "Error") { Write-UcmLog -Message "Couldnt configure diversions." -Severity 3 -Component $function Write-UcmLog -Message "Test-MSOLConnection could not locate an MSOL connection" -Severity 2 -Component $function $Return.Status = "Error" $Return.Message = "No MSOL Connection ( $($test.message) )" Return $Return } #Create an object to store all our pipeline objects $Objects= @() } PROCESS { #Check to see if we got something on the pipeline, if we did. process it if ($OriginalNumber) { #Store the object for processing Write-UcmLog -Message "Storing object for $OriginalNumber" -Severity 1 -Component $Function $Objects += $_ } Else { Write-UcmLog -Message "Couldnt configure diversions." -Severity 3 -Component $function Write-UcmLog -Message "No objects to process" -Severity 2 -Component $function $Return.Status = "Error" $Return.Message = "Pipeline Empty" Return $Return } #endregion FunctionWork } END { #region FunctionSetup, Set Default Variables for HTML Reporting and Write Log Foreach ($Diversion in $objects) { $Diversion Pause } #Actually do the work! #Resource Accounts Foreach ($Diversion in $objects) { Write-UcmLog -Message "Creating Resource Account for $($Diversion.originalNumber)" -Severity 2 -Component $Function #if ($Diversion.AADisplayName = $Null) {$Diversion.AADisplayName = ($Diversion.OriginalNumber + " Forward")} $Diversion.AADisplayName $Diversion.AADisplayName = ($OriginalNumber + " Forward") #todo fix the damn naming problem Write-UcmLog -Message "Displayname $($Diversion.AADisplayName)" -Severity 2 -Component $Function #Create the resource account $UPN = ($Diversion.AccountPrefix + $Diversion.originalNumber + "@" + $Diversion.domain) Write-UcmLog -Message "Creating Required Resource Account" -Severity 2 -Component $function $AAAccount = (New-UcmTeamsResourceAccount -upn $upn -ResourceType Autoattendant -displayname "$($Diversion.originalnumber) forward") #todo, better error handling #If ($AAAccount.status -eq "Error") {Throw "something went wrong creating the resource account"} } Write-UcmLog -Message "Resource accounts complete, wait 10 seconds for replication" -Severity 2 -Component $Function Start-sleep -seconds 10 #Licences Foreach ($Diversion in $objects) { #calculate the UPN again $UPN = ($Diversion.AccountPrefix + $Diversion.originalNumber + "@" + $Diversion.domain) #Licence the account for Phone system Write-UcmLog -Message "Licencing Resource Account for $($Diversion.originalNumber)" -Severity 2 -Component $Function $Licence1 = (Grant-UcmOffice365UserLicence -licencetype PHONESYSTEM_VIRTUALUSER -country $Diversion.country -upn $upn) #Licence the account for PSTN calling $Licence2 = (Grant-UcmOffice365UserLicence -licencetype $licencetype -country $Diversion.country -upn $upn) #todo, better error handling If ($licence1.status -eq "Error" -or $Licence2.status -eq "Error") {Throw "Something went wrong assinging licences"} } Write-UcmLog -Message "Licences assigned, wait 30 seconds for replication" -Severity 2 -Component $Function Start-sleep -seconds 30 #Assign the phone number to the resource account Foreach ($Diversion in $objects) { #calculate the UPN again $UPN = ($Diversion.AccountPrefix + $Diversion.originalNumber + "@" + $Diversion.domain) Write-UcmLog -Message "Assigning Number to Resource Account $upn" -Severity 2 -Component $function $telephoneNumber = ($Diversion.$OriginalNumber).TrimStart("tel:+") $telephoneNumber = ($Diversion.$OriginalNumber).TrimStart("+") #Set-CsOnlineApplicationInstance -Identity $AAUPN -OnpremPhoneNumber $telephoneNumber ## Direct Routing version! Set-CsOnlineVoiceApplicationInstance -Identity $UPN -TelephoneNumber $telephoneNumber } #Build the AutoAttendant Foreach ($Diversion in $objects) { #calculate the UPN AGAIN.... $UPN = ($Diversion.AccountPrefix + $Diversion.originalNumber + "@" + $Diversion.domain) Write-UcmLog -Message "Creating Autoattendant for $upn" -Severity 2 -Component $Function #Create a callable destination Write-UcmLog -Message "New-CsAutoAttendantCallableEntity" -Severity 1 -Component $function $CallForwardEntity = New-CsAutoAttendantCallableEntity -Identity "tel:+$($Diversion.TargetNumber)" -Type ExternalPSTN #Make a "Menu option" that sends the call to the target Write-UcmLog -Message "New-CsAutoAttendantMenuOption" -Severity 1 -Component $function $DiversionMenuOption = New-CsAutoAttendantMenuOption -Action TransferCallToTarget -DtmfResponse Automatic -CallTarget $CallForwardEntity #Make a "Menu" that contains the object Write-UcmLog -Message "New-CsAutoAttendantMenu" -Severity 1 -Component $function $DiversionMenu = New-CsAutoAttendantMenu -Name "Fixed Diversion" -MenuOptions @($DiversionMenuOption) #Make a "Call Flow" that contains the Menu Write-UcmLog -Message "New-CsAutoAttendantCallFlow" -Severity 1 -Component $function $DiversionCallFlow = New-CsAutoAttendantCallFlow -Name "Fixed Diversion" -Menu $DiversionMenu #Make the Autoattendant that contains the menu and defaults to the diversion call flow Write-UcmLog -Message "New-CsAutoAttendant" -Severity 1 -Component $function $o=New-CsAutoAttendant -Name $AADisplayName -DefaultCallFlow $DiversionCallFlow -CallHandlingAssociations @($afterHoursCallHandlingAssociation) -Language "en-AU" -TimeZoneId "AUS Eastern Standard Time" #Find the resource account GUID Write-UcmLog -Message "App instance lookup" -Severity 1 -Component $function $applicationInstanceId = (Get-CsOnlineUser $UPN).ObjectId #Associate the AutoAttendant with the resource account Write-UcmLog -Message "New-CsOnlineApplicationInstanceAssociation" -Severity 2 -Component $function New-CsOnlineApplicationInstanceAssociation -Identities @($applicationInstanceId) -ConfigurationId $O.identity -ConfigurationType AutoAttendant #Write-UcmLog -Message "Get-csAutoAttendant" -Severity 1 -Component $function #Get-csAutoAttendant -Identity $o.identity } Write-UcmLog -Message "AutoAttendants complete" -Severity 2 -Component $Function # Log why we were called Write-UcmLog -Message "$($MyInvocation.InvocationName) called with $($MyInvocation.Line)" -Severity 1 -Component $function Write-UcmLog -Message "Parameters" -Severity 3 -Component $function -LogOnly Write-UcmLog -Message "$($PsBoundParameters.Keys)" -Severity 1 -Component $function -LogOnly Write-UcmLog -Message "Parameters Values" -Severity 1 -Component $function -LogOnly Write-UcmLog -Message "$($PsBoundParameters.Values)" -Severity 1 -Component $function -LogOnly Write-UcmLog -Message "Optional Arguments" -Severity 1 -Component $function -LogOnly Write-UcmLog -Message "$Args" -Severity 1 -Component $function -LogOnly #endregion FunctionSetup Write-UcmLog -Message "Executing process block with Attendant $DisplayName" -Severity 1 -Component $Function } }#end function |