How to Get MSSQL Server Download Links

What do you mean? The links to MSSQL Server installers are easy to find.

Yes, those links are out there:

SQL ServerLink
SQL Server 2017 Developer Editionhttps://go.microsoft.com/fwlink/?linkid=853016
SQL Server 2019 Developer Editionhttps://go.microsoft.com/fwlink/?linkid=866662
SQL Server 2022 Developer Editionhttps://go.microsoft.com/fwlink/?linkid=2215158

But the download is a small 5MB exe file. When you run it, it downloads a 1.5GB "proper" installer file.

This is a problem if you automate things, install SQL server on many machines, or look for the installer in a language different from English.

However, the EXE binary file contains links to 100 different installers, which can be extracted with a relatively simple Python script.

TL;DR

Stop this yada yada yada, I just want the links.

Follow the Google Colab Python notebook.

Or download the Python notebook and run it yourself.

Tell me how it works

Let's dive into the Python dojo with kata-kata sticks.

First, there is an AsciiTextExtractor. This helper class converts binary exe files to ASCII text and allows to search for content starting and ending with a specific text. This is used to search for XML opening and closing tags.

class AsciiTextExtractor:
    def __init__(self, content, beginTxt, endTxt):
        self.__beginTxt = beginTxt
        self.__endTxt = endTxt

        filteredContent = bytearray(
            filter(lambda x: x == 10 or (x >= 32 and x < 127), content)
        )
        self.__contentTxt = filteredContent.decode("ascii")

    def __iter__(self):
        self.__idxEnd = 0
        self.__idxBegin = 0
        return self

    def __next__(self):
        if self.__idxBegin == -1:
            raise StopIteration

        self.__idxBegin = self.__contentTxt.find(self.__beginTxt, self.__idxEnd)
        if self.__idxBegin == -1:
            raise StopIteration

        self.__idxEnd = self.__contentTxt.find(
            self.__endTxt, self.__idxBegin + len(self.__beginTxt)
        )
        if self.__idxEnd == -1:
            raise StopIteration

        return self.__contentTxt[self.__idxBegin : self.__idxEnd + len(self.__endTxt)]

MSSQL Installer exe file has many little XML resources embedded, one for each product and language.

<Manifest xmlns="http://schemas.datacontract.org/2004/07/InstallerEngine" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <DownloadFilesList>
    <ReliableFile>
      <DownloadRoot>https://download.microsoft.com/download/6/d/f/6df2ef5b-235c-4b38-89cb-d3d98797dbfc</DownloadRoot>
      <FileHash>e59b53b4e7a79d254bd15c6dc27c9d3bf97cd64c</FileHash>
      <FileName>SQLServer2022-DEV-x64-DEU.box</FileName>
    </ReliableFile>
  </DownloadFilesList>
</Manifest>

GetMsDownloadLinks function downloads MS SQL exe installer file (around 6 Mbytes) and searches for all those pesky XMLs.

Then, it parses each XML to extract download URLs and SHA1 hashes.

import requests
from xml.dom.minidom import parseString

def GetMsDownloadLinks(msInstallerExeUrl):
    print(f"Requesting {msInstallerExeUrl}")
    request = requests.get(msInstallerExeUrl, allow_redirects=True)
    print(f"Downloaded {request.url}")
    beginTxt = '<Manifest xmlns="http://schemas.datacontract.org/2004/07/InstallerEngine" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">'
    endTxt = "</Manifest>"
    for txt in AsciiTextExtractor(request.content, beginTxt, endTxt):
        xml = parseString(txt)
        reliableFiles = xml.getElementsByTagName("ReliableFile")
        for reliableFile in reliableFiles:
            downloadRoot = reliableFile.getElementsByTagName("DownloadRoot")
            fileName = reliableFile.getElementsByTagName("FileName")
            fileHash = reliableFile.getElementsByTagName("FileHash")
            if downloadRoot and fileName and fileHash:
                downloadUrl = f"{downloadRoot[0].firstChild.nodeValue}/{fileName[0].firstChild.nodeValue}"
                sha1Hash = f"{fileHash[0].firstChild.nodeValue}"
                yield (downloadUrl, sha1Hash)

GetMsSqlDownloadLinks is a wrapper around GetMsDownloadLinks. It knows the links to different SQL EXE bootstrappers, removes duplicates and sorts the results. It also allows filtering, e.g. for specific languages or editions.

import os

sqlExelinks = {
    2017: "https://go.microsoft.com/fwlink/?linkid=853016",  # SQL Server 2017
    2019: "https://go.microsoft.com/fwlink/?linkid=866662",  # SQL Server 2019
    2022: "https://go.microsoft.com/fwlink/?linkid=2215158", # SQL Server 2022
}

# supported versions: 2022, 2019, 2017
def GetMsSqlDownloadLinks(version=2022, searchFilter=""):
    sqlExeUrl = sqlExelinks[version]

    # add links to the set to get rid of duplicates
    msDownloadLinks = set(GetMsDownloadLinks(sqlExeUrl))
    # add links to the to list, to be able to sort
    sortedMsDownloadList = list(msDownloadLinks)
    # sort by filename (x[0] is url)
    sortedMsDownloadList.sort(key=lambda x: os.path.basename(x[0]))
    
    return filter(lambda x: searchFilter in x[0], sortedMsDownloadList)

Let's use all that heavy lifting to print links to the english versions of MSSQL Server 2022 installer.

for link, sha1 in GetMsSqlDownloadLinks(2022, "ENU"):
    print(f"{link} {sha1=}")

Even thought the exe bootstrapper is for the Developer edition of SQL Server, it contains the links to SQL Express, iso image and a few others.

Python Run Result

Vielen Dank, Python 🐍