When I saw the HTTP GET in Wireshark for the file, I initially thought I could download just the BOARD.ini file, but then I found that GWs with 6.4 firmware or later have the Serial Number as part of the BOARD file name (BOARD_SN1234567.ini).
So back to the drawing board... I then had to step back and look at the page that was downloaded to give the interface to perform backups and pull out the filename used in the URL to request the file.
Anyway... I'm quite proud of hacking this together. Enjoy.
If you are running on a Windows 7 or other workstation you'll need to set your PowerShell Execution Policy.
Version 6.4 and older
# Script to Backup AudioCodes Gateways
# Created By Jonathan McKinney (blog.lyncdialog.com)
# Disclaimer: You running this script means you won't blame Jonathan McKinney or his employer if this breaks your stuff. This script is provided AS IS without warranty of any kind.
$username = "Admin"
$password = "Admin"
$regex = "BOARD.*\.ini"
$address = "10.10.1.10"
$path = "c:\scripts\"
$webclient = new-object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($username, $password)
Write-Host `r`n 'Detecting .ini name' `r`n
$configurl = "http://" + $address + "/ConfigurationFile"
$webpage = $webclient.DownloadString($configurl)
$pattern = $webpage -split "`n" | Select-String -pattern $regex -Allmatches | % { $_.Matches | % { $_.Value } }
Write-Host `r`n 'Detected Board Filename ' $pattern
Write-Host `r`n 'Backing up ' $address `r`n
$backupurl = "http://" + $address + "/FS/" + $pattern
$backupfilename = $path + $pattern
$webclient.DownloadFile($backupurl,$backupfilename)
Write-Host `r
Version 6.6 and newer
So I have to admit that this was a huge pain to figure out. So I am even more proud I got this working than the above script. Having that been said... I consider this beta until more people test this and have success. Please give me feedback if you run into problems.
Audiocodes implemented Forms Based Authentication starting on version 6.6. Since I didn't understand much about this, I had to do a lot of research and found there are 1000 different variations on this, which made it hard to find examples of what I needed to do. So once again I went back to my trusty friend Wireshark and picked apart what was being sent in the HTTP POST versus what Audiocodes was doing in javascript.
There are some pre-requisites in order for this script to run successfully.To start with you will need to set the HTTP Authentication Mode on the Audiocodes to "Web Based Authentication", as illustrated below (note you will need to switch to "Advanced" on the left). This is necessary because there is a bunch of HASH algorithms that Audiocodes has running in javascript otherwise, that I can't deal with from a command line script (Note: this is a change from the original script I wrote that didn't handle the more complicated Hash algorithm used with Web Based Authentication).
Next you will need to install a package called cURL that is a command line tool for interacting with web pages. It is required to work with the Forms Based Auth that audiocodes uses. I used a new tool to install it called Chocolatey, which is the equivalent of RPM packages for Windows. Here is the page for cURL (http://chocolatey.org/packages/curl) and here is the main page for Chocolatey (http://chocolatey.org/)
This script now supports MediaPacks and Mediants on version 6.6 and newer.
# Script to Backup AudioCodes Gateways
# Created By Jonathan McKinney (blog.lyncdialog.com)
# Disclaimer: You running this script means you won't blame Jonathan McKinney or his employer if this breaks your stuff. This script is provided AS IS without warranty of any kind.
$username = "Admin"
$password = "Admin"
$login1sregex = "\<s\>(\d*)\<\/s\>"
$login1rregex = "\<r\>(.*)\<\/r\>"
$filenameregex = "BOARD.*\.ini"
$address = "10.10.1.10"
$path = "c:\scripts\"
$a1 = $null
$loginusername = $null
$loginurl = $null
$configurl = $null
$backupurl = $null
$logoffurl = $null
$logoffwebpage = $null
$login1webpage = $null
$login1spattern = $null
$login1rpattern = $null
$hashByteArray1 = $null
$hashByteArray2 = $null
$passwordhash = $null
$resultwebpage = $null
$configwebpage = $null
$filenamepattern = $null
$backupfilename = $null
# Authentication Section
Write-Host `r`n 'Authenticating' `r`n
$login1username = "c1=" + $username
$loginurl = "http://" + $address + "/UE/Login"
$login1webpage = curl $loginurl --data-urlencode "t=1" `
--data-urlencode "c0=0" `
--data-urlencode $login1username `
--header "X-Requested-With: XMLHttpRequest" `
-c cookiejar.txt `
--header "Cookie: aclogname=; C2=ct" `
--user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
$login1spattern = $login1webpage -split "`n" | Select-String -pattern $login1sregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }
$login1spattern = "s=" + $login1spattern
$login1rpattern = $login1webpage -split "`n" | Select-String -pattern $login1rregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }
$cryptoServiceProvider = [System.Security.Cryptography.SHA256CryptoServiceProvider]
$hashAlgorithm = New-Object $cryptoServiceProvider
$hashByteArray1 = $hashAlgorithm.ComputeHash($([Char[]]$password))
foreach ($byte in $hashByteArray1)
{ $a1 += "{0:x2}" -f $byte
}$a2 = $username + ":" + $login1rpattern + ":" + $a1
$hashByteArray2 = $hashAlgorithm.ComputeHash($([Char[]]$a2))
foreach ($byte in $hashByteArray2)
{ $passwordhash += "{0:x2}" -f $byte
}$passwordhash = "c1=" + $passwordhash
$resultwebpage = curl $loginurl --data-urlencode "t=1" `
--data-urlencode $login1spattern `
--data-urlencode "c0=1" `
--data-urlencode $passwordhash `
--header "X-Requested-With: XMLHttpRequest" `
-c cookiejar.txt `
--header "Cookie: aclogname=; C2=ct" `
--user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
# Detect filename for backup section
Write-Host `r`n 'Detecting .ini name' `r`n
$configurl = "http://" + $address + "/ConfigurationFile"
$configwebpage = curl $configurl -b cookiejar.txt
$filenamepattern = $configwebpage -split "`n" | Select-String -pattern $filenameregex -Allmatches | % { $_.Matches | % { $_.Value } }
Write-Host `r`n 'Detected Board Filename ' $filenamepattern
# Backup section
Write-Host `r`n 'Backing up ' $address `r`n
$backupurl = "http://" + $address + "/FS/" + $filenamepattern
$backupfilename = $path + $filenamepattern
curl -o $backupfilename $backupurl -b cookiejar.txt
Write-Host `r
# Logoff section
$logoffurl = "http://" + $address + "/PressLogOff"
$logoffwebpage = curl $logoffurl -b cookiejar.txt
Authentication type Auto Detect
And now... the pièce de résistance... I combined both scripts... decided to make it more Function based and now it Auto Detects the authentication type. It has the same prerequisites as the Scripts above. So please read all of the article before you try to use this script. Also if you are wondering why I didn't use cURL for all of the work in the new script... it kept truncating the authentication header when doing the basic (Digest) authentication and failing. Couldn't find a parameter to increase the header size allowed.
Enjoy... as usual... feedback is always welcome.
# Script to Backup AudioCodes Gateways# Created By Jonathan McKinney (blog.lyncdialog.com)# Disclaimer: You running this script means you won't blame Jonathan McKinney or his employer if this breaks your stuff. This script is provided AS IS without warranty of any kind.
# User Modifiable Variables
$username = "Admin"
$password = "Admin"
$address = "m2600.t2mdev.com"
$backuppath = "c:\jonmck\script\"
# End User Modifiable Variables
$filename = $null
$statuscodeeval = $null
#Function to detect if Forms Auth is present
function DetectAuthType ($ad)
{
$loginurl = $null
$loginurl = "http://" + $ad + "/"
$statuscode = curl -o null.txt $loginurl -w '%{http_code}'
If ($statuscode -eq "401")
{
$statuscodeeval = "basic"
}
ElseIf ($statuscode -eq "203")
{
$statuscodeeval = "forms"
}
Else
{
$statuscodeeval = "neither"
}
Remove-Item null.txt
Return $statuscodeeval
}
# Function to Authenticate with Forms Auth
function AuthenticateForms ($un, $pw, $ad)
{
$login1sregex = "\<s\>(\d*)\<\/s\>"
$login1rregex = "\<r\>(.*)\<\/r\>"
$a1 = $null
$loginusername = $null
$loginurl = $null
$login1webpage = $null
$login1spattern = $null
$login1rpattern = $null
$hashByteArray1 = $null
$hashByteArray2 = $null
$passwordhash = $null
$resultwebpage = $null
$formsbasedauth = $null
Write-Host `r`n 'Forms Authentication' `r`n
$login1username = "c1=" + $un
$loginurl = "http://" + $ad + "/UE/Login"
$login1webpage = curl $loginurl --data-urlencode "t=1" `
--data-urlencode "c0=0" `
--data-urlencode $login1username `
--header "X-Requested-With: XMLHttpRequest" `
-c cookiejar.txt `
--header "Cookie: aclogname=; C2=ct" `
--user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
$login1spattern = $login1webpage -split "`n" | Select-String -pattern $login1sregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }
$login1spattern = "s=" + $login1spattern
$login1rpattern = $login1webpage -split "`n" | Select-String -pattern $login1rregex -Allmatches | % { $_.Matches | % { $_.groups[1].Value } }
$cryptoServiceProvider = [System.Security.Cryptography.SHA256CryptoServiceProvider]
$hashAlgorithm = New-Object $cryptoServiceProvider
$hashByteArray1 = $hashAlgorithm.ComputeHash($([Char[]]$password))
foreach ($byte in $hashByteArray1)
{
$a1 += "{0:x2}" -f $byte
}
$a2 = $username + ":" + $login1rpattern + ":" + $a1
$hashByteArray2 = $hashAlgorithm.ComputeHash($([Char[]]$a2))
foreach ($byte in $hashByteArray2)
{
$passwordhash += "{0:x2}" -f $byte
}
$passwordhash = "c1=" + $passwordhash
$resultwebpage = curl $loginurl --data-urlencode "t=1" `
--data-urlencode $login1spattern `
--data-urlencode "c0=1" `
--data-urlencode $passwordhash `
--header "X-Requested-With: XMLHttpRequest" `
-c cookiejar.txt `
--header "Cookie: aclogname=; C2=ct" `
--user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"
}
# Function to Backup Gateway WITHOUT Forms Auth
function BackupBasic ($un, $pw, $ad, $path)
{
$regex = "BOARD.*\.ini"
$webclient = $null
$configurl = $null
$backupurl = $null
$webpage = $null
$pattern = $null
$backupfilename = $null
$webclient = new-object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($un, $pw)
Write-Host `r`n 'Detecting .ini name' `r`n
$configurl = "http://" + $ad + "/ConfigurationFile"
$webpage = $webclient.DownloadString($configurl)
$pattern = $webpage -split "`n" | Select-String -pattern $regex -Allmatches | % { $_.Matches | % { $_.Value } }
Write-Host `r`n 'Detected Board Filename ' $pattern
Write-Host `r`n 'Backing up ' $ad `r`n
$backupurl = "http://" + $ad + "/FS/" + $pattern
$backupfilename = $path + $pattern
$webclient.DownloadFile($backupurl,$backupfilename)
Write-Host `r
}
# Function to Detect the Backup Filename with Forms Auth for the Audiocodes Configuration File
function DetectFilenameForms ($ad)
{
$fnregex = "BOARD.*\.ini"
$configurl = $null
$fnpat = $null
$configwebpage = $null
Write-Host `r`n 'Detecting .ini name' `r`n
$configurl = "http://" + $ad + "/ConfigurationFile"
$configwebpage = curl $configurl -b cookiejar.txt
$fnpat = $configwebpage -split "`n" | Select-String -pattern $fnregex -Allmatches | % { $_.Matches | % { $_.Value } }
Write-Host `r`n 'Detected Board Filename ' $fnpat
return $fnpat
}
# Function to Backup Gateway with Forms Auth
function BackupGatewayForms ($ad, $path, $fn)
{
$backupurl = $null
$backupfilename = $null
Write-Host `r`n 'Backing up ' $ad `r`n
$backupurl = "http://" + $ad + "/FS/" + $fn
$backupfilename = $path + $fn
curl -o $backupfilename $backupurl -b cookiejar.txt
Write-Host `r
}
# Function to Logoff Audiocodes with Forms Auth after finishing work
function LogoffForms ($ad)
{
$logoffurl = $null
$logoffwebpage = $null
$logoffurl = "http://" + $ad + "/PressLogOff"
$logoffwebpage = curl $logoffurl -b cookiejar.txt
Remove-Item cookiejar.txt
}
# Main Script
$formsbasedauth = DetectAuthType $address
if ($formsbasedauth -eq "forms")
{
AuthenticateForms $username $password $address
$filename = DetectFilenameForms $address
BackupGatewayForms $address $backuppath $filename
LogoffForms $address
}
ElseIf ($formsbasedauth -eq "basic")
{
BackupBasic $username $password $address $backuppath
}
Else
{
Write-Host "Something went really wrong... couldn't detect Authentication type... Might I suggest Wireshark"
}
Awesome job! Works flawlessly!
ReplyDeleteNice dude, thanks for posting! Hope you're doing well.
ReplyDeleteI am doing well... and staying busy like everyone else is with Lync :-)
DeleteIt seems to work on 6.4, but not on 6.6 (File is blank).
ReplyDeleteI'll take a look... thanks for the feedback
DeleteI bet it has to do with the forms based auth on 6.6....hmmm
DeleteAnyone had any luck with the 6.6 firmware form based login? Seems quite a painful process and I'd hate to reinvent the wheel... Thanks
ReplyDeleteI had a buddy working on a technique using PowerShell to drive IE... but it prompts for file download instead of just downloading... haven't found a way around that. I haven't had a chance to dig back in to this yet.
DeleteOne thing I haven't checked... is if there is a way to turn off the forms based authentication... if you can get it back to the old way of authenticating... the other script should work
Deleteyou can use SSH or telnet to
ReplyDeleteYea this might be the way we have to go... but would prefer to do it through web interface.
DeleteHi johnathan, I need to copy the configuration of one Audio Gateway 1000 to another...
ReplyDeleteDo you know how I can do that.
Just do a backup of the configuration. In the .ini file you can modify the interfaces section to match the IP address of the target gateway. You can also do a search and replace for any routes that have the old IP in case you were doing tel to ip and then ip to tel routing for analogs as an example
DeleteHi johnathan,
DeleteI tried to run the third script after installing curl, but power shell always responds with this error: Invoke-WebRequest:
Unable to process the parameter. The parameter name 'or' is ambiguous. Possible matches include:-OutFile-OutVariable OutBuffer.
In C: \ Scripts \ Backup Audiocodes.ps1: 21 car: 25
+ $ Statuscode = curl-o $ null.txt loginUrl-w '% {} HTTP_CODE'
+ ~ ~
+ CategoryInfo: InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
+ FullyQualifiedErrorId: AmbiguousParameter, Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Remove-Item: Can not find path 'C: \ null.txt' because it does not exist.
In C: \ Scripts \ Backup Audiocodes.ps1: 36 car: 6
+ Remove-Item null.txt
+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
+ CategoryInfo: ObjectNotFound: (C: \ null.txt: String) [Remove-Item], ItemNotFoundException
+ FullyQualifiedErrorId: PathNotFound, Microsoft.PowerShell.Commands.RemoveItemCommand
Do you know how I can fix this?
Thank you.
You might not have the right version of powershell installed. Can you verify by running on a Lync Server,,, people have had better luck there. In general Powershell 3 or higher should work
DeleteHi, thank you for this script I added to your hard work as I found certain versions of mediants require a different hashing sequence during FBA. HTTP://www.mklync.com Thanks guys hope it helps out
DeleteI ran into this myself Luciano... it has to do with a curl alias in PowerShell. https://github.com/lukesampson/scoop/issues/56
DeleteGetting the configuration from a 6.6 gateway involves two HTTP POSTs to log in, as well as cookie information and REFER information in each subsequent request. It's complicated. I've done it using Debian, Bash and curl and it's working perfectly so far. I could send it to you if you like.
ReplyDeleteI believe that the m version is more related to other security features being enabled rather than the defaults. I have vague memory of running into it when trying to disable forms based auth.
ReplyDeleteI'd have to poke around to find it again... but the script I have above works for all the audiocodes up to 6.8 out of the box. But I think having the modified version you did is important too. Thanks for figuring that out!
Im using load 7.00A.004.503 with the latest version of cURL and get the below. Any ideas?
ReplyDeletePS C:\Scripts> .\ACBackup.ps1
Authenticating
Invoke-WebRequest : Parameter cannot be processed because the parameter name 'c' is ambiguous. Possible matches
include: -Credential -CertificateThumbprint -Certificate -ContentType.
At C:\Scripts\ACBackup.ps1:41 char:1
+ -c cookiejar.txt `
+ ~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Invoke-WebRequest : Parameter cannot be processed because the parameter name 'c' is ambiguous. Possible matches
include: -Credential -CertificateThumbprint -Certificate -ContentType.
At C:\Scripts\ACBackup.ps1:68 char:1
+ -c cookiejar.txt `
+ ~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Detecting .ini name
curl : Cannot send a content-body with this verb-type.
At C:\Scripts\ACBackup.ps1:76 char:18
+ $configwebpage = curl $configurl -b cookiejar.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Invoke-WebRequest], ProtocolViolationException
+ FullyQualifiedErrorId : System.Net.ProtocolViolationException,Microsoft.PowerShell.Commands.InvokeWebRequestComm
and
Detected Board Filename
Backing up 1.1.1.1
Invoke-WebRequest : Parameter cannot be processed because the parameter name 'o' is ambiguous. Possible matches
include: -OutFile -OutVariable -OutBuffer.
At C:\Scripts\ACBackup.ps1:86 char:6
+ curl -o $backupfilename $backupurl -b cookiejar.txt
+ ~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
curl : Cannot send a content-body with this verb-type.
At C:\Scripts\ACBackup.ps1:91 char:18
+ $logoffwebpage = curl $logoffurl -b cookiejar.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Invoke-WebRequest], ProtocolViolationException
+ FullyQualifiedErrorId : System.Net.ProtocolViolationException,Microsoft.PowerShell.Commands.InvokeWebRequestComm
and
PS C:\Scripts>
It looks like the cURL alias is pointed to Invoke-WebRequest rather than cURL. I've had this problem on some machines... you can kill the alias using this method http://superuser.com/questions/883914/how-do-i-permanently-remove-a-default-powershell-alias
ReplyDeleteHi Jonathan, Nice Work! Thank you!
ReplyDeleteWhy don't you use Invoke-WebRequest instead of curl? curl is a powershell Alias of Invoke-WebRequest because this is the implementation of curl. So you don't need to install curl before using the script.
Oh I definitely tried... But it could t handle the more complicated auth of the newer firmware. The biggest feature I needed was the ability to store a cookie once authorized so that subsequent requests didn't go through the auth process again. If you look at the script the branch for older firmware does use invoke-webrequest.
DeleteHi Jonathan,
ReplyDeleteI get the following warning after running the script.
Backing up 172.30.40.4
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
Warning: Failed to create the file d:\CURL\: No such file or directory
curl: (23) Failed writing received data to disk/application
Thanks a lot for your help.
haven't seen that before... make sure you are running the PoSH console as "Administrator" and that the user you are logged into Windows with has the permissions to create that directory. You might also try to uninstall CURL and reinstall again and make sure there are no errors.
DeleteHi Jonathan,
ReplyDeleteI test the script but only works whith "basic authentication", in Forms Based authentication i get the following "xml version="1.0" encoding="UTF-8" fail no access level"
GW:
Mediant 1000; Version ID:6.80A.248.003
Thanks for your help.
Did you change the HTTPS cipher string? You might also take a look at this modified script http://www.mklync.com/?p=296
DeleteHi Dear Sir,
ReplyDeleteThanks for your amazing script, I don't have any idea how audiocodes devices work, but need to have a script which can backup mediant 500 to mediant 2000 devices, do you have kind of script can work on these vesrions?
thanks for your help.
Mediant500-600-800-1000 and 2000
have you tried this script... I suspect it will work for all that hardware even though I haven't tested
DeleteHi Jonathan,
ReplyDeleteTried running your script against a Mediant 2600 hundred with the latest Firmware (7.20A.000.042) installed.
Couldn't get it to work and on investigation the $login1webpage variable is getting populated with: "user not found"
Have also tried the script from mklync.com with no success.
The user I'm authenticating with definitely exists. Any suggestions?
Thanks