LUM.AudioCodes.psm1

Function New-AcManipulation{
    <#
    .SYNOPSIS
       Sets or updates a number manipulation
    .DESCRIPTION
       This script will create or update an SBC number manipulation on Audiocodes.
    .PARAMETER remoteHost
        The LAN IP address of the AudioCodes Mediant
    .PARAMETER type
        Type of manipulation;
            - a mask creates an outbound manipulations which changes the source number
            - a forward creates an inbound manipulation which changes the target number
    .PARAMETER name
        Name of the manipulation.
    .PARAMETER originalNumber
        Number to be manipulated
    .PARAMETER manipulatedNumber
        New Number
    .PARAMETER Credential
        Credentials for Audiocodes. Use "$x = Get-Credential" to store the credential in a variable. Then use "-Credential $x" to pass this to the script
    .PARAMETER Overwrite
        When specified any existing records with the same source number will be overwritten
    .PARAMETER Port
        Port of the telnet interface
    .PARAMETER WaitTime
        Wait in between of issuing telnet commands
    .EXAMPLE
       New-AcManipulation -remotehost 10.0.0.12 -name fJohnsonMask -originalNumber +41441234567 -manipulatedNumber +41448888888
    .EXAMPLE
       $x = Get-Credential
       New-AcManipulation -remotehost 10.0.0.12 -name fJohnsonMask -originalNumber +41441234567 -manipulatedNumber +41448888888 -credential $x
    .INPUTS
       Parameters
    .OUTPUTS
       Script results in output / verbose stream
    .NOTES
    +---------------------------------------------------------------------------------------------+
    | ORIGIN STORY |
    +---------------------------------------------------------------------------------------------+
    | DATE : 2018.07.02
    | AUTHOR : Michael L�rsen
    | E-Mail : michael.luersen@mondaycoffee.com
    | DESCRIPTION : First PSM1 Function
    +---------------------------------------------------------------------------------------------+
    #>

    Param(
    [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
    [Parameter(Mandatory=$true)]
    [ipaddress]$remoteHost,
    [ValidateSet('forward','mask')][String]$type = 'mask',
    [Parameter(Mandatory=$true)]
    [string]$name,
    [Parameter(Mandatory=$true)]
    [ValidateLength(12,12)]
    [string]$originalNumber,
    [Parameter(Mandatory=$true)]
    [ValidateLength(12,12)]
    [string]$manipulatedNumber,
    [System.Management.Automation.CredentialAttribute()]$Credential,
    [switch]$overwrite,
    [string]$Port = "23",
    [int]$WaitTime = 500
    )

    if($null -eq $Credential){
        $credential = Get-Credential Admin -Message "Enter credentials for $remotehost"
    }

    Write-Output "### Configuring $remotehost ###"

    Write-Verbose "Attaching to the remote device, setup streaming requirements"
    $Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
    If ($Socket)
    {   $Stream = $Socket.GetStream()
        $Writer = New-Object System.IO.StreamWriter($Stream)
        $Buffer = New-Object System.Byte[] 1024 
        $Encoding = New-Object System.Text.AsciiEncoding
        $global:Result = ""

         Function TelnetCmd{
            Param (
                [string]$cmd
            )
            Write-Verbose "issuing command: $cmd" 
            $Writer.WriteLine($cmd) 
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
            While($Stream.DataAvailable) 
            {   $Read = $Stream.Read($Buffer, 0, 1024) 
                $global:result += ($Encoding.GetString($Buffer, 0, $Read))
            }
        }


        Write-Verbose "Now start issuing the commands: username"
        
       
        $Writer.WriteLine($credential.GetNetworkCredential().username) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $global:result += ($Encoding.GetString($Buffer, 0, $Read))
        }

        Write-Verbose "entering password"
        $Writer.WriteLine($credential.GetNetworkCredential().password) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $global:result += ($Encoding.GetString($Buffer, 0, $Read))
        }

        if($global:result -notlike "*>*"){
            write-host $global:result
            
            $Writer.WriteLine("quit") 
            $Writer.Flush()
            Write-Error "Error on enter password for $remotehost"
            Return 
        }

        Write-Verbose "enabling advanced options"
        TelnetCmd -cmd 'enable'
        
        Write-Verbose "entering Enable password"
        TelnetCmd -cmd 'Admin'
        Write-verbose "Configuring Voip menu"
        TelnetCmd -cmd 'configure voip'

        Write-Verbose "query for existing masks / forwards for this number"
        #find the users row
        if($type -eq 'mask'){
            TelnetCmd -cmd ('sbc manipulations ip-outbound-manipulation find-by src-user-name-prefix "' + $originalNumber + '"')

            if($global:result -match "(?smi)(ip-outbound-manipulation \d+)\n*.*?(src-user-name-prefix \D$originalNumber\D)"){
                $global:result -match '(ip-outbound-manipulation) \d+'
                $row = $matches[0].substring(25)
                $blnMatch = $true
            }
            
        }
        if($type -eq 'forward'){
            TelnetCmd -cmd ('sbc manipulations ip-inbound-manipulation find-by dst-user-name-prefix "' + $originalNumber + '"')

            if($global:result -match "(?smi)(ip-inbound-manipulation \d+)\n*.*?(dst-user-name-prefix \D$originalNumber\D)"){
                $global:result -match '(ip-inbound-manipulation) \d+'
                $row = $matches[0].substring(24)
                $blnMatch = $true
            }
        }

        # somehow the updating mask was kind of unreliable, testing now with a bit of wait time.
        # if this doesn't work try increasing wait or changing the updating mask cmd to 'sbc sbc manipu.....'
        # start-sleep -Seconds 3

        if($blnMatch -eq $true){
            if($overwrite -ne $true){
                Write-Warning "$name already exists (with index number $row). `nUse the parameter -overwrite to update this."
                break
            }
            #select the correct row
            if($type -eq 'mask'){
                Write-Verbose "Updating Mask"
                TelnetCmd -cmd (' sbc manipulations ip-outbound-manipulation "' + $row + '"')
            }
            if($type -eq 'forward'){
                Write-Verbose "Updating Forward"
                TelnetCmd -cmd (' sbc manipulations ip-inbound-manipulation "' + $row + '"')
            }
        }else{
            if($type -eq 'mask'){
                Write-Verbose "Creating Mask"
                TelnetCmd -cmd ' sbc manipulations ip-outbound-manipulation new'
            }
            if($type -eq 'forward'){
                Write-Verbose "Creating Forward"
                TelnetCmd -cmd ' sbc manipulations ip-inbound-manipulation new'
            }
        }

        if($type -eq 'mask'){
            TelnetCmd -cmd ('manipulation-name "' + $name + '"')
            TelnetCmd -cmd ('src-user-name-prefix "' + $originalNumber + '"')
            TelnetCmd -cmd 'manipulated-uri source'
        }
        if($type -eq 'forward'){
            TelnetCmd -cmd ('manipulation-name "' + $name + '"')
            TelnetCmd -cmd ('dst-user-name-prefix "' + $originalNumber + '"')
            TelnetCmd -cmd 'manipulated-uri destination'
        }

        TelnetCmd -cmd 'remove-from-left 12'
        #TelnetCmd -cmd 'remove-from-right 0'
        #TelnetCmd -cmd 'leave-from-right 255'
        TelnetCmd -cmd ('prefix-to-add "' + $manipulatedNumber + '"')
        TelnetCmd -cmd 'activate'
        TelnetCmd -cmd 'exit'
        TelnetCmd -cmd 'exit'
        TelnetCmd -cmd 'write'
        TelnetCmd -cmd 'quit'

    }Else{  
        Write-Verbose $global:result 
        Write-Error "Unable to connect to host: $($RemoteHost):$Port"
    }
    Write-Verbose $global:result 
    Write-Output "### Finished item $name ###"
}

