A huge thank you goes out to Andrew, his blog, and his Git here! His scripts were a huge help getting unixODBC and Python 3 talking via the Microsoft ODBC Driver 11 for SQL Server – RedHat Linux on Ubuntu 14.04.


A couple of quick notes:

  • The unixODBC script build_dm.sh will download the needed tarball for you.
  • Make sure you download the latest driver from the Microsoft downloads site, the required driver for the scripts will have msodbcsql-11.0.2270.0. Download msodbcsql-11.0.2270.0.tar.gz (Microsoft’s driver) to your /opt directory. The install.sh script Andrew provides is meant to replace the install.sh script in the parent directory of the tarball downloaded from Microsoft to allow for installation on Ubuntu.
    $ sudo wget http://download.microsoft.com/download/B/C/D/BCDD264C-7517-4B7D-8159-C99FC5535680/RedHat6/msodbcsql-11.0.2270.0.tar.gz
    
  • The SQL driver and the install.sh script require the below dependencies on Debian based distros to function and make the driver:
    $ sudo apt-get install openssl libkrb5-3 libc6 e2fsprogs libodbc1
    
  • If you use the native driver from Microsoft, you do not need to use FreeTDS which is commonly found when Googling this topic. FreeTDS does not support all capabilities of the newer SQL Server versions like the native driver does.
  • Andrew’s script for install.sh REQUIRES editing to check for correct packages if running on a Debian based distro other than Debian or Ubuntu, i.e. Linux Mint:
    if [ $os_dist_id == "LinuxMint" ]
    
  • You may need to create symbolic links too if installing on a different distro. Linux Mint required all of the links to be created below as I would receive file not found without them:
    sudo ln -s /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /usr/lib/x86_64-linux-gnu/libcrypto.so.10
    sudo ln -s /lib/x86_64-linux-gnu/libssl.so.1.0.0 /usr/lib/x86_64-linux-gnu/libssl.so.10
    sudo ln -s /usr/lib/x86_64-linux-gnu/libodbcinst.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbcinst.so.1
    sudo ln -s /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbc.so.1
    
  • For the driver installer (install.sh) install, verify (Used to check if everything works), –force (Useful for forcing reinstall with: install –force), and –help are available parameters.
  • Make your life 100x easier and use SQL Authentication on the SQL server. If you have to use Kerberos, here is your reference, good luck! 😉
  • Connection string parameters for your database can be found here. pyodbc references can be found here, here, and here.
  • I’m using pypyodbc not pyodbc (Another package) in my Python below. pypyodbc is referenced here and is compatible with pyodbc.

Once unixODBC is installed, a handy tool for checking out the config files:

$ sudo odbcinst -j
unixODBC 2.3.2
DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /root/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8

Sample odbcinst.ini (The script will create this for you!):

$ cat /etc/odbcinst.ini
[ODBC Driver 11 for SQL Server]
Description=Microsoft ODBC Driver 11 for SQL Server
Driver=/opt/microsoft/msodbcsql/lib64/libmsodbcsql-11.0.so.2270.0
Threading=1
UsageCount=2

Sample connection string from Python using pypyodbc:

import pypyodbc
# APP is not required, but helpful if debugging connections from SQL server
cnxn = pypyodbc.connect('APP=YourAppsName;DRIVER={ODBC Driver 11 for SQL Server};SERVER=IPAddress;PORT=1433;DATABASE=DatabaseName;UID=user;PWD=password;')
cursor = cnxn.cursor()
cursor.execute("select * from table")
rows = cursor.fetchall()

Andrew’s build_dm.sh (In case he ever takes it down! 😉 ):

#!/bin/bash
# Microsoft SQL Server ODBC Driver V1.0 for Linux Build unixODBC DriverManager script
# Copyright Microsoft Corp.

# driver name
driver_name="Microsoft SQL Server ODBC Driver V1.0 for Linux"

# required constants
req_os="Linux";
req_proc="x86_64";
req_software=( "wget" "tar" "make" )

# Create a temp directory for intermediate files
tmp=${TMPDIR-/tmp}
tmp=$tmp/"unixODBC".$RANDOM.$RANDOM.$RANDOM
(umask 077 && mkdir $tmp) || {
    echo "Could not create temporary directory for the log file." 2>&1
    exit 1
}

# driver manager constants
dm_name="unixODBC 2.3.2 DriverManager"
dm_dir="unixODBC-2.3.2"
dm_package='unixODBC-2.3.2.tar.gz'
dm_url="ftp://ftp.unixodbc.org/pub/unixODBC/$dm_package"
dm_package_path=$tmp/$dm_package
dm_build_msg=""

libdir='/usr/lib64'
prefixdir='/usr'
sysconfdir='/etc'

log_file=$tmp/build_dm.log

# warning accepted by user or overridden by command line option
warning_accepted=0

function log()
{
    local msg=$*;
    local date=$(date);
    echo "["$date"]" $msg >> $log_file
}

