Remediation/TeamsPhone/CMP-VOICE-LICENSES.ps1
|
# UNTESTED: this script has never been run on a real tenant. Read it, try it on a test tenant first, and use it at your own risk. <# .SYNOPSIS Works out the telephony license need of the target and waits for the order (procedure CMP-VOICE-LICENSES). .DESCRIPTION Need computed on the snapshot: - one Teams Phone add-on (MCOEV) per voice user of the source, minus the accounts excluded in the matrix; - one Teams Phone Resource Account license (PHONESYSTEM_VIRTUALUSER) per resource account to recreate; - one calling plan per user number if the decision TEL-ROUTING-TARGET is CallingPlan. A resource account never enters the Teams Phone need. With no decision on the provider, calling plans stay unknown. Limit: a license order does not go through an API. The script declares the order to place, then checks what is available in the target through Graph: as soon as the need is covered, the action is seen done. #> $c = Open-TcRemediation $TypeFile = 'TEL-ROUTING-TARGET' $targetType = if (Test-TcDecisionFile $TypeFile) { @(Read-TcDecisionRow $TypeFile | ForEach-Object { (Get-TcMember $_ 'Decision') } | Where-Object { $_ }) | Select-Object -First 1 } $Needs = [ordered]@{} foreach ($t in $c.Tenants) { Write-TcRemediationTenant $t $matrix = @{} foreach ($l in @((Get-TcMember (Get-TcRemediationIdentity $t) 'rows'))) { $matrix[((Get-TcText $l 'Source UPN')).ToLowerInvariant()] = $l } $people = @(Get-TcRemediationTable $t 'teamsUsers' | Where-Object { (ConvertTo-TcBoolean $_['Enterprise Voice']) -and $_['Features'] -notmatch 'VoiceApp' }) $excludedItems = @($people | Where-Object { (Get-TcProperty $matrix[([string]$_['UPN']).ToLowerInvariant()] 'Decision') -eq 'Exclude' }).Count $resources = @(Get-TcRemediationTable $t 'resourceAccounts').Count Write-TcRemediationNote ("{0} voice user(s), {1} excluded by the matrix; {2} resource account(s)" -f $people.Count, $excludedItems, $resources) $Needs['MCOEV'] = [int]$Needs['MCOEV'] + $people.Count - $excludedItems $Needs['PHONESYSTEM_VIRTUALUSER'] = [int]$Needs['PHONESYSTEM_VIRTUALUSER'] + $resources if ($targetType -eq 'CallingPlan') { $Needs['Calling plan'] = [int]$Needs['Calling plan'] + $people.Count - $excludedItems } } if (-not $targetType) { Write-TcRemediationAlert 'Provider not decided (TEL-ROUTING-TARGET): the number of calling plans stays unknown.' } function Get-AvailableUnit { <# Units available of a license. A calling plan covers several SKUs. #> param([object[]]$Rows, [string]$License) $l = if ($License -eq 'Calling plan') { @($Rows | Where-Object { (Get-TcMember $_ 'Sku') -match 'MCOPSTN|Calling_Plan' }) } else { @($Rows | Where-Object { (Get-TcMember $_ 'Sku') -eq $License }) } return [int]((Get-TcMember ($l | Measure-Object -Property Available -Sum) 'Sum')) } $extract = @(Get-TcRemediationTable 'Target' 'licenses' | ForEach-Object { [pscustomobject]@{ Sku = [string]$_['SKU']; Available = [int]$_['Available'] } }) $rows = @(foreach ($k in $Needs.Keys) { $d = Get-AvailableUnit -Rows $extract -License $k [ordered]@{ License = $k; Need = $Needs[$k]; 'Available in target' = $d; 'To order' = [math]::Max(0, $Needs[$k] - $d) } }) Export-TcRemediationReport -Name 'voice-licenses' -Rows $rows | Out-Null $short = @($rows | Where-Object { (Get-TcMember $_ 'To order') -gt 0 }) if ($short.Count) { $details = @($short | ForEach-Object { '{0}: {1} to order (need {2}, available {3})' -f (Get-TcMember $_ 'License'), (Get-TcMember $_ 'To order'), (Get-TcMember $_ 'Need'), (Get-TcMember $_ 'Available in target') }) Add-TcManualAction -Tenant 'Target' -Object 'Telephony license order' -Description ("Order the missing licenses: {0}" -f (@($short | ForEach-Object { (Get-TcMember $_ 'License') }) -join ', ')) -Who 'Client' -Minutes 1 -IntervalSeconds 20 ` -Instruction (@('Place the order before the telephony cutover date:') + $details + @('The resource account license costs nothing, but it must still be purchased.', 'Never assign a resource account license to a regular user.')) ` -WaitUntil { param($a) $s = @((Get-TcMember (Invoke-TcGraph -Tenant $a.tenant -Uri 'v1.0/subscribedSkus') 'value') | ForEach-Object { [pscustomobject]@{ Sku = (Get-TcText $_ 'skuPartNumber'); Available = [int](Get-TcMember (Get-TcMember $_ 'prepaidUnits') 'enabled') - [int](Get-TcMember $_ 'consumedUnits') } }) foreach ($k in $Needs.Keys) { if ((Get-AvailableUnit -Rows $s -License $k) -lt $Needs[$k]) { return $false } } return $true } } Invoke-TcRemediationPlan -Verify { $s = @((Get-TcMember (Invoke-TcGraph -Tenant 'Target' -Uri 'v1.0/subscribedSkus') 'value') | ForEach-Object { [pscustomobject]@{ Sku = (Get-TcText $_ 'skuPartNumber'); Available = [int](Get-TcMember (Get-TcMember $_ 'prepaidUnits') 'enabled') - [int](Get-TcMember $_ 'consumedUnits') } }) foreach ($k in $Needs.Keys) { $d = Get-AvailableUnit -Rows $s -License $k if ($d -ge $Needs[$k]) { New-TcRemediationResult OK ("{0}: {1} available for a need of {2}" -f $k, $d, $Needs[$k]) } else { New-TcRemediationResult Warning ("{0}: {1} available for a need of {2}" -f $k, $d, $Needs[$k]) } } } Close-TcRemediation |