Function Remove-AcManipulation{
    <#
    .SYNOPSIS
       Removes a number manipulation
    .DESCRIPTION
       This script will remove an SBC number manipulation on Audiocodes.
    .PARAMETER remoteHost
        The LAN IP address of the AudioCodes Mediant
    .PARAMETER type
        Type of manipulation;
            - a mask removes an outbound manipulations which changes the source number
            - a forward removes an inbound manipulation which changes the target number
    .PARAMETER originalNumber
        Number to be manipulated
    .PARAMETER Credential
        Credentials for Audiocodes. Use "$x = Get-Credential" to store the credential in a variable. Then use "-Credential $x" to pass this to the script
    .PARAMETER Port
        Port of the telnet interface
    .PARAMETER WaitTime
        Wait in between of issuing telnet commands
    .EXAMPLE
       Remove-AcManipulation -remotehost 10.0.0.12 -originalNumber +41441234567
    .EXAMPLE
       $x = Get-Credential
       New-AcManipulation -remotehost 10.0.0.12 -originalNumber +41441234567 -credential $x
    .INPUTS
       Parameters
    .OUTPUTS
       Script results in output / verbose stream
    .NOTES
    +---------------------------------------------------------------------------------------------+
    | ORIGIN STORY |
    +---------------------------------------------------------------------------------------------+
    | DATE : 2018.10.
    | AUTHOR : Michael L�rsen / Roman Schaal
    | E-Mail : michael.luersen@mondaycoffee.com
    +---------------------------------------------------------------------------------------------+
    #>

    Param(
    [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
    [Parameter(Mandatory=$true)]
    [ipaddress]$remoteHost,
    [ValidateSet('forward','mask')][String]$type = 'mask',
    [Parameter(Mandatory=$true)]
    [ValidateLength(12,12)]
    [string]$originalNumber,
    [Parameter(Mandatory=$true)]
    [System.Management.Automation.CredentialAttribute()]$Credential,
    [string]$Port = "23",
    [int]$WaitTime = 500
    )

    if($null -eq $Credential){
        $credential = Get-Credential Admin -Message "Enter credentials for $remotehost"
    }

    Write-Output "### Configuring $remotehost ###"

    Write-Verbose "Attaching to the remote device, setup streaming requirements"
    $Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
    If ($Socket)
    {   $Stream = $Socket.GetStream()
        $Writer = New-Object System.IO.StreamWriter($Stream)
        $Buffer = New-Object System.Byte[] 1024 
        $Encoding = New-Object System.Text.AsciiEncoding
        $global:Result = ""

         Function TelnetCmd{
            Param (
                [string]$cmd
            )
            Write-Verbose "issuing command: $cmd" 
            $Writer.WriteLine($cmd) 
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
            While($Stream.DataAvailable) 
            {   $Read = $Stream.Read($Buffer, 0, 1024) 
                $global:result += ($Encoding.GetString($Buffer, 0, $Read))
            }
        }


        Write-Verbose "Now start issuing the commands: username"
        
       
        $Writer.WriteLine($credential.GetNetworkCredential().username) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $global:result += ($Encoding.GetString($Buffer, 0, $Read))
        }

        Write-Verbose "entering password"
        $Writer.WriteLine($credential.GetNetworkCredential().password) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $global:result += ($Encoding.GetString($Buffer, 0, $Read))
        }

        if($global:result -notlike "*>*"){
            write-host $global:result
            
            $Writer.WriteLine("quit") 
            $Writer.Flush()
            Write-Error "Error on enter password for $remotehost"
            Return 
        }

        Write-Verbose "enabling advanced options"
        TelnetCmd -cmd 'enable'
        
        Write-Verbose "entering Enable password"
        TelnetCmd -cmd 'Admin'
        Write-verbose "Configuring Voip menu"
        TelnetCmd -cmd 'configure voip'

        Write-Verbose "query for existing masks / forwards for this number"
        #find the users row
        if($type -eq 'mask'){
            TelnetCmd -cmd ('sbc manipulations ip-outbound-manipulation find-by src-user-name-prefix "' + $originalNumber + '"')

            if($global:result -match "(?smi)(ip-outbound-manipulation \d+)\n*.*?(src-user-name-prefix \D$originalNumber\D)"){
                $global:result -match '(ip-outbound-manipulation) \d+'
                $row = $matches[0].substring(25)
                $blnMatch = $true
            }
            
        }
        if($type -eq 'forward'){
            TelnetCmd -cmd ('sbc manipulations ip-inbound-manipulation find-by dst-user-name-prefix "' + $originalNumber + '"')

            if($global:result -match "(?smi)(ip-inbound-manipulation \d+)\n*.*?(dst-user-name-prefix \D$originalNumber\D)"){
                $global:result -match '(ip-inbound-manipulation) \d+'
                $row = $matches[0].substring(24)
                $blnMatch = $true
            }
        }

        # somehow the updating mask was kind of unreliable, testing now with a bit of wait time.
        # if this doesn't work try increasing wait or changing the updating mask cmd to 'sbc sbc manipu.....'
        # start-sleep -Seconds 3

        if($blnMatch -eq $true){
            #select the correct row
            if($type -eq 'mask'){
                Write-Verbose "Updating Mask"
                TelnetCmd -cmd ('exit')
                TelnetCmd -cmd (' no sbc manipulations ip-outbound-manipulation "' + $row + '"')
            }
            if($type -eq 'forward'){
                Write-Verbose "Updating Forward"
                TelnetCmd -cmd ('exit')
                TelnetCmd -cmd (' no sbc manipulations ip-inbound-manipulation "' + $row + '"')
            }
        }else{
            if($type -eq 'mask'){
                Write-Warning "Could not find the mask with number $originalNumber "
                break
            }
            if($type -eq 'forward'){
                Write-Warning "Could not find the forward with number $originalNumber "
                break
            }
        }

        TelnetCmd -cmd 'exit'
        TelnetCmd -cmd 'activate'
        TelnetCmd -cmd 'write'
        TelnetCmd -cmd 'quit'

    }Else{  
        Write-Verbose $global:result 
        Write-Error "Unable to connect to host: $($RemoteHost):$Port"
    }
    Write-Verbose $global:result 
    Write-Output "### Finished item $name ###"
}

