Wednesday, July 5, 2017

PowerShell Script to Backup AudioCodes Gateways (updated for 7.0 and 7.2)

A few years ago I created a PowerShell script that would backup AudioCodes gateways. It's gone through several iterations as AudioCodes has changed how they do authentication. Now with 7.0 and 7.2 out in the wild, I again found that I needed to update this script again to support the changes to the user interface.

Fortunately I've got great friends and Mitch Steiner had been messing with AudioCodes new API to pull some interesting metrics for our T2M Cloud and he let me know that he stumbled upon a way to pull the .ini configuration using the REST API. The new REST API reduced the amount of code to do a backup down to literally one line. Now obviously it would be cool to have a script that detected which AudioCodes version it was connected to, so it is more than just one line in the script below. I also found a way to "Burn" the configuration on 7.0 and 7.2 using the API. So that is part of the script and executes just before backup.

Couple of key items before you run the script below.
  • If you are running on a Windows 7 or other workstation you'll need to set your PowerShell Execution Policy.
  • 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/)
  • PowerShell has an alias for curl that you'll need to remove. The command is Remove-Item alias:curl 
Without further adieu... I give you the AudioCodes backup script that will backup any 6.4, 6.6, 6.8, 7.0, or 7.2 AudioCodes Gateway/SBC. 

# Script to Backup AudioCodes Gateways# Created By Jonathan McKinney (blog.ucomsgeek.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 = "m4000.ucomsgeek.com"
$backuppath = "c:\scripts\backups\"


# Variable Initialization
$filename = $null
$statuscodeeval = $null


#Function to detect if Forms Auth is present
function DetectAuthType ($ad)
{
    $loginurl = $null
    
    $loginurl = "http://" + $ad + "/api"
    $statuscode = curl -o null.txt $loginurl -w '%{http_code}'
    If ($statuscode -eq "404")
    {
        $loginurl = "http://" + $ad + "/"
        $statuscode = curl -o null.txt $loginurl -w '%{http_code}'
        Write-host `r`n "Status Code " $statuscode
        If ($statuscode -eq "401")
        {
            $statuscodeeval = "basic"
        }
        ElseIf ($statuscode -eq "203")
        {
            $statuscodeeval = "forms"
        }
        Else
        {
            $statuscodeeval = "neither"
        }
    }
    ElseIf ($statuscode -eq "401")
        {
            $statuscodeeval = "api"
        }
    Return $statuscodeeval
    Remove-Item null.txt
}


 # 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 BackupGatewayAPI ($un, $pw, $ad, $path)
{
    $backupurlsc = $null
    $backupurlini = $null
    $backupfilename = $null
    $securepw = $null
    $cred = $null

    $backupurlsc = "http://" + $ad + "/api/v1/actions/saveConfiguration"
    $backupurlini = "http://" + $ad + "/api/v1/files/ini"
    $addressfilename = $ad.Replace(“.”,”-”)
    $backupfilename = $path + "BOARD.ini"
    Write-Host $backupfilename
    $securepw = ConvertTo-SecureString -String $pw -AsPlainText -Force
    $cred = new-object System.Management.Automation.PSCredential ($un, $securepw)
    Invoke-RestMethod $backupurlsc -Method post -Credential $cred
    Invoke-RestMethod $backupurlini -Method get -Credential $cred -OutFile $backupfilename -verbose
    
    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
}
ElseIf ($formsbasedauth -eq "api")
{
     BackupGatewayAPI $username $password $address $backuppath
}
Else
{
     Write-Host "Something went really wrong... couldn't detect Authentication type... Might I suggest Wireshark"
}