# format a message and status for an 80 character terminal
# this assumes the msg has already been output and this used
# only for printing the status correctly
function echo_status_aligned
{
    local msg=$1
    local status=$2
    # 2 spaces in between the status and the message
    local total_len=$(( ${#msg} + ${#status} + 2 ))

    if [ $total_len -gt 80 ]; then
        echo "Cannot show a message longer than 80 characters"
        exit 1
    fi

    local dots="................................................................................"
    local dot_count=$(( 80 - $total_len ))

    local status_msg=" $(expr substr "$dots" 1 $dot_count) $status"
    echo $status_msg

    return 0
}

# verify that the installation is on a 64 bit OS
function check_for_Linux_x86_64 ()
{
    log "Verifying if on a 64 bit Linux compatible OS"

    local proc=$(uname -p);
    if [ $proc != $req_proc ]; then
        log "This installation of the" $dm_name "may only be installed"
        log "on a 64 bit Linux compatible operating system."
        return 1;
    fi

    local os=$(uname -s);
    if [ $os != $req_os ]; then
        log "This installation of the" $dm_name "may only be installed"
        log "on a 64 bit Linux compatible operating system."
        return 1;
    fi

    return 0;
}

function check_wget
{
    log "Checking that wget is installed"

    # if using a file url, wget is not necessary
    if [ "${dm_url##file://}" == "$dm_url" ]; then
        return 0;
    fi

    hash wget &> /dev/null
    if [ $? -eq 1 ]; then
        log "'wget' required to download $dm_name"
        return 1;
    fi

    return 0
}

function check_tar
{
    log "Checking that tar is installed"

    hash tar &> /dev/null
    if [ $? -eq 1 ]; then
        log "'tar' required to unpack $dm_name"
        return 1;
    fi

    return 0
}

function check_make
{
    log "Checking that make is installed"

    hash make &> /dev/null
    if [ $? -eq 1 ]; then
        log "'make' required to build $dm_name"
        return 1;
    fi

    return 0
}

function download
{
    log "Downloading $dm_url"

    # if they use a file:// url then just point the package at that path and return
    # since wget doesn't support file urls
    if [ ${dm_url##file://} != $dm_url ]; then
        dm_package_path=${dm_url##file://}
        dm_dir=`tar tzf $dm_package_path | head -1 | sed -e 's/\/.*//'`
        dm_name="Custom unixODBC $dm_dir"
        log "Using $dm_name"
        make_build_msg
        return 0
    fi

    $(wget -a $log_file -P $tmp $dm_url  )

    if [ ! -e $dm_package_path ]; then
        log "Failed to retrieve $dm_name from $dm_url."
        return 1;
    fi

    return 0
}

function unpack
{
    log "Unpacking $dm_package_path to $tmp"

    $(tar --directory=$tmp -xvzf $dm_package_path >> $log_file 2>&1)

    if [ $? -ne 0 ]; then
        log "Unpacking $dm_package_path failed."
        return 1
    fi

    return 0
}

function configure_dm
{
    log "Configuring"

    # As per https://msdn.microsoft.com/en-US/library/hh568449(v=sql.110).aspx
    # we set this here rather than at the top to delay the eval of
    # the variables in the string
    local config_options=(
                "--enable-gui=no"
                "--enable-drivers=no"
                "--enable-iconv"
                "--with-iconv-char-enc=UTF8"
                "--with-iconv-ucode-enc=UTF16LE"
                "--libdir=$libdir"
                "--prefix=$prefixdir"
                "--sysconfdir=$sysconfdir"
                "CPPFLAGS=-DSIZEOF_LONG_INT=8"
               )

    $(cd $tmp/$dm_dir >> $log_file 2>&1; ./configure ${config_options[@]} >> $log_file 2>&1)

    if [ $? -ne 0 ]; then
        log "Failed to configure $dm_name"
        return 1
    fi

    return 0
}

function make_dm
{
    log "Building"

    $(cd $tmp/$dm_dir >> $log_file 2>&1 ; make >> $log_file 2>&1)

    if [ $? -ne 0 ]; then
        log "Failed to make $dm_name"
        return 1
    fi

    return 0
}

function install_dm
{
  log "Installing"

  $(cd $tmp/$dm_dir >> $log_file 2>&1 ; make install >> $log_file 2>&1)

  if [ $? -ne 0 ]; then
    log "Failed to make install $dm_name"
    return 1
  fi

  return 0
}

function make_build_msg
{
    dm_build_msg=(
        "Verifying processor and operating system"
        "Verifying wget is installed"
        "Verifying tar is installed"
        "Verifying make is installed"
        "Downloading $dm_name"
        "Unpacking $dm_name"
        "Configuring $dm_name"
        "Building $dm_name"
        "Installing $dm_name"
    )
}

function build
{
    local build_steps=( check_for_Linux_x86_64 check_wget check_tar check_make  download unpack configure_dm make_dm install_dm )
        make_build_msg
    local build_neutral=( "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" "NOT ATTEMPTED" )
    local build_success=( 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' 'OK' )
    local build_fail=( 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' 'FAILED' )

    # asserts for the arrays above
    if [ ${#build_steps[@]} -ne ${#dm_build_msg[@]} ]; then
        echo "Build steps and build message array out of sync"
        exit 1
    fi

    if [ ${#build_steps[@]} -ne ${#build_neutral[@]} ]; then
        echo "Build steps and build message array out of sync"
        exit 1
    fi

    if [ ${#build_steps[@]} -ne ${#build_success[@]} ]; then
        echo "Build steps and build message array out of sync"
        exit 1
    fi

    if [ ${#build_steps[@]} -ne ${#build_fail[@]} ]; then
        echo "Build steps and build message array out of sync"
        exit 1
    fi

    local status=0

    for (( i = 0; i < ${#build_steps[@]}; i++ ))
    do

        local fn=${build_steps[$i]}
        local status_msg="${build_neutral[$i]}"

        echo -n "${dm_build_msg[$i]} "

        if [ $status -eq 0 ]; then

            $fn

            if [ $? -ne 0 ]; then
                status_msg="${build_fail[$i]}"
                status=1
            else
                status_msg="${build_success[$i]}"
            fi
        fi

        echo_status_aligned "${dm_build_msg[$i]} " "$status_msg"

    done

    return $status
}

function print_usage
{
    echo "Usage: build_dm.sh [options]"
    echo
    echo "This script downloads, configures, builds and installs $dm_name"
    echo
    echo "Valid options are --help, --download-url, --prefix, --libdir, --sysconfdir, --accept-warning"
    echo "  --help - prints this message"
    echo "  --download-url=url | file:// - Specify the location (and name) of unixODBC-2.3.0.tar.gz."
    echo "       For example, if unixODBC-2.3.0.tar.gz is in the current directory, specify "
    echo "       --download-url=file://unixODBC-2.3.0.tar.gz."
    echo "  --prefix - directory to install $dm_package to."
    echo "  --libdir - directory where ODBC drivers will be placed"
    echo "  --sysconfdir - directory where $dm_name configuration files are placed"
    echo "  --accept-warning - indicate that you have read and accept the warning to download the file"
    echo

    # prevent the script from continuing
    exit 0
}

function approve_download
{
    log "Accept the WARNING about download of unixODBC"

    if [ ! -f "./WARNING" ]; then
        log "WARNING file not found."
        echo "Cannot display download warning.  Please refer to the original archive for the"
        echo "WARNING file and then use the --accept-warning option to run this script."
        exit 1
    fi

    hash more &> /dev/null

    if [ $? -ne 0 ]; then
        log "more program not found. Cannot display the build warning without more."
        echo "Cannot display license agreement.  Please read the license agreement in LICENSE and"
        echo "re-run the install with the --accept-license parameter."
        exit 1
    fi

    more ./WARNING

    echo
    read -p "Enter 'YES' to have this script continue: " accept
    echo

    if [ "$accept" == "YES" ]; then
        log "Warning accepted"
        warning_accepted=1
        return 0
    fi

    log "Warning not accepted"
    echo "Exiting because warning not accepted"
    exit 1
}

echo
echo "Build and Install $dm_name script"
echo "Copyright Microsoft Corp."
echo

while [ "$1" ]
do
    case "$1" in
        --download-url=*)
            dm_url=${1##--download-url=}
            log "$dm_name URL: $dm_url"
            ;;
        --prefix=*)
            prefixdir=${1##--prefix=}
            log "Installing $dm_name to $prefixdir"
            ;;
        --libdir=*)
            libdir=${1##--libdir=}
            log "Drivers configured to be placed at $libdir"
            ;;
        --sysconfdir=*)
            sysconfdir=${1##--sysconfdir=}
            log "Configuration directory set to $sysconfdir"
            ;;
        --help)
            print_usage
            ;;
        --accept-warning)
            warning_accepted=1
            ;;
        *)
            echo "Unknown option $1"
            print_usage
            exit 1
            ;;
    esac
    shift
done

echo "PLEASE NOTE THAT THIS WILL POTENTIALLY INSTALL THE NEW DRIVER MANAGER OVER ANY"
echo "EXISTING UNIXODBC DRIVER MANAGER.  IF YOU HAVE ANOTHER COPY OF UNIXODBC INSTALLED,"
echo "THIS MAY POTENTIALLY OVERWRITE THAT COPY."
echo
read -p "Would you like to proceed? (YES/NO): " accept
echo

if [ "$accept" == "YES" ]; then
  log "Accepted overwrite warning."
else
  log "Declined overwrite warning."
  echo "The script is now ending and no actions will be taken."
  echo
  exit 0
fi

if [ $warning_accepted -ne 1 ]; then
    approve_download
fi

build $*

if [ $? -ne 0 ]; then
    echo "Errors occurred. See the $log_file file for more details."
    exit 1
fi

echo "Successfully installed $dm_name. Please see $log_file for additional information."

exit 0

Andrew’s install.sh (Pretty much what someone at Microsoft should copy to support Ubuntu!):

#!/bin/bash
# Microsoft ODBC Driver 11 for SQL Server Installer
# Copyright Microsoft Corp.

# Set to 1 for debugging information/convenience.
debug=0

# Strings listed here
driver_name="Microsoft ODBC Driver 11 for SQL Server";
driver_version="11.0.2270.0"
driver_dm_name="ODBC Driver 11 for SQL Server"
driver_short_name="msodbcsql"

# Requirements listed here
req_os="Linux";
req_proc="x86_64";
req_dm_ver="2.3.2";
dm_name="unixODBC $req_dm_ver";
os_dist_id=`lsb_release -is`
req_libs=""

if [ $os_dist_id == "Ubuntu" ] || [ $os_dist_id == "Debian" ]; then
    req_libs=( '~i"^libc6$"' '~i"libkrb5\-[0-9]$"' '~i"^e2fsprogs$"' '~i"^openssl$"' )
else
    req_libs=( glibc e2fsprogs krb5-libs openssl )
fi

#language of the install
lang_id="en_US";

# files to be copied by directory
driver_file="libmsodbcsql-11.0.so.2270.0"

lib_files=( "lib64/$driver_file" )
lib_perms=( 0755 )

bin_files=( "bin/bcp-11.0.2270.0"
            "bin/sqlcmd-11.0.2270.0" )
bin_sym=( bcp sqlcmd )
bin_perms=( 0755 0755 )

sup_files=( install.sh build_dm.sh README LICENSE WARNING )
sup_perms=( 0755 0755 0644 0644 0644 )

rll_files=( "bin/bcp.rll"
            "bin/SQLCMD.rll"
            "bin/BatchParserGrammar.dfa"
            "bin/BatchParserGrammar.llr"
            "lib64/msodbcsqlr11.rll" )
rll_perms=( 0644 0644 0644 0644 0644 )

doc_files=( "docs/en_US.tar.gz" )
doc_perms='$(printf "0644 %.0s" {1..'${#doc_files[@]}'})'
doc_perms=( $(eval "echo $doc_perms") )

inc_files=( "include/msodbcsql.h" )
inc_perms='$(printf "0644 %.0s" {1..'${#inc_files[@]}'})'
inc_perms=( $(eval "echo $inc_perms") )

dirs=( bin_dir lib_dir sup_dir rll_dir doc_dir inc_dir )
sym_dirs=( bin_sym_dir lib_sym_dir sup_sym_dir rll_sym_dir doc_sym_dir inc_sym_dir )
file_sets=( bin_files lib_files sup_files rll_files doc_files inc_files )
link_sets=( bin_sym "null" "null" "null" "null" "null" )
file_perm_sets=( bin_perms lib_perms sup_perms rll_perms doc_perms inc_perms )
link_perm_sets=( bin_perms lib_perms sup_perms rll_perms doc_perms inc_perms )

# "assertions" that the file and sym link arrays are sane
if [ ${#lib_files[@]} -ne ${#lib_perms[@]} ]; then
    echo "Lib files and permission sets don't match"
    exit 1;
fi
if [ ${#bin_files[@]} -ne ${#bin_sym[@]} ]; then
    echo "Bin files and sym links don't match"
    exit 1;
fi
if [ ${#bin_files[@]} -ne ${#bin_perms[@]} ]; then
    echo "Bin files and permission sets don't match"
    exit 1;
fi
if [ ${#sup_files[@]} -ne ${#sup_perms[@]} ]; then
    echo "Supplemental files and permission sets don't match"
    exit 1;
fi
if [ ${#rll_files[@]} -ne ${#rll_perms[@]} ]; then
    echo "RLL files and permission sets don't match"
    exit 1;
fi
if [ ${#dirs[@]} -ne ${#file_sets[@]} ]; then
    echo "Directories and file sets don't match"
    exit 1;
fi
if [ ${#file_sets[@]} -ne ${#link_sets[@]} ]; then
    echo "File and link sets don't match"
    exit 1;
fi
if [ ${#file_sets[@]} -ne ${#file_perm_sets[@]} ]; then
    echo "File and permission sets don't match"
    exit 1;
fi
if [ ${#link_sets[@]} -ne ${#link_perm_sets[@]} ]; then
    echo "Link and permission sets don't match"
    exit 1;
fi
if [ ${#doc_files[@]} -ne ${#doc_perms[@]} ]; then
    echo "Doc files and permission sets don't match"
    exit 1;
fi
if [ ${#inc_files[@]} -ne ${#inc_perms[@]} ]; then
    echo "Include files and permission sets don't match"
    exit 1;
fi

# directories to hold the file categories
bin_dir="";
lib_dir="";
sup_dir="/opt/microsoft/$driver_short_name/$driver_version";
rll_dir="";
doc_dir="";
inc_dir="";

# Force installation flag (--force parameter) default is not to force
force=0

# Accept the license flag (--accept-license) default is that they must accept the license via a prompt
license_accepted=0

# Log file in the temp directory
tmp=${TMPDIR-/tmp}
tmp="$tmp/$driver_short_name.$RANDOM.$RANDOM.$RANDOM"
(umask 077 && mkdir $tmp) || {
    echo "Could not create temporary directory for the log file." 2>&1
    exit 1
}
log_file=$tmp/install.log

# for debugging purposes
[ $debug -eq 1 ] && log_file="install.log"
[ $debug -eq 1 ] && rm -f install.log

echo
echo "$driver_name Installation Script"
echo "Copyright Microsoft Corp."
echo
echo "Starting install for $driver_name"
echo

function print_usage()
{
    echo "Usage: install.sh [global options] command [command options]"
    echo
    echo "Global options:"
    echo "   --help - prints this message"
    echo "Valid commands are verify and install"
    echo "  install) install the driver (also verifies before installing and registers"
    echo "           with the driver manager)"
    echo "  verify) check to make sure the unixODBC DriverManager configuration is"
    echo "          correct before installing"
    echo "install command take the following options:"
    echo "  --bin-dir=<directory> - location to create symbolic links for bcp and sqlcmd utilities,"
    echo "      defaults to the /usr/bin directory"
    echo "  --lib-dir=<directory> - location to deposit the Microsoft SQL Server ODBC Driver for Linux,"
    echo "      defaults to the /opt/microsoft/msodbcsql/lib directory"
    echo "  --force - continues installation even if an error occurs"
    echo "  --accept-license - forgoes showing the EULA and implies agreement with its contents"
    echo

    # don't return if we're printing the usage
    exit 0;
}

function log()
{
    local msg=$*;
    local date=$(date);
    echo "["$date"]" $msg >> $log_file
}

# format a message and status for an 80 character terminal
function format_status
{
    local msg=$1
    local status=$2
    # 2 spaces in between the status and the message
    local total_len=$(( ${#msg} + ${#status} + 2 ))

    if [ $total_len -gt 80 ]; then
        echo "Cannot show a message longer than 80 characters"
        exit 1
    fi

    local dots="................................................................................"
    local dot_count=$(( 80 - $total_len ))

    local full_msg="$msg $(expr substr "$dots" 1 $dot_count) $status"
    echo $full_msg

    return 0
}

function report_config()
{
    format_status "Checking for 64 bit Linux compatible OS" "$1"
    format_status "Checking required libs are installed" "$2"
    format_status "unixODBC utilities (odbc_config and odbcinst) installed" "$3"
    format_status "unixODBC Driver Manager version $req_dm_ver installed" "$4"
    format_status "unixODBC Driver Manager configuration correct" "$5"
    format_status "$driver_name already installed" "$6"
}

# verify that the installation is on a 64 bit OS
function check_for_Linux_x86_64 ()
{
    log "Verifying on a 64 bit Linux compatible OS"
    local proc=$(uname -p);
    if [ $proc != $req_proc ]; then
        log "This installation of the $driver_name may only be installed"
        log "on a 64 bit Linux compatible operating system."
        return 1;
    fi

    local os=$(uname -s);
    if [ $os != $req_os ]; then
        log "This installation of the $driver_name may only be installed"
        log "on a 64 bit Linux compatible operating system."
        return 1;
    fi

    return 0;
}

# verify that the required libs are on the system
function check_required_libs
{
    log "Checking that required libraries are installed"

    for lib in ${req_libs[@]}
    do
        hash rpm &> /dev/null
        has_rpm=$?
        hash aptitude &> /dev/null
        has_aptitude=$?
        local present=""
        if [ $has_rpm -eq 0 ]; then
            present=$(rpm -q -a $lib) >> $log_file 2>&1
        elif [ $has_aptitude -eq 0 ]; then
            present=$(aptitude search $lib ) >> $log_file 2>&1
        fi
        if [ "$present" == "" ]; then
            log "The $lib library was not found installed in the RPM database."
            log "See README for which libraries are required for the $driver_name."
            return 1;
        fi
    done

    return 0;
}

# verify that the driver manager utilities are runnable so we may
# check the configuration and install the driver.
function find_odbc_config ()
{
    log "Verifying if unixODBC is present"
    # see if odbc_config is installed
    hash odbc_config &> /dev/null
    if [ $? -eq 1 ]; then
        log "odbc_config from unixODBC was not found.  It is required to properly install the $driver_name";
        return 1;
    fi

    hash odbcinst &> /dev/null
    if [ $? -eq 1 ]; then
        log "odbcinst from unixODBC was not found.  It is required to properly install the $driver_name";
        return 1;
    fi

    return 0;
}

function is_already_installed()
{
    log "Checking if $driver_name is already installed in $dm_name"
    odbcinst -q -d -n "$driver_dm_name" -v >> $log_file
    if [ $? -eq 0 ]; then
        log "The $driver_name is already installed."
        log "Use --force to reinstall the driver again.";
        return 1;
    fi

    return 0;
}

function verify_dm_version ()
{
    log "Verifying that unixODBC is version $req_dm_ver"

    # verify version
    local version=$(odbc_config --version);
    if [ $? -ne 0 ]; then
        log "Cannot determine version of installed unixODBC.";
        return 1;
    fi
    local maj_ver_num=`echo $version | cut -d'.' -f1`
    local min_ver_num=`echo $version | cut -d'.' -f2`
    local pat_ver_num=`echo $version | cut -d'.' -f3`
    if [ "$maj_ver_num" -eq "2" ] && [ "$min_ver_num" -ge "3" ] && [ "$pat_ver_num" -ge "0" ]; then
        log "unixODBC version is >= 2.3.0";
    else
        log "unixODBC version must be" $req_dm_ver ".  See README for more information.";
        return 1;
    fi

    return 0;
}

function verify_dm_config()
{
    local config=$(odbc_config --cflags)

    local sizeof_long_int=${config/SIZEOF_LONG_INT\=8/}
    local legacy_64bit_mode=${config/BUILD_LEGACY_64_BIT_MODE/}

    # configuration must have this flag set, so it should be deleted from sizeof_long_int
    if [ "$config" == "$sizeof_long_int" ]; then
        log "unixODBC must have the configuration SIZEOF_LONG_INT=8."
        log "This will probably require a rebuild of the unixODBC Driver Manager."
        log "See README for more information."
        return 1;
    fi

    # configuration shouldn't have this flag set, so it should not be deleted (be the same)
    if [ "$config" != "$legacy_64bit_mode" ]; then
        log "unixODBC must not have BUILD_LEGACY_64_BIT_MODE configuration flag set."
        log "This will probably require a rebuild of the unixODBC Driver Manager."
        log "See README for more information."
        return 1;
    fi

    return 0;
}

# verify all configuration prerequisites and print the status of each.
# 0 means all checks pass, 1 means one or more items has failed

function verify_config
{
    local proc_os_okay="NOT CHECKED"
    local libs_installed="NOT CHECKED"
    local odbc_config="NOT CHECKED"
    local already_installed="NOT CHECKED"
    local version_dm_okay="NOT CHECKED"
    local dm_config_okay="NOT CHECKED"

    verify_steps=( check_for_Linux_x86_64 check_required_libs find_odbc_config verify_dm_version verify_dm_config is_already_installed )
    verify_status_vars=( proc_os_okay libs_installed odbc_config version_dm_okay dm_config_okay already_installed )
    verify_success=( 'OK' 'OK' 'OK' 'OK' 'OK*' 'NOT FOUND' )
    verify_fail=( 'FAILED' 'NOT FOUND' 'FAILED' 'FAILED' 'FAILED' 'INSTALLED' )

    if [ ${#verify_steps[@]} -ne ${#verify_status_vars[@]} ]; then
        echo "Error in verify script 1"
        exit 1;
    fi
    if [ ${#verify_steps[@]} -ne ${#verify_success[@]} ]; then
        echo "Error in verify script 2"
        exit 1;
    fi
    if [ ${#verify_steps[@]} -ne ${#verify_fail[@]} ]; then
        echo "Error in verify script 3"
        exit 1;
    fi

    local i=0

    for (( i = 0 ; i < ${#verify_steps[@]} ; i++ ))
    do
        local fn=${verify_steps[$i]}
        local status_var=${verify_status_vars[$i]}

        eval $status_var="\"${verify_success[$i]}\""

        $fn
        if [ $? -ne 0 ]; then
            if [ $force -ne 1 ]; then
               status=1;
            fi
            eval $status_var="\"${verify_fail[$i]}\""
            break
        fi

    done

    report_config "$proc_os_okay" "$libs_installed" "$odbc_config" "$version_dm_okay" "$dm_config_okay" "$already_installed"

    return $status
}

# installation

function copy_files
{
    log "Copying files"

    local i=0

    for (( i = 0 ; i < ${#dirs[@]} ; i++ ))
    do
        local dir="${dirs[$i]}"
        local files="${file_sets[$i]}"
        local perms="${file_perm_sets[$i]}"

        # evaluate the contents of the variables and assign them back
        eval dir=\$$dir
        eval files=\(\$\{$files\[\@\]\}\)
        eval perms=\(\$\{$perms\[\@\]\}\)

        mkdir -p $dir

        local j=0

        for (( j = 0; j < ${#files[@]}; j++ ))
        do

            local f=${files[$j]}
            local p=${perms[$j]}

            log "Copying $f to $dir"
            cp $f $dir >> $log_file 2>&1

            if [ $? -ne 0 ]; then
                log "Failed to copy $f to $dir."
                if [ $force -ne 1 ]; then
                    return 1;
                fi
            fi

            f=${f##*/}
            log "Setting permissions on $f"
            chmod $p $dir/$f >> $log_file 2>&1

            if [ $? -ne 0 ]; then
                log "Failed to set the permission of $f to $p."
                if [ $force -ne 1 ]; then
                    return 1;
                fi
            fi

        done
    done

    return 0;
}

# it tries to remove all the files regardless of the outcome of a removal,
# as such it always returns 0 for "success"
function remove_files
{
    log "Removing files"

    local i=0

    for (( i = 0 ; i < ${#dirs[@]} ; i++ ))
    do
        local dir="${dirs[$i]}"
        local files="${file_sets[$i]}"
        local links="${link_sets[$i]}"

        # evaluate the contents of the variables and assign them back
        eval dir=\$$dir
        eval files=\( \$\{$files\[\@\]\} \)
        eval links=\( \$\{$links\[\@\]\} \)

        for l in ${links[@]}
        do
            [ "$l" == "null" ] && continue

            log "Removing $dir/$l"
            rm -f $dir/$l >> $log_file 2>&1

            if [ $? -ne 0 ]; then
                log "Non fatal error: Failed to remove $l to $dir."
            fi
        done

        for f in ${files[@]}
        do
            log "Removing $dir/$f"
            rm -f $dir/$f >> $log_file 2>&1

            if [ $? -ne 0 ]; then
                log "Non fatal error: Failed to remove $f to $dir."
            fi
        done
    done

    return 0;
}

function register_driver
{
    log "Registering the $driver_name driver"

    # write INI file for driver installation in the temp directory
    local template_ini="$tmp/$driver_short_name.ini"

    # for debugging purposes
    [ $debug -eq 1 ] && template_ini="$driver_short_name.ini"

    echo "[$driver_dm_name]" > $template_ini
    echo "Description = $driver_name" >> $template_ini
    echo "Driver = $lib_dir/$driver_file" >> $template_ini
    echo "Threading = 1" >> $template_ini
    echo "" >> $template_ini

    if [ $? -ne 0 ]; then
        log "Failed to create ini file $template_ini used to install the driver"
        return 1;
    fi

    # install the driver using odbcinst
    odbcinst -i -d -f "$template_ini" 2>&1 >> $log_file

    if [ $? -ne 0 ]; then
        log "Failed installing driver $driver_name with $dm_name"
        return 1;
    fi

    # copy the template ini file to the supplemental directory
    cp $template_ini $sup_dir 2>> $log_file

    if [ $? -ne 0 ]; then
        log "Warning: $template_ini could not be copied to $sup_dir"
    fi

    return 0;
}

function extract_docs
{
    local doc_file="$doc_dir/$lang_id.tar.gz"

    log "Extracting documentation from $doc_file"

    local cwd=$(pwd)

    cd $doc_dir
    if [ $? -ne 0 ]; then
        log "Couldn't enter the directory $doc_dir to extract documentation."
        return 1
    fi

    tar xvzf $doc_file 2>&1 >> $log_file
    if [ $? -ne 0 ]; then
        log "Couldn't extract documentation from $doc_file"
        return 1
    fi

    rm $doc_file 2>&1 >> $log_file
    if [ $? -ne 0 ]; then
        log "Couldn't erase documentation archive after extraction"
        return 1
    fi

    return 0;
}

function create_symlinks
{
    log "Creating symbolic links"

    local i=0
    local j=0

    for (( i = 0 ; i < ${#dirs[@]} ; i++ ))
    do
        local dir="${dirs[$i]}"
        local sym_dir="${sym_dirs[i]}"
        local files="${file_sets[$i]}"
        local links="${link_sets[$i]}"

        [ "$links" == "null" ] && continue

        # evaluate the contents of the variables and assign them back
        eval dir=\$$dir
        eval sym_dir=\$$sym_dir
        eval files=\( \$\{$files\[\@\]\} \)
        eval links=\( \$\{$links\[\@\]\} \)

        # assertion that the links and files are the same length
        if [ ${#files[@]} -ne ${#links[@]} ]; then
            log "Fatal: file and link lists do not match."
            exit 1;
        fi

        # if there is no symbolic link dir, then there are no symlinks
        [ "$sym_dir" == "" ] && continue

        local j=0

        for (( j = 0 ; j < ${#files[@]} ; j++ ))
        do
            local f="${files[$j]##*/}"
            local l="${links[$j]}"

            #if the "link" is null, then skip it
            [ "$l" == "null" ] && continue

            if [ $force -ne 0 ]; then
                log "Removing previous link due to force flag"
                rm $sym_dir/$l >> $log_file 2>&1
            fi

            log "Linking $l to $f"
            ln -s $dir/$f $sym_dir/$l >> $log_file 2>&1

            if [ $? -ne 0 ]; then
                log "Failed to link $l to $f."
                if [ $force -ne 1 ]; then
                    return 1;
                fi
            fi
        done
    done

    # This has been tested in Ubuntu 12.04 and 14.04 LTS
    log "Creating symlinks needed in Ubuntu."
    hash aptitude &> /dev/null
   	local has_aptitude=$?
    local os_id=$(lsb_release -si);
    if [ $has_aptitude -eq 0 ] && [ "$os_id" == "Ubuntu" ]; then
        if [ $force -eq 1 ]; then
            if [ -h /usr/lib/x86_64-linux-gnu/libcrypto.so.10 ]; then
                rm /usr/lib/x86_64-linux-gnu/libcrypto.so.10;
            fi
            if [ -h /usr/lib/x86_64-linux-gnu/libssl.so.10 ]; then
                rm /usr/lib/x86_64-linux-gnu/libssl.so.10;
            fi
            if [ -h /usr/lib/x86_64-linux-gnu/libodbcinst.so.1 ]; then
                rm /usr/lib/x86_64-linux-gnu/libodbcinst.so.1;
            fi
            if [ -h /usr/lib/x86_64-linux-gnu/libodbc.so.1 ]; then
                rm /usr/lib/x86_64-linux-gnu/libodbc.so.1;
            fi
        fi
        ln -s /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /usr/lib/x86_64-linux-gnu/libcrypto.so.10 >> $log_file 2>&1;
        ln -s /lib/x86_64-linux-gnu/libssl.so.1.0.0 /usr/lib/x86_64-linux-gnu/libssl.so.10 >> $log_file 2>&1;
        if [ -f /usr/lib/x86_64-linux-gnu/libodbcinst.so.2.0.0 ]; then
            ln -s /usr/lib/x86_64-linux-gnu/libodbcinst.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbcinst.so.1 >> $log_file 2>&1;
        else
            log "proper libodbcinst.so not found. You will need to create the symlink manually"
        fi
        if [ -f /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0 ]; then
            ln -s /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0 /usr/lib/x86_64-linux-gnu/libodbc.so.1 >> $log_file 2>&1;
        else
            log "proper libodbc.so not found. You will need to create the symlink manually"
        fi
    else
        SCRIPTPATH=$( cd "$(dirname "$0")" ; pwd -P )
        log "You need to create some symlinks manually. Use the following command to find out more:"
        log "ldd $SCRIPTPATH/lib64/libmsodbcsql-11.0.so.2270.0"
    fi

    return 0;
}

function report_install()
{
    format_status "$driver_name files copied" "$1"
    format_status "Symbolic links for bcp and sqlcmd created" "$2"
    format_status "$driver_name registered" "$3"
}

function process_params
{
    # process parameters
    while [ "$1" ]
    do
        case "$1" in
            --force)
                force=1
                ;;
            --bin-dir=*)
                bin_sym_dir=${1##--bin-dir=}
                bin_sym_dir=${bin_sym_dir/#"~"/$HOME}
                log "Symbolic links to binaries created in $bin_sym_dir"
                ;;
            --lib-dir=*)
                lib_dir=${1##--lib-dir=}
                lib_dir=${lib_dir/#"~"/$HOME}
                log "Driver directory set to $lib_dir"
                ;;
            --accept-license)
                license_accepted=1
                log "License agreement accepted"
                ;;
            *)
                echo "Unknown parameter $1"
                print_usage
                exit 1
                ;;
        esac

        shift
    done

    return 0
}

function accept_license
{
    log "Accept the license agreement"

    if [ ! -f "./LICENSE" ]; then
        log "LICENSE file not found."
        echo "Cannot display license agreement.  Please refer to the original archive for the"
        echo "LICENSE file and re-run the install with the --accept-license parameter."
        exit 1
    fi

    hash more &> /dev/null

    if [ $? -ne 0 ]; then
        log "more program not found. Cannot display license agreement without more."
        echo "Cannot display license agreement.  Please read the license agreement in LICENSE and"
        echo "re-run the install with the --accept-license parameter."
        exit 1
    fi

    more ./LICENSE

    echo
    read -p "Enter YES to accept the license or anything else to terminate the installation: " accept
    echo

    if [ "$accept" == "YES" ]; then
        log "License agreement accepted"
        license_accepted=1
        return 0
    fi

    log "License agreement not accepted"
    return 1
}

function install()
{
    # return value
    local status=0

    process_params $*

    if [ $license_accepted -ne 1 ]; then
        accept_license
    fi

    # technically accept_license should not return if the license isn't accepted,
    # but this is a catch for it
    if [ $? -ne 0 ] || [ $license_accepted -ne 1 ]; then
        return 1
    fi

    verify_config

    local verified=$?

    local files_copied="NOT ATTEMPTED"
    local docs_extracted="NOT ATTEMPTED"
    local driver_registered="NOT ATTEMPTED"
    local symlinks_created="NOT ATTEMPTED"

    # if verification passed or force was specified, then call the functions to install
    # and register the driver
    if [ $verified -eq 0 ] || [ $force -eq 1 ]; then

        if [ "$lib_dir" == "" ]; then
            lib_dir="/opt/microsoft/$driver_short_name/lib64"
        fi

        bin_dir="/opt/microsoft/$driver_short_name/bin"

        if [ "$bin_sym_dir" == "" ]; then
            bin_sym_dir="/usr/bin";
        fi

        sup_dir="/opt/microsoft/$driver_short_name/$driver_version"
        mkdir -p $sup_dir
        if [ -d $sup_dir ]; then
            log "$sup_dir exists"
        else
            log "Could not create $sup_dir"
            return 1
        fi

        rll_dir="$sup_dir/$lang_id"
        mkdir -p $rll_dir
        if [ -d $rll_dir ]; then
            log "$rll_dir exists"
        else
            log "Could not create $rll_dir"
            return 1
        fi

        doc_dir="$sup_dir/docs/$lang_id"
        mkdir -p $doc_dir
        if [ -d $doc_dir ]; then
            log "$doc_dir exists"
        else
            log "Could not create $doc_dir"
            return 1
        fi

        inc_dir="$sup_dir/include"
        mkdir -p $inc_dir
        if [ -d $inc_dir ]; then
            log "$inc_dir exists"
        else
            log "Could not create $inc_dir"
            return 1
        fi

        local install_steps=( copy_files extract_docs create_symlinks register_driver )
        local install_status_vars=( files_copied docs_extracted symlinks_created driver_registered )
        local install_success=( 'OK' 'OK' 'OK' 'INSTALLED' )
        local install_fail=( 'FAILED' 'FAILED' 'FAILED' 'FAILED' )

        # "assertions" that the variables are sane
        if [ ${#install_steps[@]} -ne ${#install_status_vars[@]} ]; then
            echo "Error in install script 1"
            exit 1;
        fi
        if [ ${#install_steps[@]} -ne ${#install_success[@]} ]; then
            echo "Error in install script 2"
            exit 1;
        fi
        if [ ${#install_steps[@]} -ne ${#install_fail[@]} ]; then
            echo "Error in install script 3"
            exit 1;
        fi

        local i=0

        for (( i = 0 ; i < ${#install_steps[@]} ; i++ ))
        do
            local fn=${install_steps[$i]}
            local status_var=${install_status_vars[$i]}

            eval $status_var="\"${install_success[$i]}\""

            $fn

            if [ $? -ne 0 ]; then
                status=1
                eval $status_var="\"${install_fail[$i]}\""
                if [ $force -ne 1 ]; then
                    uninstall
                    break
                fi
            fi
        done
    fi

    report_install "$files_copied" "$symlinks_created" "$driver_registered"

    return $status
}

function uninstall
{
    process_params $*

    return 0;
}

command=$1
shift

case "$command" in
    install)
        install $*
        ;;
    verify)
        verify_config $*
        ;;
    --help)
        print_usage
        ;;
    *)
        echo "Unknown command given."
        print_usage
        ;;
esac

# if any problems, print out a final diagnostic about where to find failure reasons
# and exit with status of 1
if [ $? -ne 0 ]; then
    echo
    echo "See $log_file for more information about installation failures."
    exit 1
fi

echo
echo "Install log created at $log_file."
echo
echo "One or more steps may have an *. See README for more information regarding"
echo "these steps."

# return success
exit 0