Function New-AcManipulationCsv{
    <#
    .SYNOPSIS
       Sets or updates a number manipulation by CSV file
    .DESCRIPTION
       This script will create or update an SBC number manipulation on Audiocodes.
    .PARAMETER csvFile
        Full path to the CSV file to be used.
        csvFile contains headers and must look like this:
             
            remotehost,name,originalNumber,manipulatedNumber,type
            10.0.0.10,johnsonsmask,+41441234567,+41441234999,mask
            10.0.0.10,billyforward,+41441234567,+41441234999,forward
 
    .PARAMETER Credential
        Credentials for Audiocodes. Used stored credential from variable or -Credential (Get-Credential)
    .PARAMETER Overwrite
        When specified any existing records with the same source number will be overwritten
    .EXAMPLE
       New-AcManipulationCsv -csvFile c:\config.csv -credential $x -Overwrite
    .INPUTS
       Parameters
    .OUTPUTS
       Script results in output / verbose stream
    .NOTES
    +---------------------------------------------------------------------------------------------+
    | ORIGIN STORY |
    +---------------------------------------------------------------------------------------------+
    | DATE : 2018.07.02
    | AUTHOR : Michael L�rsen
    | E-Mail : michael.luersen@mondaycoffee.com
    | DESCRIPTION : First PSM1 Function
    +---------------------------------------------------------------------------------------------+
    #>

    Param(
    [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
    [System.Management.Automation.CredentialAttribute()]$Credential,
    [string]$csvFile,
    [switch]$overwrite
    )

    if($null -eq $Credential){
        $credential = Get-Credential Admin -Message "Credentials for $remotehost"
    }

    $list = Import-Csv -Delimiter "," -Path $csvFile
    write-verbose "configuring $($list.count) items"
    foreach($item in $list){
        write-verbose "configuring $item"
        if($overwrite -eq $true){
            New-AcManipulation `
                -remotehost $item.remotehost `
                -name $item.name `
                -originalNumber $item.originalNumber `
                -manipulatedNumber $item.manipulatedNumber `
                -type $item.type `
                -credential $Credential `
                -overwrite
        }else{
            New-AcManipulation `
                -remotehost $item.remotehost `
                -name $item.name `
                -originalNumber $item.originalNumber `
                -manipulatedNumber $item.manipulatedNumber `
                -type $item.type `
                -credential $Credential
        }
    }
    write-output "### Finished configuring host(s) ###"
}

Function Get-AcManipulation{
    <#
    .SYNOPSIS
       Gets the current number manipulations
    .DESCRIPTION
       This script will create or update an SBC number manipulation on Audiocodes.
    .PARAMETER remoteHost
        The LAN IP address of the AudioCodes Mediant
    .PARAMETER type
        Type of manipulation;
            - a mask is an outbound manipulations which changes the source number
            - a forward is an inbound manipulation which changes the target number
    .PARAMETER Credential
        Credentials for Audiocodes. Use "$x = Get-Credential" to store the credential in a variable. Then use "-Credential $x" to pass this to the script
    .PARAMETER Port
        Port of the telnet interface
    .PARAMETER WaitTime
        Wait in between of issuing telnet commands
    .EXAMPLE
       Get-AcManipulation -remotehost 10.0.0.12
    .EXAMPLE
       $x = Get-Credential
       Get-AcManipulation -remotehost 10.0.0.12 -type forward -credential $x
    .INPUTS
       Parameters
    .OUTPUTS
       $global:output contains an array of objects with the results.
       Script results in output / verbose stream
    .NOTES
    +---------------------------------------------------------------------------------------------+
    | ORIGIN STORY |
    +---------------------------------------------------------------------------------------------+
    | DATE : 2018.07.02
    | AUTHOR : Michael L?rsen
    | E-Mail : michael.luersen@mondaycoffee.com
    | DESCRIPTION : First PSM1 Function
    +---------------------------------------------------------------------------------------------+
    #>

    Param(
    [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
    [Parameter(Mandatory=$true)]
    [ipaddress]$remoteHost,
    [ValidateSet('forward','mask')][String]$type = 'mask',
    [System.Management.Automation.CredentialAttribute()]$Credential,
    [string]$Port = "23",
    [int]$WaitTime = 500
    )

    if($null -eq $Credential){
        $credential = Get-Credential Admin -Message "Enter credentials for $remotehost"
    }

    #Write-Output "### Configuring $remotehost ###"

    Write-Verbose "Attaching to the remote device, setup streaming requirements"
    $Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
    If ($Socket)
    {   $Stream = $Socket.GetStream()
        $Writer = New-Object System.IO.StreamWriter($Stream)
        $Buffer = New-Object System.Byte[] 1024 
        $Encoding = New-Object System.Text.AsciiEncoding
        $global:Result = ""

         Function TelnetCmd{
            Param (
                [string]$cmd
            )
            Write-Verbose "issuing command: $cmd" 
            $Writer.WriteLine($cmd) 
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
            While($Stream.DataAvailable) 
            {   $Read = $Stream.Read($Buffer, 0, 1024) 
                $global:result += ($Encoding.GetString($Buffer, 0, $Read))
            }
        }


        Write-Verbose "Now start issuing the commands: username"
        
       
        $Writer.WriteLine($credential.GetNetworkCredential().username) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $global:result += ($Encoding.GetString($Buffer, 0, $Read))
        }

        Write-Verbose "entering password"
        $Writer.WriteLine($credential.GetNetworkCredential().password) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $global:result += ($Encoding.GetString($Buffer, 0, $Read))
        }

        if($global:result -notlike "*>*"){
            write-host $global:result
            
            $Writer.WriteLine("quit") 
            $Writer.Flush()
            Write-Error "Error on enter password for $remotehost"
            Return 
        }

        Write-Verbose "enabling advanced options"
        TelnetCmd -cmd 'enable'
        
        Write-Verbose "entering Enable password"
        TelnetCmd -cmd 'Admin'
        Write-verbose "Configuring Voip menu"
        TelnetCmd -cmd 'configure voip'

        if($type -eq 'mask'){
            write-verbose "Retrieving Outbound manipulations"
            $Writer.WriteLine("sbc manipulations ip-outbound-manipulation display") 
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
            While($Stream.DataAvailable) 
            {   $Read = $Stream.Read($Buffer, 0, 1024) 
                $manipulation = -join ($Encoding.GetString($Buffer, 0, $Read))
                while($manipulation.substring($manipulation.length - 8) -notlike "*)#*"){
                    $Writer.WriteLine(" ") 
                    $Writer.Flush()
                    Start-Sleep -Milliseconds $WaitTime
                    While($Stream.DataAvailable) 
                    {   $Read = $Stream.Read($Buffer, 0, 1024) 
                        $manipulation += -join ($Encoding.GetString($Buffer, 0, $Read))
                    }
                }
            }
        }else{ #type is forward
            write-verbose "Retrieving Inbound manipulations"
            $Writer.WriteLine("sbc manipulations ip-inbound-manipulation display") 
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
            While($Stream.DataAvailable) 
            {   $Read = $Stream.Read($Buffer, 0, 1024) 
                $manipulation = -join ($Encoding.GetString($Buffer, 0, $Read))
                while($manipulation.substring($manipulation.length - 8) -notlike "*)#*"){
                    $Writer.WriteLine(" ") 
                    $Writer.Flush()
                    Start-Sleep -Milliseconds $WaitTime
                    While($Stream.DataAvailable) 
                    {   $Read = $Stream.Read($Buffer, 0, 1024) 
                        $manipulation += -join ($Encoding.GetString($Buffer, 0, $Read))
                    }
                }
            }
        }
 
        $Writer.WriteLine("quit") 
        $Writer.Flush()

        #modify this part with the text which appears in the ini file, if it fails to remove it.
        write-verbose "replacing --MORE-- xxxxx from strings"
        $manipulation = $manipulation -replace ("\s{2}(--MORE--)([^\w]{27})")
        $manipulation = $manipulation -replace ("\s(--MORE--)([^\w]{27})")
        $manipulation = $manipulation -replace(" --MORE-- ","")
        write-verbose $manipulation
        $global:output = @()

        foreach($line in $($manipulation -split "`r`n")){
            if($line -like "*)#*" -or $line.trim() -like ""){
                Break
            }
            if ($line -like "*ip-*bound-manipulation*" -and $line -notlike "*display"){
                if($hit){
                    $global:output += $pscustomobject 
                }
                $pscustomobject = New-Object -TypeName psobject
                $hit = $true
                Continue
            }
            if($hit){
                $line = $line.split(" ")
                $i = $line.count - 1 
                $pscustomobject  | add-member -MemberType NoteProperty -value $line[$i].trim() -name $line[$i - 1 ].trim()
            }


        }
        $global:output += $pscustomobject  
        $global:output | ft manipulation-name,src-user-name-prefix,dst-user-name-prefix,manipulated-uri,prefix-to-add
        $global:output | ogv
#___________________________________


    }Else{  
        Write-Verbose $global:result 
        Write-Error "Unable to connect to host: $($RemoteHost):$Port"
    }
}

Function Backup-Ini{   
    Param (
        [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
        [Parameter(ValueFromPipeline=$true)]
        [string]$RemoteHost,
        [string]$BackupPath,
        [string]$Port = "23",
        [int]$WaitTime = 1000,
        [string]$Pwd
    )
 
    write-verbose "Attach to the remote device, setup streaming requirements"
    $Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
    If ($Socket)
    {   $Stream = $Socket.GetStream()
        $Writer = New-Object System.IO.StreamWriter($Stream)
        $Buffer = New-Object System.Byte[] 1024
        $Encoding = New-Object System.Text.ASCIIEncoding
 
        write-verbose "Now start issuing the commands: username"
        $Result = ""
        $Writer.WriteLine("Admin") 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $Result += ($Encoding.GetString($Buffer, 0, $Read))
        }
 
        write-verbose "enter password"
        $Writer.WriteLine($Pwd) 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $Result += ($Encoding.GetString($Buffer, 0, $Read))
        }
        
        write-verbose "Retrieving INI file"
        $Writer.WriteLine("show ini-file") 
        $Writer.Flush()
        Start-Sleep -Milliseconds $WaitTime
        While($Stream.DataAvailable) 
        {   $Read = $Stream.Read($Buffer, 0, 1024) 
            $ini = -join ($Encoding.GetString($Buffer, 0, $Read))
            while($ini.substring($ini.length - 8) -notlike "*>*"){
                $Writer.WriteLine(" ") 
                $Writer.Flush()
                Start-Sleep -Milliseconds $WaitTime
                While($Stream.DataAvailable) 
                {   $Read = $Stream.Read($Buffer, 0, 1024) 
                    $ini += -join ($Encoding.GetString($Buffer, 0, $Read))
                }
            }
        }
 
    }Else{  
        write-error "Unable to connect to host: $($RemoteHost):$Port"
    }
 
    $Writer.WriteLine("quit") 
    $Writer.Flush()
 
    #modify this part with the text which appears in the ini file, if it fails to remove it.
    write-verbose "replacing --MORE-- xxxxx from strings"
    $ini = $ini -replace ("\s{2}(--MORE--)([^\w]{27})")
    $ini = $ini -replace ("\s(--MORE--)([^\w]{27})")
    $ini = $ini.replace(" --MORE-- ","")
    $ini = $ini.replace("show ini-file","")
 
    $filename = (Get-Date -format "ddMMyyyy") + '_' + $RemoteHost + '.ini'
    if(!(test-path "$BackupPath\$remotehost")){
        new-item -path $BackupPath -Name $remotehost -ItemType directory
    }
    if(test-path "$BackupPath\$remotehost\$filename"){
        $filename = $filename.Substring(0,$filename.Length -4) + "_.ini"
    }
    $ini | Out-File -FilePath "$BackupPath\$remotehost\$filename" -Force -Encoding ascii
 
    write-verbose "Creating delta file in case of differences with last file"
    $files = Get-ChildItem -path $BackupPath\$remotehost -filter "*.ini"| Sort-Object LastWriteTime | select -last 2
    if($files.count -eq 2){
        $oldIni = Get-Content $files[1].FullName | %{$i = 1} { new-object psobject -prop @{Line=$i;Text=$_}; $i++}
        $newIni = Get-Content $files[0].FullName | %{$i = 1} { new-object psobject -prop @{Line=$i;Text=$_}; $i++}
        $difference = Compare-Object $oldIni $newIni -Property Text -PassThru |  Sort-Object -Property Line | fl
        if($difference.count -ne 0){
            write-verbose "Creating delta file as there are differences with last file"
            $difference | Out-File -FilePath "$BackupPath\$remotehost\$filename.changes.txt" -Force -Encoding ascii
        }else{
            write-verbose "removing the new ini file as it is the same as previous"
            Remove-Item "$BackupPath\$remotehost\$filename" -Force
        }
    }
}

Function Backup-AudioCodes{
    <#
    .SYNOPSIS
       Creates Backup of Audiocodes mediant
    .DESCRIPTION
       This cmdlet will export the ini file via a telnet session to the AudioCodes Mediant
    .PARAMETER Hosts
        IP Address or array of IP addresses from which to backup the INI files
    .PARAMETER BackupDir
        Directory where to write backup files as well as encrypted password strings for accessing the audiocodes.
    .PARAMETER Port
        Optionally define the telnet port to connect on. When omitted the default port 23 is used.
    .EXAMPLE
       Backup-Audiocodes -hosts "10.0.0.10","10.0.0.12" -BackupDir D:\Backups\AudioCodes
    .INPUTS
       Parameters
    .OUTPUTS
       Key Directory
        Keys
       Host Directory
        Ini Files
        Delta Files
    .NOTES
    +---------------------------------------------------------------------------------------------+
    | ORIGIN STORY |
    +---------------------------------------------------------------------------------------------+
    | DATE : 2018.07.02
    | AUTHOR : Michael L�rsen
    | E-Mail : michael.luersen@mondaycoffee.com
    +---------------------------------------------------------------------------------------------+
    #>

    Param(
    [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
    [Parameter(Mandatory=$true)]
    $hosts,
    [Parameter(Mandatory=$true)]
    [string]$BackupDir,
    [string]$Port = 23
    )

    $BackupDir = $BackupDir.trim()

    if($BackupDir.Substring($BackupDir.Length -1) -ne "\"){
        $BackupDir = $BackupDir + '\'
    }
    if(!(test-path "$backupdir\Keys")){
        New-Item "$backupdir\Keys" -ItemType Directory | Out-Null
    }
    

    foreach($item in $hosts){
        $passwordfile = "$BackupDir\Keys\$($item)key.txt" 
        if(!(test-path $passwordfile)){
            [byte[]] $key = (1..16)   
            $credential = Get-Credential Admin -Message "Enter Credentials for $item"
            $credential.Password | ConvertFrom-SecureString -key $key | Set-Content $passwordfile
        }
 
        [byte[]] $key = (1..16)
        $securePassword = Get-Content $passwordfile | ConvertTo-SecureString -Key $key
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securepassword)
        $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
 
        Write-Output "### Backup up $item ###"
        if($verbose -eq $true){
            
            Backup-Ini -RemoteHost $item -BackupPath $BackupDir -Pwd $UnsecurePassword -port $port -Verbose
        }else{
            Backup-Ini -RemoteHost $item -BackupPath $BackupDir -Pwd $UnsecurePassword -port $port
        }
    }
 
    write-output "done" 
}


Function Export-AssignedNumbers{
    <#
    .SYNOPSIS
       Exports Assigned Numbers
    .DESCRIPTION
       This cmdlet will export the assigned numbers in SfB and on the Audiocodes (Fax and Gateway numbers)
       Numbers Queried: LineURI, Private Line, Analouge Lines, Common Area Phones, RGS Workflows,
       Exchange UM Contacts, Trusted Applications and Conferencing Numbers
       LineURI's are run against a regex pattern to extract the DDI/DID and the extension to a separate column.
    .PARAMETER AcBackupDir
        Path where the Backup-Audiocodes commandlet has exported the INI Files.
    .PARAMETER OutputDir
        Path where the Backup-Audiocodes commandlet will place the SfBAssignedNUmbers files as well
        as the AvailableNumbers file.
        If omitted then the output will be in AcBackupDir
    .EXAMPLE
       Export-AssignedNumbers -AcBackupDir D:\Backups\AudioCodes -OutputDir D:\AssignedNumbers
    .INPUTS
       Parameters
    .OUTPUTS
       SfbAssignedNumbers file
       AvailabeNumbers file
    .NOTES
    +---------------------------------------------------------------------------------------------+
    | ORIGIN STORY |
    +---------------------------------------------------------------------------------------------+
    | DATE : 2018.07.24
    | AUTHOR : Michael L�rsen
    | E-Mail : michael.luersen@mondaycoffee.com
    +---------------------------------------------------------------------------------------------+
    #>



    Param(
    [CmdletBinding(
                  SupportsShouldProcess=$false, 
                  PositionalBinding=$false,
                  ConfirmImpact='Low')]
    [Parameter(Mandatory=$true)]
    $AcBackupDir,
    $OutputDir
    )

    if($null -eq $OutputDir){
        $OutputDir = $AcBackupDir
        }

    if(!(Test-Path "$OutputDir\History")){
        New-Item -Path "$OutputDir\History" -ItemType Directory
    }

    $FileName = "SfbAssignedNumbers_" + (Get-Date -Format s).replace(":","-") +".csv"
    $FilePath = "$OutputDir\History\$FileName"
    $filePath2 = "$OutputDir\SfbAssignedNumbers.csv"


    #audiocodes ini directory - will take the most recent file in here
    $backupPaths = (Get-ChildItem -Directory -Path $AcBackupDir | Where-Object{$_.name -match "\d{1,3}(\.\d{1,3}){3}"}).fullname

    Import-Module Lync

    $Regex1 = '^(?:tel:)?(?:\+)?(\d+)(?:;ext=(\d+))?(?:;([\w-]+))?$'

    $Array1 = @()

    #Get Fax numbers
    #get most recent ini file
    $ipRouting = $null
    foreach($backupPath in $backupPaths){
        $backupFile = get-content (Get-ChildItem -path $backupPath -filter "*.ini" | Sort-Object -Property LastWriteTime -Descending  | Select-Object -First 1).fullname
        $match = $false

        foreach($line in $backupFile){
            if($line -eq "[ \IP2IPRouting ]"){
                #stop capturing strings after this
                $match = $false
            }
            if($match -eq $true){
                #capture strings
                $ipRouting += $line 
                $ipRouting += "`n"
            }

            if($line -eq "[ IP2IPRouting ]"){
                #start capturing strings
                $match = $true
            }

        }
    }


    $ipRouting = $ipRouting.trim()
    $ipRouting = $ipRouting.tostring()
    $ipRouting = $ipRouting.Replace(" = ",", ")
    $ipRouting = ConvertFrom-Csv $ipRouting -Delimiter "," 
    #select only fax numbers (not going to SfB and no empty lines)
    #include a destination type of 8 so that numbers sent to the gateway (astra dect) are included too, albeit as fax numbers.
    $faxNumbers = $ipRouting | Where-Object{$_.IP2IPRouting_DestSipInterfaceName -eq 'SIP_LAN' -and ($_.IP2IPRouting_DestIPGroupName -notin 'SfB','' -or $_.IP2IPRouting_DestType -eq 8)}
        foreach($item in $faxNumbers){
        
            if($item.IP2IPRouting_DestUsernamePrefix.IndexOf("[") -ne -1){ #add lines for ranges and series
                $number = $item.IP2IPRouting_DestUsernamePrefix.split("[")[0]
                $number = $number.replace("+","")
                if( ($item.IP2IPRouting_DestUsernamePrefix.split("[")[1]).trim("]").indexof("-") -ne -1){#found a - in the [] // create a range
                    $range = ($item.IP2IPRouting_DestUsernamePrefix.split("[")[1]).trim("]").split("-")
                    $range[0]..$range[1] | foreach-object{
                        $myObject1 = New-Object System.Object
                        $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $item.IP2IPRouting_DestUsernamePrefix
                        if($_ -lt 10){
                            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value "$($number)0$($_)"
                        }else{
                            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $number$_
                        }
                    
                        $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value ""
                        $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.IP2IPRouting_RouteName
                        $myObject1 | Add-Member -type NoteProperty -name "FirstName" -Value ""
                        $myObject1 | Add-Member -type NoteProperty -name "LastName" -Value ""
                        $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "FaxSeries"
                        $Array1 += $myObject1 
                    }
                }  
                if( ($item.IP2IPRouting_DestUsernamePrefix.split("[")[1]).trim("]").indexof(",") -ne -1){#found a , in the [] // create a series
                    $range = ($item.IP2IPRouting_DestUsernamePrefix.split("[")[1]).trim("]").split(",")
                    foreach($thing in $range){
                        $myObject1 = New-Object System.Object
                        $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $item.IP2IPRouting_DestUsernamePrefix
                        $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $number$thing
                        $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value ""
                        $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.IP2IPRouting_RouteName
                        $myObject1 | Add-Member -type NoteProperty -name "FirstName" -Value ""
                        $myObject1 | Add-Member -type NoteProperty -name "LastName" -Value ""
                        $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "Faxrange"
                        $Array1 += $myObject1 
                    }
                }
            }  
            Else{# just add the number as is
                $myObject1 = New-Object System.Object
                $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $item.IP2IPRouting_DestUsernamePrefix
                $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $item.IP2IPRouting_DestUsernamePrefix.replace("+","")
                $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value ""
                $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.IP2IPRouting_RouteName
                $myObject1 | Add-Member -type NoteProperty -name "FirstName" -Value ""
                $myObject1 | Add-Member -type NoteProperty -name "LastName" -Value ""
                $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "Fax"
                $Array1 += $myObject1 
            }         
        }


    #Get Users with LineURI
    $UsersLineURI = Get-CsUser -Filter {LineURI -ne $Null}
    if($UsersLineURI -ne $null){
        foreach($item in $UsersLineURI)    {                  
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "FirstName" -Value $Item.FirstName
            $myObject1 | Add-Member -type NoteProperty -name "LastName" -Value $Item.LastName
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "User"
            $Array1 += $myObject1          
        }
    }

    #Get Users with Private Line
    $UsersPrivateLine = Get-CsUser -Filter {PrivateLine -ne $Null} 
    if($UsersPrivateLine -ne $null){
        foreach($item in $UsersPrivateLine)    {                   
            $Matches = @()
            $Item.PrivateLine -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.PrivateLine
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "FirstName" -Value $Item.FirstName
            $myObject1 | Add-Member -type NoteProperty -name "LastName" -Value $Item.LastName
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "UserPrivateLine"
            $Array1 += $myObject1          
        }
    }

    #Get analouge lines
    $AnalougeLineURI = Get-CsAnalogDevice -Filter {LineURI -ne $Null}  
    if($AnalougeLineURI -ne $null){
        foreach($item in $AnalougeLineURI)    {                  
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "AnalougeLine"
            $Array1 += $myObject1          
        }
    }

    #Get common area phones
    $CommonAreaLineURI = Get-CsCommonAreaPhone -Filter {LineURI -ne $Null} 
    if($CommonAreaLineURI -ne $null){
        foreach($item in $CommonAreaLineURI)    {                    
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "CommonArea"
            $Array1 += $myObject1          
        }
    }

    #Get RGS workflows
    $WorkflowLineURI = Get-CsRgsWorkflow
    if($WorkflowLineURI -ne $null){
        foreach($item in $WorkflowLineURI)    {                 
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "RGSWorkflow"
            $Array1 += $myObject1          
        }
    }

    #Get Exchange UM Contacts
    $ExUmContactLineURI = Get-CsExUmContact -Filter {LineURI -ne $Null}
    if($ExUmContactLineURI -ne $null){
        foreach($item in $ExUmContactLineURI)    {                   
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "ExUmContact"
            $Array1 += $myObject1          
        }
    }

    #Get trusted applications
    $TrustedApplicationLineURI = Get-CsTrustedApplicationEndpoint -Filter {LineURI -ne $Null}
    if($TrustedApplicationLineURI -ne $null){
        foreach($item in $TrustedApplicationLineURI){                   
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null
            
            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.Name
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "TrustedApplication"
            $Array1 += $myObject1          
        }
    }

    #Get conferencing numbers
    $DialInConfLineURI = Get-CsDialInConferencingAccessNumber -Filter {LineURI -ne $Null}
    if($DialInConfLineURI -ne $null){
        foreach($Item in $DialInConfLineURI){                 
            $Matches = @()
            $Item.LineURI -match $Regex1 | out-null

            $myObject1 = New-Object System.Object
            $myObject1 | Add-Member -type NoteProperty -name "LineURI" -Value $Item.LineURI
            $myObject1 | Add-Member -type NoteProperty -name "DDI" -Value $Matches[1]
            $myObject1 | Add-Member -type NoteProperty -name "Ext" -Value $Matches[2]
            $myObject1 | Add-Member -type NoteProperty -name "Name" -Value $Item.DisplayName
            $myObject1 | Add-Member -type NoteProperty -name "Type" -Value "DialInConf"
            $Array1 += $myObject1          
        }
    }

    $Array1 | export-csv $FilePath -NoTypeInformation -Encoding unicode
    $Array1 | export-csv $FilePath2 -NoTypeInformation -Encoding unicode -Force
    Write-Host "ALL DONE!! Your file has been saved to $FilePath"
    Write-Host "Creating available numbers list"

    Remove-Item -path "$OutputDir\Available-numbers.txt" -Force


    $ranges = Get-CsUnassignedNumber 

    $latest = Get-ChildItem -Path $OutputDir -filter "*.csv" | Sort-Object LastAccessTime -Descending | Select-Object -First 1
    $assignedNumbers = Import-Csv -LiteralPath $latest.VersionInfo.FileName

    $result = "Range,RangeStart,RangeEnd,AvailableNumber"

    foreach($range in $ranges){
        $start = [long]$range.NumberRangeStart.Substring(5,11)
        $end = [long]$range.NumberRangeEnd.Substring(5,11)
        #reduce to a +41 number in case the range has been put in as +99
        if($start -gt 98999999999){$start = $start - 58000000000}
        if($end -gt 98999999999){$end = $end - 58000000000}
        $available = @()
        $x = $start
        while($x -le $end){
            if($x -notin $assignedNumbers.DDI){
                $available += $x 
            }
            $x = $x + 1
        }
    
        foreach($item in $available){
            $result += [environment]::NewLine
            $result += "$($range.Identity),$start,$end,$item"
        }
    
    }

    $result | Out-File -FilePath "$OutputDir\Available-Numbers.csv" -Encoding unicode
}