20 comments:

  1. Hello Jonathan, my name is Veronica.
    Thank you very much for posting this!
    I just tried it out but I am getting an empty file, do you have any idea of what I might be missing?

    I have never used cURL before and I keep getting a native command error warning for each cURL line in the script.

    curl : % Total % Received % Xferd Average Speed Time Time Time Current
    At line:199 char:23
    + $logoffwebpage = curl $logoffurl -b cookiejar.txt
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: ( % Total % ... Time Current:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

    Dload Upload Total Spent Left Speed
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
    100 4096 0 4096 0 0 2303 0 --:--:-- 0:00:01 --:--:-- 2303
    100 13799 0 13799 0 0 6858 0 --:--:-- 0:00:02 --:--:-- 6858

    ReplyDelete
    Replies
    1. did you remove the curl alias that powershell has by default?

      Delete
    2. are you running within ISE? You might try running from standard powershell if that is the case

      Delete
    3. Hi Jonathan, I have tried to reply twice but for some reason my responses are not being published.
      I did remove the curl alias however the errors stopped once I started using standard powershell instead of ISE.
      Now I do not get any errors but unfortunately I am getting a blank file.

      Is there anything I should enable on the GW side?

      Delete
    4. which version of GW do you have?

      Delete
  2. The script is exactly the same as your except that I changed http to https in $loginurl.
    When the script finishes I get the following output:

    PS C:\Users\60053465> # Main Script
    PS C:\Users\60053465>
    PS C:\Users\60053465> $formsbasedauth = DetectAuthType $address
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 150 0 150 0 0 566 0 --:--:-- --:--:-- --:--:-- 566
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 13799 0 13799 0 0 42070 0 --:--:-- --:--:-- --:--:-- 42070

    Status Code 203


    PS C:\Users\60053465> 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
    >> }
    >> ElseIf ($formsbasedauth -eq "api")
    >> {
    >> BackupGatewayAPI $username $password $address $backuppath
    >> }
    >> Else
    >> {
    >> Write-Host "Something went really wrong... couldn't detect Authentication type... Might I suggest Wireshark"
    >> }
    >>

    Forms Authentication

    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 143 0 122 100 21 326 56 --:--:-- --:--:-- --:--:-- 326
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 192 0 112 100 80 265 189 --:--:-- --:--:-- --:--:-- 265

    Detecting .ini name

    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 13799 0 13799 0 0 44227 0 --:--:-- --:--:-- --:--:-- 44227

    Detected Board Filename

    Backing up xxxx.xxxx.xxxx.net

    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0

    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 13799 0 13799 0 0 42070 0 --:--:-- --:--:-- --:--:-- 42070
    PS C:\Users\60053465>


    ReplyDelete
    Replies
    1. if youu require https, you would need to change all instances of http to https in the script. the $loginurl only applies to just that one function.

      Delete
  3. Sorry I didn´t explain better, I did change http to https on all instances in the script. I noticed this because I kept getting login errors but after I made the change the script seemed to be working fine.

    ReplyDelete
    Replies
    1. I think I will need to see your script... is it possible to post here?

      Delete
    2. Sure, here it goes.
      URL, user and pass have been replaced.

      ------------------------------------------

      Remove-Item alias:curl

      # User Modifiable Variables
      $username = "test"
      $password = "test"
      $address = "xxxx.net"
      $backuppath = "d:\scripts.txt"


      # Variable Initialization
      $filename = $null
      $statuscodeeval = $null


      #Function to detect if Forms Auth is present
      function DetectAuthType ($ad)
      {
      $loginurl = $null

      $loginurl = "https://" + $ad + "/api"
      $statuscode = curl -o null.txt $loginurl -w '%{http_code}'
      Write-Host "STATUS CODE 1" $statuscode

      If ($statuscode -eq "404")
      {
      $loginurl = "https://" + $ad + "/"
      $statuscode = curl -o null.txt $loginurl -w '%{http_code}'
      Write-Host "STATUS CODE 2" $statuscode

      If ($statuscode -eq "401")
      {
      $statuscodeeval = "basic"
      Write-Host $statuscodeeval
      }
      ElseIf ($statuscode -eq "203")
      {
      $statuscodeeval = "forms"
      Write-Host $statuscodeeval
      }
      Else
      {
      $statuscodeeval = "neither"
      Write-Host $statuscodeeval
      }
      }
      ElseIf ($statuscode -eq "401")
      {
      $statuscodeeval = "api"
      }
      Return $statuscodeeval
      Remove-Item null.txt
      }

      Delete
    3. # Function to Authenticate with Forms Auth
      function AuthenticateForms ($un, $pw, $ad)
      {
      $login1sregex = "\(\d*)\<\/s\>"
      $login1rregex = "\(.*)\<\/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 = "https://" + $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"
      }

      Delete
    4. # 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 = "https://" + $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 = "https://" + $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 = "https://" + $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 = "https://" + $ad + "/FS/" + $fn
      $backupfilename = $path + $fn
      curl -o $backupfilename $backupurl -b cookiejar.txt
      Write-Host `r
      }

      Delete
    5. function BackupGatewayAPI ($un, $pw, $ad, $path)
      {
      $backupurlsc = $null
      $backupurlini = $null
      $backupfilename = $null
      $securepw = $null
      $cred = $null

      $backupurlsc = "https://" + $ad + "/api/v1/actions/saveConfiguration"
      $backupurlini = "https://" + $ad + "/api/v1/files/ini"
      $addressfilename = $ad.Replace(“.”,”-”)
      $backupfilename = $path + "BOARD.ini"
      Write-Host $backupfilename
      $securepw = ConvertTo-SecureString -String $pw -AsPlainText -Force
      $cred = new-object System.Management.Automation.PSCredential ($un, $securepw)
      Invoke-RestMethod $backupurlsc -Method post -Credential $cred
      Invoke-RestMethod $backupurlini -Method get -Credential $cred -OutFile $backupfilename -verbose

      Write-Host `r
      }


      # Function to Logoff Audiocodes with Forms Auth after finishing work
      function LogoffForms ($ad)
      {
      $logoffurl = $null
      $logoffwebpage = $null

      $logoffurl = "https://" + $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

      }
      ElseIf ($formsbasedauth -eq "api")
      {
      BackupGatewayAPI $username $password $address $backuppath

      }
      Else
      {
      Write-Host "Something went really wrong... couldn't detect Authentication type... Might I suggest Wireshark"
      }

      Delete
    6. so right off the bat... your $backuppath has a filename indicated. That should only be a directory like "d:\scripts\backup\"

      Delete
  4. Hi Jonathan,

    Awesome script... but I'm having issues executing it from Task Scheduler. Any ideas how make cURL functionality available from within there? This seems to be the issue:

    Invoke-WebRequest : Parameter cannot be processed because the parameter name 'o' is ambiguous. Possible matches
    include: -OutFile -OutVariable -OutBuffer.
    At C:\Scripts\Backup-Audiocodes.ps1:21 char:24
    + $statuscode = curl -o null.txt $loginurl -w '%{http_code}'
    + ~~
    + CategoryInfo : InvalidArgument: (:) [Invoke-WebRequest], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousParameter,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

    ReplyDelete
  5. Added Remove-Item alias:curl to the script.
    Now is works! :D

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Yea I had a note about that above... I struggle with it because some people like to add that to their profile instead

      Delete