1571 lines
49 KiB
PowerShell
1571 lines
49 KiB
PowerShell
param(
|
|
[string]$Extensions = "201,205,206",
|
|
[string]$LogFile = ".\netgescon-tapi-dotnet-events.log",
|
|
[int]$Seconds = 0,
|
|
[string]$InteropOutputDir = '',
|
|
[ValidateSet('none', 'dry-run', 'live')]
|
|
[string]$BridgeMode = 'none',
|
|
[string]$BaseUrl = '',
|
|
[string]$Token = '',
|
|
[switch]$IncludeDiagnosticEvents
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if ([string]::IsNullOrWhiteSpace($InteropOutputDir)) {
|
|
if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
|
|
$InteropOutputDir = Join-Path $env:LOCALAPPDATA 'NetGescon\tapi-interop'
|
|
}
|
|
else {
|
|
$InteropOutputDir = Join-Path $PSScriptRoot 'tapi-interop'
|
|
}
|
|
}
|
|
elseif (-not [System.IO.Path]::IsPathRooted($InteropOutputDir)) {
|
|
$InteropOutputDir = Join-Path $PSScriptRoot $InteropOutputDir
|
|
}
|
|
|
|
Add-Type -AssemblyName System.Runtime.InteropServices
|
|
|
|
$TapiMediaTypeAudio = 8
|
|
$TapiEventFilterCallNotification = 4
|
|
$TapiEventFilterCallState = 8
|
|
$TapiEventFilterCallMedia = 16
|
|
$TapiEventFilterCallHub = 32
|
|
$TapiEventFilterCallInfoChange = 64
|
|
$TapiEventFilterPrivate = 128
|
|
$CallStateIdle = 0
|
|
$CallStateInProgress = 1
|
|
$CallStateConnected = 2
|
|
$CallStateDisconnected = 3
|
|
$CallStateOffering = 4
|
|
$CallStateHold = 5
|
|
$CallStateQueued = 6
|
|
|
|
function Get-SafeProperty {
|
|
param(
|
|
[Parameter(Mandatory = $true)] $Object,
|
|
[Parameter(Mandatory = $true)] [string]$Name
|
|
)
|
|
|
|
if ($null -eq $Object) {
|
|
return $null
|
|
}
|
|
|
|
try {
|
|
return $Object.$Name
|
|
}
|
|
catch {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Get-SafeTypeName {
|
|
param($Object)
|
|
|
|
if ($null -eq $Object) {
|
|
return '<null>'
|
|
}
|
|
|
|
try {
|
|
$type = $Object.GetType()
|
|
if ($null -eq $type) {
|
|
return '<unknown>'
|
|
}
|
|
|
|
return [string]$type.FullName
|
|
}
|
|
catch {
|
|
return '<type-error>'
|
|
}
|
|
}
|
|
|
|
function Convert-ToSafeString {
|
|
param($Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return ''
|
|
}
|
|
|
|
try {
|
|
return [string]$Value
|
|
}
|
|
catch {
|
|
return '<string-error>'
|
|
}
|
|
}
|
|
|
|
function Get-FirstNonEmptyProperty {
|
|
param(
|
|
$Object,
|
|
[Parameter(Mandatory = $true)] [string[]]$Names
|
|
)
|
|
|
|
if ($null -eq $Object) {
|
|
return ''
|
|
}
|
|
|
|
foreach ($name in $Names) {
|
|
$value = Get-SafeProperty -Object $Object -Name $name
|
|
$valueString = Convert-ToSafeString -Value $value
|
|
if ($valueString -ne '') {
|
|
return $valueString
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function Normalize-PhoneLikeValue {
|
|
param([string]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return ''
|
|
}
|
|
|
|
$trimmed = $Value.Trim()
|
|
if ($trimmed -eq '') {
|
|
return ''
|
|
}
|
|
|
|
return ($trimmed -replace '[^\d\+]', '')
|
|
}
|
|
|
|
function Expand-ExtensionToken {
|
|
param([string]$Value)
|
|
|
|
$text = Convert-ToSafeString -Value $Value
|
|
if ($text -eq '') {
|
|
return @()
|
|
}
|
|
|
|
if ($text -match '^(\d+)-(\d+)$') {
|
|
$start = [int]$matches[1]
|
|
$end = [int]$matches[2]
|
|
if ($end -lt $start) {
|
|
$swap = $start
|
|
$start = $end
|
|
$end = $swap
|
|
}
|
|
|
|
$expanded = @()
|
|
for ($index = $start; $index -le $end; $index++) {
|
|
$expanded += [string]$index
|
|
}
|
|
|
|
return $expanded
|
|
}
|
|
|
|
return @($text)
|
|
}
|
|
|
|
function Parse-WatchList {
|
|
param([string]$ExtensionsValue)
|
|
|
|
$watchItems = New-Object System.Collections.Generic.List[string]
|
|
foreach ($rawToken in @($ExtensionsValue.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })) {
|
|
foreach ($expandedToken in @(Expand-ExtensionToken -Value $rawToken)) {
|
|
$token = Convert-ToSafeString -Value $expandedToken
|
|
if ($token -ne '' -and -not $watchItems.Contains($token)) {
|
|
$watchItems.Add($token)
|
|
}
|
|
}
|
|
}
|
|
|
|
return @($watchItems.ToArray())
|
|
}
|
|
|
|
function Is-WatchedExtensionValue {
|
|
param(
|
|
[string]$Value,
|
|
[Parameter(Mandatory = $true)] [string[]]$WatchList
|
|
)
|
|
|
|
$token = Normalize-ExtensionToken -Value $Value
|
|
if ($token -eq '') {
|
|
return $false
|
|
}
|
|
|
|
foreach ($watch in $WatchList) {
|
|
if ((Normalize-ExtensionToken -Value $watch) -eq $token) {
|
|
return $true
|
|
}
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Select-ExternalPhoneCandidate {
|
|
param(
|
|
[string[]]$Candidates,
|
|
[Parameter(Mandatory = $true)] [string[]]$WatchList
|
|
)
|
|
|
|
if ($null -eq $Candidates) {
|
|
return ''
|
|
}
|
|
|
|
foreach ($candidate in $Candidates) {
|
|
$normalized = Normalize-PhoneLikeValue -Value $candidate
|
|
if ($normalized -eq '') {
|
|
continue
|
|
}
|
|
|
|
if (-not (Is-WatchedExtensionValue -Value $normalized -WatchList $WatchList)) {
|
|
return $normalized
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function Convert-ComCollectionToArray {
|
|
param($Collection)
|
|
|
|
$items = @()
|
|
if ($null -eq $Collection) {
|
|
return $items
|
|
}
|
|
|
|
try {
|
|
foreach ($item in $Collection) {
|
|
$items += $item
|
|
}
|
|
}
|
|
catch {
|
|
}
|
|
|
|
return , $items
|
|
}
|
|
|
|
function Flatten-ObjectArray {
|
|
param([object[]]$Items)
|
|
|
|
$flattened = @()
|
|
foreach ($item in $Items) {
|
|
if ($null -eq $item) {
|
|
continue
|
|
}
|
|
|
|
if ($item -is [System.Array]) {
|
|
foreach ($nested in $item) {
|
|
if ($null -ne $nested) {
|
|
$flattened += $nested
|
|
}
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
$flattened += $item
|
|
}
|
|
|
|
return @($flattened)
|
|
}
|
|
|
|
function Normalize-ExtensionToken {
|
|
param([string]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return ''
|
|
}
|
|
|
|
$trimmed = ([string]$Value).Trim()
|
|
if ($trimmed -eq '') {
|
|
return ''
|
|
}
|
|
|
|
if ($trimmed -match '(\d+)$') {
|
|
return $matches[1].TrimStart('0')
|
|
}
|
|
|
|
return $trimmed
|
|
}
|
|
|
|
function Get-TokenCategory {
|
|
param([string]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return ''
|
|
}
|
|
|
|
$trimmed = ([string]$Value).Trim()
|
|
if ($trimmed -eq '') {
|
|
return ''
|
|
}
|
|
|
|
$upper = $trimmed.ToUpperInvariant()
|
|
if ($upper -match '^EXT\d+$') {
|
|
return 'EXT'
|
|
}
|
|
|
|
if ($upper -match '^GRP\d+$') {
|
|
return 'GRP'
|
|
}
|
|
|
|
if ($upper -match '^CO\d+$') {
|
|
return 'CO'
|
|
}
|
|
|
|
if ($upper -eq 'SYSTEM') {
|
|
return 'SYSTEM'
|
|
}
|
|
|
|
$normalized = Normalize-ExtensionToken -Value $trimmed
|
|
if ($normalized -eq '') {
|
|
return ''
|
|
}
|
|
|
|
if ($trimmed -match '^0\d+$') {
|
|
return 'CO'
|
|
}
|
|
|
|
if ($normalized -match '^6\d\d$') {
|
|
return 'GRP'
|
|
}
|
|
|
|
if ($normalized -match '^\d+$') {
|
|
return 'EXT'
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function Get-AddressTokenInfos {
|
|
param([string[]]$Values)
|
|
|
|
$tokens = New-Object System.Collections.Generic.List[object]
|
|
|
|
foreach ($value in $Values) {
|
|
$text = Convert-ToSafeString -Value $value
|
|
if ($text -eq '') {
|
|
continue
|
|
}
|
|
|
|
foreach ($piece in @([regex]::Split($text.Trim(), '[\s,;\|]+') | Where-Object { $_ -ne '' })) {
|
|
$tokens.Add([pscustomobject]@{
|
|
Raw = $piece
|
|
Normalized = Normalize-ExtensionToken -Value $piece
|
|
Category = Get-TokenCategory -Value $piece
|
|
})
|
|
}
|
|
}
|
|
|
|
return @($tokens.ToArray())
|
|
}
|
|
|
|
function Find-AddressWatchMatch {
|
|
param(
|
|
[string[]]$Values,
|
|
[Parameter(Mandatory = $true)] [string[]]$WatchList
|
|
)
|
|
|
|
$tokenInfos = @(Get-AddressTokenInfos -Values $Values)
|
|
foreach ($tokenInfo in $tokenInfos) {
|
|
if ($null -eq $tokenInfo -or $tokenInfo.Normalized -eq '') {
|
|
continue
|
|
}
|
|
|
|
foreach ($watch in $WatchList) {
|
|
$watchNormalized = Normalize-ExtensionToken -Value $watch
|
|
if ($watchNormalized -eq '' -or $watchNormalized -ne $tokenInfo.Normalized) {
|
|
continue
|
|
}
|
|
|
|
$category = $tokenInfo.Category
|
|
if ($category -eq '' -or $category -eq 'SYSTEM') {
|
|
$category = Get-TokenCategory -Value $watch
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
Raw = $tokenInfo.Raw
|
|
Normalized = $tokenInfo.Normalized
|
|
Category = $category
|
|
WatchValue = [string]$watch
|
|
}
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Get-AddressCategory {
|
|
param([string]$AddressName)
|
|
|
|
if ($null -eq $AddressName) {
|
|
return ''
|
|
}
|
|
|
|
$trimmed = $AddressName.Trim().ToUpperInvariant()
|
|
if ($trimmed -match '^([A-Z]+)') {
|
|
return $matches[1]
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function Convert-ToNullableInt {
|
|
param([string]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return $null
|
|
}
|
|
|
|
$digits = ($Value -replace '\D+', '')
|
|
if ($digits -eq '') {
|
|
return $null
|
|
}
|
|
|
|
try {
|
|
return [int]$digits
|
|
}
|
|
catch {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Should-WatchAddress {
|
|
param(
|
|
[Parameter(Mandatory = $true)] $Address,
|
|
[Parameter(Mandatory = $true)] [string[]]$WatchList
|
|
)
|
|
|
|
$rawDial = Convert-ToSafeString -Value (Get-SafeProperty -Object $Address -Name 'DialableAddress')
|
|
$rawName = Convert-ToSafeString -Value (Get-SafeProperty -Object $Address -Name 'AddressName')
|
|
$addressMatch = Find-AddressWatchMatch -Values @($rawDial, $rawName) -WatchList $WatchList
|
|
if ($null -ne $addressMatch) {
|
|
return $true
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Get-BridgeWatchMode {
|
|
param([Parameter(Mandatory = $true)] [string[]]$WatchList)
|
|
|
|
foreach ($watch in $WatchList) {
|
|
$value = Convert-ToSafeString -Value $watch
|
|
if ($value -match '^6\d\d$') {
|
|
return 'group-first'
|
|
}
|
|
}
|
|
|
|
foreach ($watch in $WatchList) {
|
|
$value = Convert-ToSafeString -Value $watch
|
|
if ($value -match '^0\d+$') {
|
|
return 'co-first'
|
|
}
|
|
}
|
|
|
|
return 'extension-only'
|
|
}
|
|
|
|
function Should-EmitBridgeForSnapshot {
|
|
param([Parameter(Mandatory = $true)] $Snapshot)
|
|
|
|
if ($Snapshot.Direction -ne 'inbound') {
|
|
switch ($script:BridgeWatchMode) {
|
|
'co-first' {
|
|
return $Snapshot.AddressCategory -eq 'CO'
|
|
}
|
|
'group-first' {
|
|
return $Snapshot.AddressCategory -eq 'EXT' -or $Snapshot.AddressCategory -eq 'CO'
|
|
}
|
|
default {
|
|
return $Snapshot.AddressCategory -eq 'EXT'
|
|
}
|
|
}
|
|
}
|
|
|
|
switch ($script:BridgeWatchMode) {
|
|
'group-first' {
|
|
return $Snapshot.AddressCategory -eq 'GRP'
|
|
}
|
|
'co-first' {
|
|
return $Snapshot.AddressCategory -eq 'CO'
|
|
}
|
|
default {
|
|
return $Snapshot.AddressCategory -eq 'EXT'
|
|
}
|
|
}
|
|
}
|
|
|
|
function Should-LogDotNetEvent {
|
|
param([string]$EventType)
|
|
|
|
if ($script:IncludeDiagnosticEvents) {
|
|
return $true
|
|
}
|
|
|
|
return $EventType -eq 'TE_CALLNOTIFICATION' -or $EventType -eq 'TE_CALLSTATE'
|
|
}
|
|
|
|
function Resolve-BridgeEventId {
|
|
param([string]$EventId)
|
|
|
|
if ($EventId -eq '') {
|
|
return ''
|
|
}
|
|
|
|
if ($script:BridgeEventAliases.ContainsKey($EventId)) {
|
|
return [string]$script:BridgeEventAliases[$EventId]
|
|
}
|
|
|
|
return $EventId
|
|
}
|
|
|
|
function Test-BridgeCallRecentlyCompleted {
|
|
param([string]$EventId)
|
|
|
|
if ($EventId -eq '' -or -not $script:CompletedBridgeCalls.ContainsKey($EventId)) {
|
|
return $false
|
|
}
|
|
|
|
$completedAt = $script:CompletedBridgeCalls[$EventId]
|
|
if ($null -eq $completedAt) {
|
|
return $false
|
|
}
|
|
|
|
$ageSeconds = [Math]::Abs((New-TimeSpan -Start $completedAt -End (Get-Date)).TotalSeconds)
|
|
if ($ageSeconds -gt 30) {
|
|
[void]$script:CompletedBridgeCalls.Remove($EventId)
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Mark-BridgeCallCompleted {
|
|
param([string]$EventId)
|
|
|
|
if ($EventId -ne '') {
|
|
$script:CompletedBridgeCalls[$EventId] = Get-Date
|
|
}
|
|
}
|
|
|
|
function Find-BridgeCallMatchBySignature {
|
|
param([Parameter(Mandatory = $true)] $Snapshot)
|
|
|
|
if ($Snapshot.Direction -ne 'outbound') {
|
|
return $null
|
|
}
|
|
|
|
$snapshotPhone = Convert-ToSafeString -Value $Snapshot.ExternalPhone
|
|
$snapshotCallingExtension = Convert-ToSafeString -Value $Snapshot.CallingExtension
|
|
$now = Get-Date
|
|
|
|
foreach ($eventId in @($script:BridgeCalls.Keys)) {
|
|
$callRecord = $script:BridgeCalls[$eventId]
|
|
if ($null -eq $callRecord) {
|
|
continue
|
|
}
|
|
|
|
if ((Convert-ToSafeString -Value $callRecord.Direction) -ne 'outbound') {
|
|
continue
|
|
}
|
|
|
|
$ageSeconds = [Math]::Abs((New-TimeSpan -Start $callRecord.StartedAt -End $now).TotalSeconds)
|
|
if ($ageSeconds -gt 120) {
|
|
continue
|
|
}
|
|
|
|
$recordPhone = Convert-ToSafeString -Value $callRecord.ExternalPhone
|
|
if ($snapshotPhone -ne '' -and $recordPhone -ne '' -and $snapshotPhone -ne $recordPhone) {
|
|
continue
|
|
}
|
|
|
|
$recordCallingExtension = Convert-ToSafeString -Value $callRecord.CallingExtension
|
|
if ($snapshotCallingExtension -ne '' -and $recordCallingExtension -ne '' -and $snapshotCallingExtension -ne $recordCallingExtension) {
|
|
continue
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
EventId = [string]$eventId
|
|
CallRecord = $callRecord
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Write-LogLine {
|
|
param([string]$Text)
|
|
|
|
$line = "{0} | {1}" -f (Get-Date).ToString('yyyy-MM-dd HH:mm:ss'), $Text
|
|
Write-Host $line
|
|
|
|
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
|
try {
|
|
$directory = Split-Path -Parent $script:LogFilePath
|
|
if ($directory -and -not (Test-Path $directory)) {
|
|
New-Item -ItemType Directory -Force -Path $directory | Out-Null
|
|
}
|
|
|
|
$fileMode = [System.IO.FileMode]::OpenOrCreate
|
|
$fileAccess = [System.IO.FileAccess]::Write
|
|
$fileShare = [System.IO.FileShare]::ReadWrite
|
|
$stream = New-Object System.IO.FileStream($script:LogFilePath, $fileMode, $fileAccess, $fileShare)
|
|
try {
|
|
$stream.Seek(0, [System.IO.SeekOrigin]::End) | Out-Null
|
|
$writer = New-Object System.IO.StreamWriter($stream, [System.Text.UTF8Encoding]::new($false))
|
|
try {
|
|
$writer.WriteLine($line)
|
|
$writer.Flush()
|
|
return
|
|
}
|
|
finally {
|
|
$writer.Dispose()
|
|
}
|
|
}
|
|
finally {
|
|
$stream.Dispose()
|
|
}
|
|
}
|
|
catch {
|
|
if ($attempt -eq 3) {
|
|
Write-Host ("[WARN] Log file non scrivibile: {0}" -f $_.Exception.Message)
|
|
return
|
|
}
|
|
|
|
Start-Sleep -Milliseconds 150
|
|
}
|
|
}
|
|
}
|
|
|
|
function Try-RegisterCallNotifications {
|
|
param(
|
|
[Parameter(Mandatory = $true)] $Tapi,
|
|
[Parameter(Mandatory = $true)] $Address
|
|
)
|
|
|
|
try {
|
|
$cookie = $Tapi.RegisterCallNotifications($Address, $true, $true, $TapiMediaTypeAudio, 0)
|
|
return [pscustomobject]@{
|
|
Ok = $true
|
|
Cookie = $cookie
|
|
Error = ''
|
|
}
|
|
}
|
|
catch {
|
|
return [pscustomobject]@{
|
|
Ok = $false
|
|
Cookie = $null
|
|
Error = $_.Exception.Message
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-SafeCallInfoString {
|
|
param(
|
|
$Call,
|
|
[Parameter(Mandatory = $true)] [int]$Id
|
|
)
|
|
|
|
if ($null -eq $Call) {
|
|
return ''
|
|
}
|
|
|
|
try {
|
|
return Convert-ToSafeString -Value ($Call.CallInfoString($Id))
|
|
}
|
|
catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
function Get-PreferredCallInfoString {
|
|
param(
|
|
$Call,
|
|
[Parameter(Mandatory = $true)] [int[]]$Ids
|
|
)
|
|
|
|
foreach ($id in $Ids) {
|
|
$value = Get-SafeCallInfoString -Call $Call -Id $id
|
|
if ($value -ne '') {
|
|
return $value
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
function Get-TypeByName {
|
|
param(
|
|
[Parameter(Mandatory = $true)] [System.Reflection.Assembly]$Assembly,
|
|
[Parameter(Mandatory = $true)] [string]$Name
|
|
)
|
|
|
|
return $Assembly.GetTypes() | Where-Object { $_.Name -eq $Name } | Select-Object -First 1
|
|
}
|
|
|
|
function Get-InteropAssembly {
|
|
param([Parameter(Mandatory = $true)] [string]$OutputDir)
|
|
|
|
Add-Type -TypeDefinition @"
|
|
using System;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices.ComTypes;
|
|
|
|
public static class NetGesconTapiWatcherNativeMethods
|
|
{
|
|
[DllImport("oleaut32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
|
|
public static extern void LoadTypeLib(string file, out ITypeLib typeLib);
|
|
}
|
|
|
|
public sealed class NetGesconTapiWatcherImporterSink : ITypeLibImporterNotifySink
|
|
{
|
|
public void ReportEvent(ImporterEventKind eventKind, int eventCode, string eventMsg)
|
|
{
|
|
}
|
|
|
|
public Assembly ResolveRef(object typeLib)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
"@
|
|
|
|
$tapiDll = Join-Path $env:WINDIR 'System32\tapi3.dll'
|
|
if (-not (Test-Path $tapiDll)) {
|
|
throw "File non trovato: $tapiDll"
|
|
}
|
|
|
|
$outputDirectoryInfo = New-Item -ItemType Directory -Force -Path $OutputDir
|
|
$outputDirectory = $outputDirectoryInfo.FullName
|
|
$interopFileName = 'Tapi3.Interop.dll'
|
|
$interopDll = Join-Path $outputDirectory $interopFileName
|
|
|
|
$typeLib = $null
|
|
[NetGesconTapiWatcherNativeMethods]::LoadTypeLib($tapiDll, [ref]$typeLib)
|
|
|
|
$converter = New-Object System.Runtime.InteropServices.TypeLibConverter
|
|
$sink = New-Object NetGesconTapiWatcherImporterSink
|
|
|
|
Push-Location $outputDirectory
|
|
try {
|
|
$assemblyBuilder = $converter.ConvertTypeLibToAssembly(
|
|
$typeLib,
|
|
$interopFileName,
|
|
0,
|
|
$sink,
|
|
$null,
|
|
$null,
|
|
$null,
|
|
$null
|
|
)
|
|
|
|
try {
|
|
$assemblyBuilder.Save($interopFileName)
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
|
|
if (Test-Path $interopDll) {
|
|
return [System.Reflection.Assembly]::LoadFrom($interopDll)
|
|
}
|
|
|
|
return [System.Reflection.Assembly]$assemblyBuilder
|
|
}
|
|
|
|
function Describe-Address {
|
|
param($Address)
|
|
|
|
if ($null -eq $Address) {
|
|
return ''
|
|
}
|
|
|
|
$name = Convert-ToSafeString -Value (Get-SafeProperty -Object $Address -Name 'AddressName')
|
|
$dial = Convert-ToSafeString -Value (Get-SafeProperty -Object $Address -Name 'DialableAddress')
|
|
$provider = Convert-ToSafeString -Value (Get-SafeProperty -Object $Address -Name 'ServiceProviderName')
|
|
return "address=$name dial=$dial provider=$provider"
|
|
}
|
|
|
|
function Describe-Call {
|
|
param($Call)
|
|
|
|
if ($null -eq $Call) {
|
|
return ''
|
|
}
|
|
|
|
$parts = @()
|
|
foreach ($name in @('CallState', 'Privilege', 'MediaType', 'CallerIDAddress', 'CalledIDAddress', 'ConnectedIDAddress', 'CallerIDName', 'CalledIDName', 'ConnectedIDName')) {
|
|
$value = Get-SafeProperty -Object $Call -Name $name
|
|
$valueString = Convert-ToSafeString -Value $value
|
|
if ($null -ne $value -and $valueString -ne '') {
|
|
$parts += ("{0}={1}" -f $name, $valueString)
|
|
}
|
|
}
|
|
|
|
$callAddress = Get-SafeProperty -Object $Call -Name 'Address'
|
|
if ($null -ne $callAddress) {
|
|
$parts += (Describe-Address -Address $callAddress)
|
|
}
|
|
|
|
return ($parts -join ' ')
|
|
}
|
|
|
|
function Get-PhoneDebugSummary {
|
|
param($Call)
|
|
|
|
if ($null -eq $Call) {
|
|
return 'call=<null>'
|
|
}
|
|
|
|
$candidates = @(
|
|
'CallerIDAddress',
|
|
'CalledIDAddress',
|
|
'ConnectedIDAddress',
|
|
'CallerIDName',
|
|
'CalledIDName',
|
|
'ConnectedIDName',
|
|
'CalledPartyID',
|
|
'CallingPartyID',
|
|
'OriginatingAddress',
|
|
'DestinationAddress',
|
|
'DisplayableAddress'
|
|
)
|
|
|
|
$parts = @()
|
|
foreach ($name in $candidates) {
|
|
$valueString = Convert-ToSafeString -Value (Get-SafeProperty -Object $Call -Name $name)
|
|
if ($valueString -ne '') {
|
|
$parts += ("{0}={1}" -f $name, $valueString)
|
|
}
|
|
}
|
|
|
|
$callInfo01 = Get-SafeCallInfoString -Call $Call -Id 1
|
|
$callInfo03 = Get-SafeCallInfoString -Call $Call -Id 3
|
|
$callInfo05 = Get-SafeCallInfoString -Call $Call -Id 5
|
|
$callInfo12 = Get-SafeCallInfoString -Call $Call -Id 12
|
|
$callInfo13 = Get-SafeCallInfoString -Call $Call -Id 13
|
|
$callInfo14 = Get-SafeCallInfoString -Call $Call -Id 14
|
|
$callInfo15 = Get-SafeCallInfoString -Call $Call -Id 15
|
|
$callInfo16 = Get-SafeCallInfoString -Call $Call -Id 16
|
|
if ($callInfo01 -ne '') {
|
|
$parts += ("CallInfoString1_CallerIdNumber={0}" -f $callInfo01)
|
|
}
|
|
if ($callInfo03 -ne '') {
|
|
$parts += ("CallInfoString3_CalledIdNumber={0}" -f $callInfo03)
|
|
}
|
|
if ($callInfo05 -ne '') {
|
|
$parts += ("CallInfoString5_ConnectedIdNumber={0}" -f $callInfo05)
|
|
}
|
|
if ($callInfo12 -ne '') {
|
|
$parts += ("CallInfoString12_DisplayableAddress={0}" -f $callInfo12)
|
|
}
|
|
if ($callInfo13 -ne '') {
|
|
$parts += ("CallInfoString13_CallingPartyId={0}" -f $callInfo13)
|
|
}
|
|
if ($callInfo14 -ne '') {
|
|
$parts += ("CallInfoString14={0}" -f $callInfo14)
|
|
}
|
|
if ($callInfo15 -ne '') {
|
|
$parts += ("CallInfoString15={0}" -f $callInfo15)
|
|
}
|
|
if ($callInfo16 -ne '') {
|
|
$parts += ("CallInfoString16={0}" -f $callInfo16)
|
|
}
|
|
|
|
if ($parts.Count -eq 0) {
|
|
return 'callProperties=<none-accessible>'
|
|
}
|
|
|
|
return ($parts -join ' ')
|
|
}
|
|
|
|
function Get-CallIdentity {
|
|
param($Call)
|
|
|
|
if ($null -eq $Call) {
|
|
return ''
|
|
}
|
|
|
|
try {
|
|
$pointer = [System.Runtime.InteropServices.Marshal]::GetIUnknownForObject($Call)
|
|
try {
|
|
if ($pointer -eq [IntPtr]::Zero) {
|
|
return ''
|
|
}
|
|
|
|
return ('0x{0}' -f $pointer.ToInt64().ToString('X'))
|
|
}
|
|
finally {
|
|
if ($pointer -ne [IntPtr]::Zero) {
|
|
[void][System.Runtime.InteropServices.Marshal]::Release($pointer)
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
function Get-CallSnapshot {
|
|
param(
|
|
[Parameter(Mandatory = $true)] [string]$EventType,
|
|
$Payload,
|
|
[Parameter(Mandatory = $true)] [string[]]$WatchList
|
|
)
|
|
|
|
$call = Get-SafeProperty -Object $Payload -Name 'Call'
|
|
$address = Get-SafeProperty -Object $Payload -Name 'Address'
|
|
if ($null -eq $address -and $null -ne $call) {
|
|
$address = Get-SafeProperty -Object $call -Name 'Address'
|
|
}
|
|
|
|
$addressName = Convert-ToSafeString -Value (Get-SafeProperty -Object $address -Name 'AddressName')
|
|
$dialableAddress = Convert-ToSafeString -Value (Get-SafeProperty -Object $address -Name 'DialableAddress')
|
|
$addressMatch = Find-AddressWatchMatch -Values @($dialableAddress, $addressName) -WatchList $WatchList
|
|
$addressCategory = Get-AddressCategory -AddressName $addressName
|
|
$watchedExtension = ''
|
|
if ($null -ne $addressMatch) {
|
|
$watchedExtension = $addressMatch.Normalized
|
|
if ($addressMatch.Category -ne '') {
|
|
$addressCategory = $addressMatch.Category
|
|
}
|
|
}
|
|
|
|
if ($watchedExtension -eq '') {
|
|
$watchedExtension = Normalize-ExtensionToken -Value $dialableAddress
|
|
}
|
|
if ($watchedExtension -eq '') {
|
|
$watchedExtension = Normalize-ExtensionToken -Value $addressName
|
|
}
|
|
if (($addressCategory -eq '' -or $addressCategory -eq 'SYSTEM') -and $watchedExtension -ne '') {
|
|
$addressCategory = Get-TokenCategory -Value $watchedExtension
|
|
}
|
|
|
|
$callState = Convert-ToSafeString -Value (Get-SafeProperty -Object $Payload -Name 'State')
|
|
$cause = Convert-ToSafeString -Value (Get-SafeProperty -Object $Payload -Name 'Cause')
|
|
|
|
$callerIdAddress = Normalize-PhoneLikeValue -Value (Get-FirstNonEmptyProperty -Object $call -Names @('CallerIDAddress', 'CallingPartyID', 'CallerAddress', 'OriginatingAddress'))
|
|
$calledIdAddress = Normalize-PhoneLikeValue -Value (Get-FirstNonEmptyProperty -Object $call -Names @('CalledIDAddress', 'CalledPartyID', 'DestinationAddress'))
|
|
$connectedIdAddress = Normalize-PhoneLikeValue -Value (Get-FirstNonEmptyProperty -Object $call -Names @('ConnectedIDAddress', 'ConnectedAddress'))
|
|
|
|
if ($callerIdAddress -eq '') {
|
|
$callerIdAddress = Normalize-PhoneLikeValue -Value (Get-PreferredCallInfoString -Call $call -Ids @(1, 13, 12, 14))
|
|
}
|
|
if ($calledIdAddress -eq '') {
|
|
$calledIdAddress = Normalize-PhoneLikeValue -Value (Get-PreferredCallInfoString -Call $call -Ids @(3, 12, 15))
|
|
}
|
|
if ($connectedIdAddress -eq '') {
|
|
$connectedIdAddress = Normalize-PhoneLikeValue -Value (Get-PreferredCallInfoString -Call $call -Ids @(5, 12, 16))
|
|
}
|
|
|
|
$callerName = Get-FirstNonEmptyProperty -Object $call -Names @('CallerIDName', 'CallingPartyName')
|
|
if ($callerName -eq '') {
|
|
$callerName = Get-SafeCallInfoString -Call $call -Id 0
|
|
}
|
|
|
|
$rawCallerIdAddress = $callerIdAddress
|
|
$rawCalledIdAddress = $calledIdAddress
|
|
$rawConnectedIdAddress = $connectedIdAddress
|
|
|
|
$direction = 'inbound'
|
|
$externalPhone = Select-ExternalPhoneCandidate -Candidates @($callerIdAddress, $connectedIdAddress, $calledIdAddress) -WatchList $WatchList
|
|
|
|
$callerToken = Normalize-ExtensionToken -Value $callerIdAddress
|
|
$calledToken = Normalize-ExtensionToken -Value $calledIdAddress
|
|
$connectedToken = Normalize-ExtensionToken -Value $connectedIdAddress
|
|
|
|
$callingExtension = ''
|
|
$calledExtension = $watchedExtension
|
|
|
|
if (Is-WatchedExtensionValue -Value $calledToken -WatchList $WatchList) {
|
|
$calledExtension = $calledToken
|
|
if ($watchedExtension -eq '' -or $addressCategory -eq '' -or $addressCategory -eq 'SYSTEM') {
|
|
$watchedExtension = $calledToken
|
|
$addressCategory = Get-TokenCategory -Value $calledIdAddress
|
|
}
|
|
}
|
|
|
|
if ($callerToken -ne '' -and (Is-WatchedExtensionValue -Value $callerToken -WatchList $WatchList) -and $calledIdAddress -ne '' -and -not (Is-WatchedExtensionValue -Value $calledToken -WatchList $WatchList)) {
|
|
$direction = 'outbound'
|
|
$externalPhone = Select-ExternalPhoneCandidate -Candidates @($calledIdAddress, $connectedIdAddress, $callerIdAddress) -WatchList $WatchList
|
|
$callingExtension = $callerToken
|
|
$calledExtension = $watchedExtension
|
|
if ($watchedExtension -eq '') {
|
|
$watchedExtension = $callerToken
|
|
}
|
|
if ($addressCategory -eq '' -or $addressCategory -eq 'SYSTEM') {
|
|
$addressCategory = Get-TokenCategory -Value $callerIdAddress
|
|
}
|
|
}
|
|
elseif ($connectedToken -ne '' -and (Is-WatchedExtensionValue -Value $connectedToken -WatchList $WatchList) -and $callerToken -eq '') {
|
|
$calledExtension = $connectedToken
|
|
if ($watchedExtension -eq '' -or $addressCategory -eq '' -or $addressCategory -eq 'SYSTEM') {
|
|
$watchedExtension = $connectedToken
|
|
$addressCategory = Get-TokenCategory -Value $connectedIdAddress
|
|
}
|
|
}
|
|
elseif ($watchedExtension -eq '' -and $connectedToken -ne '' -and (Is-WatchedExtensionValue -Value $connectedToken -WatchList $WatchList)) {
|
|
$calledExtension = $connectedToken
|
|
$watchedExtension = $connectedToken
|
|
if ($addressCategory -eq '' -or $addressCategory -eq 'SYSTEM') {
|
|
$addressCategory = Get-TokenCategory -Value $connectedIdAddress
|
|
}
|
|
}
|
|
|
|
$eventId = Get-CallIdentity -Call $call
|
|
if ($eventId -eq '') {
|
|
$eventId = "{0}:{1}:{2}" -f $addressName, $watchedExtension, $EventType
|
|
}
|
|
|
|
return [pscustomobject]@{
|
|
EventType = $EventType
|
|
EventId = $eventId
|
|
CallState = $callState
|
|
Cause = $cause
|
|
AddressName = $addressName
|
|
AddressCategory = $addressCategory
|
|
WatchedExtension = $watchedExtension
|
|
CalledExtension = $calledExtension
|
|
CallingExtension = $callingExtension
|
|
Direction = $direction
|
|
ExternalPhone = $externalPhone
|
|
RawCallerIdAddress = $rawCallerIdAddress
|
|
RawCalledIdAddress = $rawCalledIdAddress
|
|
RawConnectedIdAddress = $rawConnectedIdAddress
|
|
CallerName = $callerName
|
|
CallObject = $call
|
|
}
|
|
}
|
|
|
|
function Build-IncomingPayload {
|
|
param([Parameter(Mandatory = $true)] $CallRecord)
|
|
|
|
$isTest = $script:BridgeMode -ne 'live'
|
|
$bridgeNote = if ($isTest) { 'bridge dry-run da watcher TAPI .NET' } else { 'bridge live da watcher TAPI .NET' }
|
|
|
|
return [ordered]@{
|
|
phone = $CallRecord.ExternalPhone
|
|
name = $CallRecord.CallerName
|
|
direction = $CallRecord.Direction
|
|
outcome = $null
|
|
event_id = $CallRecord.EventId
|
|
called_extension = $CallRecord.CalledExtension
|
|
calling_extension = $CallRecord.CallingExtension
|
|
event_type = $CallRecord.LastEventType
|
|
note = $bridgeNote
|
|
called_at = $CallRecord.StartedAt.ToString('o')
|
|
is_test = $isTest
|
|
}
|
|
}
|
|
|
|
function Build-CallEndedPayload {
|
|
param([Parameter(Mandatory = $true)] $CallRecord)
|
|
|
|
$durationSeconds = [Math]::Max(0, [int][Math]::Round((New-TimeSpan -Start $CallRecord.StartedAt -End $CallRecord.EndedAt).TotalSeconds))
|
|
$outcome = 'terminata'
|
|
if ($CallRecord.Answered) {
|
|
$outcome = 'risposta'
|
|
}
|
|
|
|
$isTest = $script:BridgeMode -ne 'live'
|
|
$bridgeNote = if ($isTest) { 'bridge dry-run da watcher TAPI .NET' } else { 'bridge live da watcher TAPI .NET' }
|
|
|
|
return [ordered]@{
|
|
phone = $CallRecord.ExternalPhone
|
|
event_id = $CallRecord.EventId
|
|
outcome = $outcome
|
|
duration_seconds = $durationSeconds
|
|
ended_at = $CallRecord.EndedAt.ToString('o')
|
|
direction = $CallRecord.Direction
|
|
called_extension = $CallRecord.CalledExtension
|
|
calling_extension = $CallRecord.CallingExtension
|
|
event_type = $CallRecord.LastEventType
|
|
note = $bridgeNote
|
|
is_test = $isTest
|
|
}
|
|
}
|
|
|
|
function Invoke-BridgeRequest {
|
|
param(
|
|
[Parameter(Mandatory = $true)] [string]$Endpoint,
|
|
[Parameter(Mandatory = $true)] [System.Collections.Specialized.OrderedDictionary]$Payload
|
|
)
|
|
|
|
$json = $Payload | ConvertTo-Json -Depth 8 -Compress
|
|
if ($script:BridgeMode -eq 'dry-run') {
|
|
Write-LogLine ("BRIDGE DRYRUN endpoint={0} payload={1}" -f $Endpoint, $json)
|
|
return
|
|
}
|
|
|
|
if ($script:BridgeMode -ne 'live') {
|
|
return
|
|
}
|
|
|
|
if ($script:BaseUrl -eq '' -or $script:Token -eq '') {
|
|
Write-LogLine ("BRIDGE SKIP endpoint={0} reason=missing-baseurl-or-token payload={1}" -f $Endpoint, $json)
|
|
return
|
|
}
|
|
|
|
try {
|
|
$headers = @{ 'X-CTI-Token' = $script:Token }
|
|
$uri = ('{0}/api/v1/cti/panasonic/{1}' -f $script:BaseUrl.TrimEnd('/'), $Endpoint)
|
|
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Post -ContentType 'application/json' -Body $json
|
|
Write-LogLine ("BRIDGE LIVE OK endpoint={0} response={1}" -f $Endpoint, (($response | ConvertTo-Json -Depth 8 -Compress)))
|
|
}
|
|
catch {
|
|
Write-LogLine ("BRIDGE LIVE KO endpoint={0} error={1} payload={2}" -f $Endpoint, $_.Exception.Message, $json)
|
|
}
|
|
}
|
|
|
|
function Register-BridgeIncoming {
|
|
param([Parameter(Mandatory = $true)] $Snapshot)
|
|
|
|
if (-not (Should-EmitBridgeForSnapshot -Snapshot $Snapshot)) {
|
|
Write-LogLine ("BRIDGE IGNORE endpoint=incoming address={0} category={1} mode={2}" -f $Snapshot.AddressName, $Snapshot.AddressCategory, $script:BridgeWatchMode)
|
|
return
|
|
}
|
|
|
|
$resolvedEventId = Resolve-BridgeEventId -EventId $Snapshot.EventId
|
|
if ($resolvedEventId -eq '' -and $Snapshot.EventId -ne '') {
|
|
$resolvedEventId = $Snapshot.EventId
|
|
}
|
|
|
|
if ($resolvedEventId -eq '') {
|
|
$resolvedEventId = "{0}:{1}:{2}" -f $Snapshot.AddressName, $Snapshot.CalledExtension, $Snapshot.EventType
|
|
}
|
|
|
|
if (Test-BridgeCallRecentlyCompleted -EventId $resolvedEventId) {
|
|
Write-LogLine ("BRIDGE IGNORE endpoint=incoming reason=already-completed event_id={0} address={1}" -f $resolvedEventId, $Snapshot.AddressName)
|
|
return
|
|
}
|
|
|
|
if (-not $script:BridgeCalls.ContainsKey($resolvedEventId)) {
|
|
$matchedCall = Find-BridgeCallMatchBySignature -Snapshot $Snapshot
|
|
if ($null -ne $matchedCall) {
|
|
$resolvedEventId = $matchedCall.EventId
|
|
if ($Snapshot.EventId -ne '') {
|
|
$script:BridgeEventAliases[$Snapshot.EventId] = $resolvedEventId
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($script:BridgeCalls.ContainsKey($resolvedEventId)) {
|
|
return
|
|
}
|
|
|
|
$callRecord = [ordered]@{
|
|
EventId = $resolvedEventId
|
|
StartedAt = Get-Date
|
|
EndedAt = $null
|
|
LastEventType = $Snapshot.EventType
|
|
CalledExtension = $Snapshot.CalledExtension
|
|
CallingExtension = $Snapshot.CallingExtension
|
|
Direction = $Snapshot.Direction
|
|
ExternalPhone = $Snapshot.ExternalPhone
|
|
CallerName = $Snapshot.CallerName
|
|
Answered = $false
|
|
IncomingSent = $false
|
|
}
|
|
|
|
if ($Snapshot.EventId -ne '' -and $Snapshot.EventId -ne $resolvedEventId) {
|
|
$script:BridgeEventAliases[$Snapshot.EventId] = $resolvedEventId
|
|
}
|
|
|
|
$script:BridgeCalls[$resolvedEventId] = $callRecord
|
|
|
|
if ($callRecord.ExternalPhone -eq '') {
|
|
Write-LogLine ("BRIDGE SKIP endpoint=incoming reason=phone-missing event_id={0} called_extension={1}" -f $callRecord.EventId, $callRecord.CalledExtension)
|
|
Write-LogLine ("BRIDGE DEBUG event_id={0} rawCaller={1} rawCalled={2} rawConnected={3}" -f $callRecord.EventId, $Snapshot.RawCallerIdAddress, $Snapshot.RawCalledIdAddress, $Snapshot.RawConnectedIdAddress)
|
|
Write-LogLine ("BRIDGE DEBUG event_id={0} {1}" -f $callRecord.EventId, (Get-PhoneDebugSummary -Call $Snapshot.CallObject))
|
|
return
|
|
}
|
|
|
|
Invoke-BridgeRequest -Endpoint 'incoming' -Payload (Build-IncomingPayload -CallRecord $callRecord)
|
|
$callRecord.IncomingSent = $true
|
|
}
|
|
|
|
function Register-BridgeStateTransition {
|
|
param([Parameter(Mandatory = $true)] $Snapshot)
|
|
|
|
if (-not (Should-EmitBridgeForSnapshot -Snapshot $Snapshot)) {
|
|
Write-LogLine ("BRIDGE IGNORE endpoint=state address={0} category={1} mode={2}" -f $Snapshot.AddressName, $Snapshot.AddressCategory, $script:BridgeWatchMode)
|
|
return
|
|
}
|
|
|
|
if ($Snapshot.Direction -eq 'outbound' -and $Snapshot.AddressCategory -eq 'CO' -and $Snapshot.CallingExtension -eq '') {
|
|
Write-LogLine ("BRIDGE WARN unresolved-outbound-source address={0} phone={1} rawCaller={2} rawCalled={3} rawConnected={4}" -f $Snapshot.AddressName, $Snapshot.ExternalPhone, $Snapshot.RawCallerIdAddress, $Snapshot.RawCalledIdAddress, $Snapshot.RawConnectedIdAddress)
|
|
}
|
|
|
|
$resolvedEventId = Resolve-BridgeEventId -EventId $Snapshot.EventId
|
|
if ($resolvedEventId -eq '' -and $Snapshot.EventId -ne '') {
|
|
$resolvedEventId = $Snapshot.EventId
|
|
}
|
|
|
|
if ($resolvedEventId -eq '') {
|
|
$resolvedEventId = "{0}:{1}:{2}" -f $Snapshot.AddressName, $Snapshot.CalledExtension, $Snapshot.EventType
|
|
}
|
|
|
|
if (Test-BridgeCallRecentlyCompleted -EventId $resolvedEventId) {
|
|
Write-LogLine ("BRIDGE IGNORE endpoint=state reason=already-completed event_id={0} address={1}" -f $resolvedEventId, $Snapshot.AddressName)
|
|
return
|
|
}
|
|
|
|
if (-not $script:BridgeCalls.ContainsKey($resolvedEventId)) {
|
|
$matchedCall = Find-BridgeCallMatchBySignature -Snapshot $Snapshot
|
|
if ($null -ne $matchedCall) {
|
|
$resolvedEventId = $matchedCall.EventId
|
|
if ($Snapshot.EventId -ne '') {
|
|
$script:BridgeEventAliases[$Snapshot.EventId] = $resolvedEventId
|
|
}
|
|
}
|
|
}
|
|
|
|
$callRecord = $null
|
|
if ($script:BridgeCalls.ContainsKey($resolvedEventId)) {
|
|
$callRecord = $script:BridgeCalls[$resolvedEventId]
|
|
}
|
|
else {
|
|
$callRecord = [ordered]@{
|
|
EventId = $resolvedEventId
|
|
StartedAt = Get-Date
|
|
EndedAt = $null
|
|
LastEventType = $Snapshot.EventType
|
|
CalledExtension = $Snapshot.CalledExtension
|
|
CallingExtension = $Snapshot.CallingExtension
|
|
Direction = $Snapshot.Direction
|
|
ExternalPhone = $Snapshot.ExternalPhone
|
|
CallerName = $Snapshot.CallerName
|
|
Answered = $false
|
|
IncomingSent = $false
|
|
}
|
|
|
|
if ($Snapshot.EventId -ne '' -and $Snapshot.EventId -ne $resolvedEventId) {
|
|
$script:BridgeEventAliases[$Snapshot.EventId] = $resolvedEventId
|
|
}
|
|
|
|
$script:BridgeCalls[$resolvedEventId] = $callRecord
|
|
}
|
|
|
|
if ($callRecord.ExternalPhone -eq '' -and $Snapshot.ExternalPhone -ne '') {
|
|
$callRecord.ExternalPhone = $Snapshot.ExternalPhone
|
|
}
|
|
|
|
if ($callRecord.CallerName -eq '' -and $Snapshot.CallerName -ne '') {
|
|
$callRecord.CallerName = $Snapshot.CallerName
|
|
}
|
|
|
|
if ($callRecord.CalledExtension -eq '' -and $Snapshot.CalledExtension -ne '') {
|
|
$callRecord.CalledExtension = $Snapshot.CalledExtension
|
|
}
|
|
|
|
if ($callRecord.CallingExtension -eq '' -and $Snapshot.CallingExtension -ne '') {
|
|
$callRecord.CallingExtension = $Snapshot.CallingExtension
|
|
}
|
|
|
|
if ($callRecord.Direction -eq '' -and $Snapshot.Direction -ne '') {
|
|
$callRecord.Direction = $Snapshot.Direction
|
|
}
|
|
|
|
if ($Snapshot.EventType -ne '') {
|
|
$callRecord.LastEventType = $Snapshot.EventType
|
|
}
|
|
|
|
if (-not $callRecord.IncomingSent -and $callRecord.ExternalPhone -ne '') {
|
|
Invoke-BridgeRequest -Endpoint 'incoming' -Payload (Build-IncomingPayload -CallRecord $callRecord)
|
|
$callRecord.IncomingSent = $true
|
|
}
|
|
|
|
switch ([int]$Snapshot.CallState) {
|
|
$CallStateConnected {
|
|
$callRecord.Answered = $true
|
|
Write-LogLine ("BRIDGE MARK answered event_id={0} called_extension={1}" -f $callRecord.EventId, $callRecord.CalledExtension)
|
|
break
|
|
}
|
|
$CallStateDisconnected {
|
|
$callRecord.EndedAt = Get-Date
|
|
if ($callRecord.ExternalPhone -eq '') {
|
|
Write-LogLine ("BRIDGE SKIP endpoint=call-ended reason=phone-missing event_id={0} called_extension={1}" -f $callRecord.EventId, $callRecord.CalledExtension)
|
|
Write-LogLine ("BRIDGE DEBUG event_id={0} rawCaller={1} rawCalled={2} rawConnected={3}" -f $callRecord.EventId, $Snapshot.RawCallerIdAddress, $Snapshot.RawCalledIdAddress, $Snapshot.RawConnectedIdAddress)
|
|
Write-LogLine ("BRIDGE DEBUG event_id={0} {1}" -f $callRecord.EventId, (Get-PhoneDebugSummary -Call $Snapshot.CallObject))
|
|
}
|
|
else {
|
|
Invoke-BridgeRequest -Endpoint 'call-ended' -Payload (Build-CallEndedPayload -CallRecord $callRecord)
|
|
}
|
|
|
|
Mark-BridgeCallCompleted -EventId $resolvedEventId
|
|
[void]$script:BridgeCalls.Remove($resolvedEventId)
|
|
if ($Snapshot.EventId -ne '' -and $script:BridgeEventAliases.ContainsKey($Snapshot.EventId)) {
|
|
[void]$script:BridgeEventAliases.Remove($Snapshot.EventId)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
function Describe-EventPayload {
|
|
param($Payload)
|
|
|
|
if ($null -eq $Payload) {
|
|
return 'payload=<null>'
|
|
}
|
|
|
|
$parts = @()
|
|
|
|
$payloadTypeName = Get-SafeTypeName -Object $Payload
|
|
$parts += ("payloadType={0}" -f $payloadTypeName)
|
|
|
|
|
|
if ($payloadTypeName -eq '<type-error>') {
|
|
try {
|
|
if ([System.Runtime.InteropServices.Marshal]::IsComObject($Payload)) {
|
|
$parts += 'payloadComObject=true'
|
|
}
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
|
|
$eventValue = Get-SafeProperty -Object $Payload -Name 'Event'
|
|
if ($null -ne $eventValue) {
|
|
$parts += ("event={0}" -f (Convert-ToSafeString -Value $eventValue))
|
|
|
|
}
|
|
|
|
$stateValue = Get-SafeProperty -Object $Payload -Name 'State'
|
|
if ($null -ne $stateValue) {
|
|
$parts += ("state={0}" -f (Convert-ToSafeString -Value $stateValue))
|
|
}
|
|
|
|
|
|
$causeValue = Get-SafeProperty -Object $Payload -Name 'Cause'
|
|
if ($null -ne $causeValue) {
|
|
$parts += ("cause={0}" -f (Convert-ToSafeString -Value $causeValue))
|
|
|
|
}
|
|
|
|
$errorValue = Get-SafeProperty -Object $Payload -Name 'Error'
|
|
if ($null -ne $errorValue) {
|
|
$parts += ("error={0}" -f (Convert-ToSafeString -Value $errorValue))
|
|
}
|
|
|
|
$address = Get-SafeProperty -Object $Payload -Name 'Address'
|
|
if ($null -ne $address) {
|
|
$parts += (Describe-Address -Address $address)
|
|
}
|
|
|
|
$call = Get-SafeProperty -Object $Payload -Name 'Call'
|
|
if ($null -ne $call) {
|
|
$parts += (Describe-Call -Call $call)
|
|
}
|
|
|
|
if ($parts.Count -le 1) {
|
|
$parts += 'payloadDetails=non-accessible'
|
|
}
|
|
|
|
return ($parts -join ' ')
|
|
}
|
|
|
|
function Try-SubscribeDotNetEvent {
|
|
param(
|
|
[Parameter(Mandatory = $true)] $InputObject,
|
|
[Parameter(Mandatory = $true)] [string]$SourceIdentifier,
|
|
[Parameter(Mandatory = $true)] [string]$Label
|
|
)
|
|
|
|
try {
|
|
$events = @($InputObject.GetType().GetEvents() | Select-Object -ExpandProperty Name)
|
|
Write-LogLine ("DOTNET CHECK label={0} type={1} events={2}" -f $Label, $InputObject.GetType().FullName, ($events -join ','))
|
|
if (-not ($events -contains 'Event')) {
|
|
return [pscustomobject]@{
|
|
Ok = $false
|
|
Error = 'evento .NET Event non esposto'
|
|
}
|
|
}
|
|
|
|
Register-ObjectEvent -InputObject $InputObject -EventName Event -SourceIdentifier $SourceIdentifier | Out-Null
|
|
return [pscustomobject]@{
|
|
Ok = $true
|
|
Error = ''
|
|
}
|
|
}
|
|
catch {
|
|
return [pscustomobject]@{
|
|
Ok = $false
|
|
Error = $_.Exception.Message
|
|
}
|
|
}
|
|
}
|
|
|
|
function Process-ReceivedEvent {
|
|
param([Parameter(Mandatory = $true)] $EventRecord)
|
|
|
|
try {
|
|
$sourceIdentifier = Convert-ToSafeString -Value $EventRecord.SourceIdentifier
|
|
$sourceArgs = @($EventRecord.SourceArgs)
|
|
$argTypes = @()
|
|
foreach ($arg in $sourceArgs) {
|
|
$argTypes += (Get-SafeTypeName -Object $arg)
|
|
}
|
|
|
|
$eventType = ''
|
|
if ($sourceArgs.Count -ge 1 -and $null -ne $sourceArgs[0]) {
|
|
$eventType = Convert-ToSafeString -Value $sourceArgs[0]
|
|
}
|
|
|
|
if (Should-LogDotNetEvent -EventType $eventType) {
|
|
$payloadDescription = 'payload=<none>'
|
|
if ($sourceArgs.Count -ge 2) {
|
|
$payloadDescription = Describe-EventPayload -Payload $sourceArgs[1]
|
|
}
|
|
|
|
Write-LogLine ("DOTNET EVENT source={0} eventType={1} argTypes={2} {3}" -f $sourceIdentifier, $eventType, ($argTypes -join ','), $payloadDescription)
|
|
}
|
|
|
|
if ($script:BridgeMode -ne 'none' -and $sourceArgs.Count -ge 2 -and ($eventType -eq 'TE_CALLNOTIFICATION' -or $eventType -eq 'TE_CALLSTATE')) {
|
|
$snapshot = Get-CallSnapshot -EventType $eventType -Payload $sourceArgs[1] -WatchList $watchList
|
|
if ($eventType -eq 'TE_CALLNOTIFICATION') {
|
|
Register-BridgeIncoming -Snapshot $snapshot
|
|
}
|
|
elseif ($eventType -eq 'TE_CALLSTATE' -and $snapshot.CallState -ne '') {
|
|
Register-BridgeStateTransition -Snapshot $snapshot
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-LogLine ("DOTNET EVENT ERROR source={0} error={1}" -f (Convert-ToSafeString -Value $EventRecord.SourceIdentifier), $_.Exception.Message)
|
|
}
|
|
}
|
|
|
|
$script:LogFilePath = $LogFile
|
|
$script:BridgeMode = $BridgeMode
|
|
$script:BaseUrl = $BaseUrl
|
|
$script:Token = $Token
|
|
$script:IncludeDiagnosticEvents = $IncludeDiagnosticEvents.IsPresent
|
|
$script:BridgeCalls = @{}
|
|
$script:BridgeEventAliases = @{}
|
|
$script:CompletedBridgeCalls = @{}
|
|
$watchList = @(Parse-WatchList -ExtensionsValue $Extensions)
|
|
$script:BridgeWatchMode = Get-BridgeWatchMode -WatchList $watchList
|
|
$subscriptions = @()
|
|
$wrappers = @()
|
|
|
|
$assembly = Get-InteropAssembly -OutputDir $InteropOutputDir
|
|
$tapiClassType = Get-TypeByName -Assembly $assembly -Name 'TAPIClass'
|
|
$typedEventInterfaceType = Get-TypeByName -Assembly $assembly -Name 'ITTAPIEventNotification_Event'
|
|
$dispatchEventInterfaceType = Get-TypeByName -Assembly $assembly -Name 'ITTAPIDispatchEventNotification_Event'
|
|
|
|
$tapi = New-Object -ComObject TAPI.TAPI
|
|
$tapi.Initialize()
|
|
$eventFilter = $TapiEventFilterCallNotification + $TapiEventFilterCallState + $TapiEventFilterPrivate
|
|
if ($script:IncludeDiagnosticEvents) {
|
|
$eventFilter += $TapiEventFilterCallMedia + $TapiEventFilterCallHub + $TapiEventFilterCallInfoChange
|
|
}
|
|
$tapi.EventFilter = $eventFilter
|
|
|
|
$allAddresses = @(Flatten-ObjectArray -Items @(Convert-ComCollectionToArray -Collection $tapi.Addresses))
|
|
$addresses = @($allAddresses | Where-Object {
|
|
Should-WatchAddress -Address $_ -WatchList $watchList
|
|
})
|
|
|
|
Write-LogLine "=== AVVIO WATCH DOTNET TAPI ==="
|
|
Write-LogLine ("Assembly interop: {0}" -f $assembly.FullName)
|
|
Write-LogLine "Watch list: $Extensions"
|
|
Write-LogLine ("Watch list expanded: {0}" -f ($watchList -join ','))
|
|
Write-LogLine "Provider addresses totali: $($allAddresses.Count)"
|
|
Write-LogLine "Provider addresses filtrati: $($addresses.Count)"
|
|
Write-LogLine "Bridge mode: $BridgeMode"
|
|
Write-LogLine "Bridge watch mode: $($script:BridgeWatchMode)"
|
|
Write-LogLine ("Diagnostic events: {0}" -f ($(if ($script:IncludeDiagnosticEvents) { 'enabled' } else { 'disabled' })))
|
|
|
|
foreach ($address in $addresses) {
|
|
Write-LogLine ("ADDRESS READY {0}" -f (Describe-Address -Address $address))
|
|
$registration = Try-RegisterCallNotifications -Tapi $tapi -Address $address
|
|
if ($registration.Ok) {
|
|
Write-LogLine ("REGISTER OK mode=monitor+owner cookie={0} {1}" -f $registration.Cookie, (Describe-Address -Address $address))
|
|
}
|
|
else {
|
|
Write-LogLine ("REGISTER KO {0} error={1}" -f (Describe-Address -Address $address), $registration.Error)
|
|
}
|
|
}
|
|
|
|
if ($null -ne $tapiClassType) {
|
|
try {
|
|
$typedTapi = [System.Runtime.InteropServices.Marshal]::CreateWrapperOfType($tapi, $tapiClassType)
|
|
$wrappers += $typedTapi
|
|
$subscriptions += [pscustomobject]@{ Label = 'tapi-class'; Result = (Try-SubscribeDotNetEvent -InputObject $typedTapi -SourceIdentifier 'NetGescon.Tapi.DotNet.TapiClass' -Label 'tapi-class') }
|
|
}
|
|
catch {
|
|
Write-LogLine ("DOTNET WRAP KO label=tapi-class error={0}" -f $_.Exception.Message)
|
|
}
|
|
}
|
|
|
|
if ($null -ne $typedEventInterfaceType) {
|
|
try {
|
|
$typedEventSource = [System.Runtime.InteropServices.Marshal]::CreateWrapperOfType($tapi, $typedEventInterfaceType)
|
|
$wrappers += $typedEventSource
|
|
$subscriptions += [pscustomobject]@{ Label = 'typed-event-interface'; Result = (Try-SubscribeDotNetEvent -InputObject $typedEventSource -SourceIdentifier 'NetGescon.Tapi.DotNet.TypedEvent' -Label 'typed-event-interface') }
|
|
}
|
|
catch {
|
|
Write-LogLine ("DOTNET WRAP KO label=typed-event-interface error={0}" -f $_.Exception.Message)
|
|
}
|
|
}
|
|
|
|
if ($null -ne $dispatchEventInterfaceType) {
|
|
try {
|
|
$dispatchEventSource = [System.Runtime.InteropServices.Marshal]::CreateWrapperOfType($tapi, $dispatchEventInterfaceType)
|
|
$wrappers += $dispatchEventSource
|
|
$subscriptions += [pscustomobject]@{ Label = 'dispatch-event-interface'; Result = (Try-SubscribeDotNetEvent -InputObject $dispatchEventSource -SourceIdentifier 'NetGescon.Tapi.DotNet.DispatchEvent' -Label 'dispatch-event-interface') }
|
|
}
|
|
catch {
|
|
Write-LogLine ("DOTNET WRAP KO label=dispatch-event-interface error={0}" -f $_.Exception.Message)
|
|
}
|
|
}
|
|
|
|
foreach ($subscription in $subscriptions) {
|
|
if ($subscription.Result.Ok) {
|
|
Write-LogLine ("DOTNET SUBSCRIBE OK label={0}" -f $subscription.Label)
|
|
}
|
|
else {
|
|
Write-LogLine ("DOTNET SUBSCRIBE KO label={0} error={1}" -f $subscription.Label, $subscription.Result.Error)
|
|
}
|
|
}
|
|
|
|
Write-LogLine 'Fai ora chiamate reali su 201, 205 o 206.'
|
|
|
|
$end = $null
|
|
if ($Seconds -gt 0) {
|
|
$end = (Get-Date).AddSeconds($Seconds)
|
|
}
|
|
|
|
try {
|
|
while ($true) {
|
|
if ($null -ne $end -and (Get-Date) -ge $end) {
|
|
break
|
|
}
|
|
|
|
$eventRecord = Wait-Event -Timeout 1
|
|
if ($null -eq $eventRecord) {
|
|
continue
|
|
}
|
|
|
|
$sourceIdentifier = Convert-ToSafeString -Value $eventRecord.SourceIdentifier
|
|
if ($sourceIdentifier -notlike 'NetGescon.Tapi.DotNet.*') {
|
|
Remove-Event -EventIdentifier $eventRecord.EventIdentifier -ErrorAction SilentlyContinue
|
|
continue
|
|
}
|
|
|
|
Process-ReceivedEvent -EventRecord $eventRecord
|
|
Remove-Event -EventIdentifier $eventRecord.EventIdentifier -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
finally {
|
|
foreach ($sourceIdentifier in @('NetGescon.Tapi.DotNet.TapiClass', 'NetGescon.Tapi.DotNet.TypedEvent', 'NetGescon.Tapi.DotNet.DispatchEvent')) {
|
|
Unregister-Event -SourceIdentifier $sourceIdentifier -ErrorAction SilentlyContinue
|
|
Get-Event -SourceIdentifier $sourceIdentifier -ErrorAction SilentlyContinue | Remove-Event -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
try {
|
|
$tapi.Shutdown()
|
|
}
|
|
catch {
|
|
}
|
|
} |