Automation is Fun: Easily Open Tableau Workbooks in the Right Version!

Data

Automation is Fun: Easily Open Tableau Workbooks in the Right Version!

Hey there! Are you tired of having to manually open your Tableau workbooks in the correct version of Tableau? No? Is it just me? Well anyway, in today’s blog post, we’re going to talk about a neat Python script that saves me tons of time when working with different versions of Tableau.

Not only could this script make your life easier, but you’ll also get to practice your Python skills and learn a little more about what goes on behind the scenes in Tableau. But don’t worry, we won’t leave anyone out! Even if you’re not a Python or Tableau expert, you can still learn something useful.

Now, before we get started, we want to make it clear that this is a personal project of mine and not a commercial product of InterWorks. As such, we don’t support it or take any responsibility for any parts of the code. We also want to emphasize that this tool is only available for Windows users with default Tableau installations, sorry Mac and Linux folks!

So, what does this script do? Well, it’s pretty simple. When you double-click a Tableau workbook (.twb or .twbx), the script will automatically open the workbook in the correct version of Tableau, without you having to do a thing. If you don’t have the correct version of Tableau installed, the script will give you the option to either open the workbook in the next higher version, download the appropriate version from the internet, or cancel.

Sound too good to be true? Well, it’s not! You can download the complete executable version of the script at the end of the post, or read along to get into the details. We’ll walk you through every step of the way to write your own version of the script. So, let’s get started!

When Multiple Tableau Versions Become a Problem

When an organization uses Tableau actively, over time, Tableau workbooks created in different versions of the software can accumulate. As long as all server and desktop installations are updated simultaneously, upgrading workbooks to the new version of the software is not an issue.

However, in practice this is rarely the case. What happens if a user decides to play around with the newest version of Tableau and doesn’t realize that it became the default on their computer? They might inadvertently use features in their regular work that do not exist in the version of the department’s Tableau server. What if there are multiple Tableau servers running multiple versions in different departments of the organization? What if a user opens a workbook in an older version, where a certain feature used does not yet exist, not realizing it breaks a functionality somewhere in the workbook and re-uploads to the Tableau server? The best practice in such situations is to always open the workbooks in the appropriate version of Tableau, or if not possible, the earliest greater version. This helps to minimize the issues arising from missing features. At InterWorks, we work with different versions of Tableau all the time to match the version used in our clients’ environment. The picture below is an accurate representation of the desktop of an InterWorks Analytics Consultant:

Automation is Fun

While we put in all the effort to do our best work for our clients, we love to automate things to make our work more efficient. If we learn something in the process, even better! Besides having the tool save us save time when working with different Tableau versions, we can practice our Python skills and learn a bit about Tableau behind the scenes. Understanding all the content will require some understanding about both Python and Tableau, but there is something to learn for everyone here. Note that this blog is exclusively tackling Windows as a platform so the tool cannot be used on Mac or Linux platforms.

Let’s start with a plan of what we want to achieve:

  • When double-clicking a Tableau workbook (.twb or .twbx), if we have the appropriate version installed, open the workbook in the correct version without further ado.
  • If the exact version is not installed, we want to ask the user about what they want to do.  A) Open the workbook in the next greater version; B) Download the appropriate version from the internet; C) Cancel

Let’s Do It

1. Setting up our Python environment and passing the workbook to python

This blog is not meant to be a beginner’s guide to Python, so we will assume you know how to create your own environment or you can follow this excellent blog by (ADD link) . I am using an Anaconda environment with Python 3.9. Most of the packages we use are included in the standard python library. The only exceptions are BeautifulSoup and PyInstaller, which you can install easily by running:

pip install beautifulsoup4
pip install pyinstaller

Let’s start by importing all the packages we will be using:

import sys
import os
import linecache 
import re
from zipfile import ZipFile
import subprocess
import ctypes
from bs4 import BeautifulSoup
from urllib.request import Request, urlopen
import webbrowser

Since our ultimate aim is to use the script as a default program for .twb and .twbx files, we will be passing the complete path of the workbook as the first command line argument to the script. First, we check if we have the right number of attributes. Note that the argument at the 0 index will be the name of the script, while index 1 is the file path to the workbook. If we do not get two arguments, it means the script was used incorrectly, so let’s tell the user and terminate.

if len(sys.argv)==2:
  file_path = sys.argv[1]
else:
  ctypes.windll.user32.MessageBoxW(0, f"Please follow the instructions to associate this software with .twb and .twbx files.", "Please look at the instructions", 1)
  exit()

2. Detecting Workbook Version

Tableau uses two main filetypes for storing workbooks: .twb and .twbx files. Let’s start with the easier ones, .twb files. A .twb file is simply an xml file that contains all the information Tableau needs to connect to data and display the visualisations in the workbook, except for the data itself. If you were to open a .twb file in a text editor, it would start something like this:

<?xml version='1.0' encoding='utf-8' ?>

<!-- build 20223.22.1005.1835                               -->
<workbook locale='en_GB' source-build='2022.3.0 (20223.22.1005.1835)' source-platform='win' version='18.1' xmlns:user='http://www.tableausoftware.com/xml/user'>

You probably already spotted what we are looking for (Perhaps even three times?). Tableau stores the version of Tableau it was built in in the first few lines of the xml. “build 20223.xx.xxxx.xxxx” in line 3 refers to a build of the quarterly major version 2022.3. This is also confirmed in line 4 where the source-build attribute of the workbook tag is given as 2022.3.0. We can choose either of these to extract and parse. Note that we don’t care about the patch version (2022.3.0 vs 2022.3.1), as these get published fairly frequently and have feature parity with the quarterly version. Whichever version we have on our computer – ideally the latest one of course – will work just fine. In this case, we will extract and parse the third line of the xml. Linecache helps us extract the third line without parsing the whole file. As workbooks can get quite large, this is useful for performance. From the third line, we can use regex for extracting the “20223” string and add a “.” into the middle for future convenience.

if file_path.endswith("twb"):
  versionline=linecache.getline(file_path,3)
  m=re.match("[^a-zA-Z]*build (\d+)\..*",versionline)
  version=m.group(1) #version = "20223"
  version=version[:4]+"."+version[-1] #version="2022.3"

.twbx files are a bit more complicated. They are actually undercover zipfiles:

In every .twbx file, there is a .twb file, and we already know how to extract the version number from those. Ideally, we want to extract just the version number from the zip file without having to save any temporary files. Thankfully, the ZipFile package contains just what we need. First, let’s use a context manager to open the .twbx as a zip file. Since the name of the .twb inside the zip need not be the same as the .twbx (as in the example above), we need to find it using the namelist() method. Once we have, it we can open the .twb in a new context manager. The .twb file can be read sequentially without decompressing and loading the whole file. Given the setup, we need to use a slightly different method to finding the third line, such as below and we also need to decode it, as the zipfile.open() method returns lines as encoded bytes objects. From the “versionline” string, we can extract the version as in the case of the normal .twb file.

elif file_path.endswith("twbx"):
    with ZipFile(file_path,"r") as twbx:
       
        # Find name of .twb file
        namelist=twbx.namelist()
        for name in namelist:
            if name.endswith("twb"):
                file_name=name
    
        # Extract version line
        with twbx.open(file_name) as file:
            file.__next__()
            file.__next__()
            versionline=file.__next__().decode("UTF-8")
    m=re.match("[^a-zA-Z]*build (\d+)\..*",versionline)
    version=m.group(1) #version = "20223"
    version=version[:4]+"."+version[-1] #version="2022.3"

3. Detecting Installed Tableau Versions

To keep things simple, we will focus only on the default Tableau installation directory. The aim of our script here is to find a list of installed Tableau versions on the computer, while making sure there is at least one installation available. We also don’t want to accidentally detect Tableau Prep or Tableau Bridge as Tableau Desktop versions, so we need to make sure to exclude them. From there, we can ensure that the Tableau binary actually exists in any given version directory and then use regex to extract the version string.

# Check if Tableau is installed in default location
if "Tableau" not in os.listdir("C:\\Program Files"):
    ctypes.windll.user32.MessageBoxW(0,"Tableau is not installed","Tableau is not installed",1)
    exit()

# Find all installed versions 
versions = []
    for file in os.listdir("C:\\Program Files\\Tableau"):
        if file.find("Tableau") != -1 and file.find("Prep") == -1 and file.find("Bridge") == -1:
            m = re.match("Tableau (\d+\.\d)", file)
            try:
                ver=m.group(1)
                if os.path.exists(f"C:\\Program Files\\Tableau\\Tableau {ver}\\bin\\tableau.exe"):
                versions.append(ver)
            except:
                continue

4. Find the Matching Version

Now that we have our version from the workbook and the list of installed versions, we can look for a match and implement our logic. To be a bit more transparent here, we separated out some parts of the code into functions. The behaviour we expect here is what we set out to do at the beginning. If we have the correct version installed, let’s open the workbook with that version. If we don’t let’s try to find the nearest greater version. If there are any versions like this, ask the user if they would rather open the workbook in this version or download the matching version.

# Make sure the functions are defined before this chunk of code
if version in versions:
    open_file_with_tableau(version, file_path)
else:
    nearest_version = get_nearest_version(version, versions)
    if nearest_version:
        choice = prompt_user(version, nearest_version)
        if choice == 'open':
            open_file_with_tableau(nearest_version, file_path)
        elif choice == 'download':
            download_version(version)

Opening a file with any given version is very simple. We just need to create the command to run Tableau and then execute it. Using the subprocess library with the detached_process flag we can ensure that Tableau will not run as a child process of our python script, allowing our python script to terminate without closing Tableau.

def open_file_with_tableau(version, file_path):
    """Open the file with the matching version of Tableau"""
    DETACHED_PROCESS=0x00000008
    os.chdir(f"C:\\Program Files\\Tableau\\Tableau {version}\\bin")
    pid=subprocess.Popen(f'tableau.exe "{file_path}"',creationflags=DETACHED_PROCESS)

We can find the nearest version by simply sorting the versions we have and picking the next highest. Note that we are relying on alphabetical sorting here, but that coincides with what we need.

def get_nearest_version(version, versions):
    """Get the nearest version greater than the extracted version"""
    versions.sort()
    for ver in versions:
        if version < ver:
            return ver
    return None

Prompting the user is done through a ctypes messagebox:

def prompt_user(version, nearest_version):
    """Prompt the user to choose between opening the file with the nearest version or downloading the appropriate version"""
    messageBox = ctypes.windll.user32.MessageBoxW
    result = messageBox(0, f"Tableau version {version} not found, the earliest greater version found is {nearest_version}. Do you want to open the file with this version? Pressing 'No' will open the download page for the appropriate version. Press 'Cancel' to abort.", "Version not found", 3)
    if result == 6:
        return 'open'
    elif result == 7:
        return 'download'
    else:
        return None

Finally, if the user chooses to download the matching version we can find the newest patch version of that quarterly version using urllib, BeautifulSoup, and a bit more regex.

def download_version(version):
    # Open release page
    req = Request("https://www.tableau.com/support/releases")
    html_page = urlopen(req)
    soup = BeautifulSoup(html_page, "lxml")
    version_links = []
  
    # Find all version links
    for link in soup.findAll('a'):
        curr_link=str(link.get('href'))
        if curr_link.find(version)!=-1:
            version_links.append(curr_link)
    version_dict={}
    available_sub_versions=list()
  
    # Find all versions and add a .0 for the initial release
    for link in version_links:
        m=re.match(".*/([\d\.]+)$",link)
        sub_version=m.group(1)
        if version==sub_version:
            sub_version+=".0"
        available_sub_versions.append(int(sub_version[7:]))
        version_dict[sub_version]=link
      
    # If there were no available versions, it probably means the workbook is too old
    if len(version_links)==0:
        ctypes.windll.user32.MessageBoxW(0, f"This version is not supported by Tableau, please find a compatible version manually.", "Version too old", 1)
        return None
      
    # Open the link to the download page in a browser
    webbrowser.open(version_dict[version+"."+str(max(available_sub_versions))])

Bonus: Bundle the Script as a .exe File

We can use pyinstaller to create an executable for the script that we can associate with .twb and .twbx files in Windows. We can write a small batch script that creates the .exe with all dependencies packaged. Note that we save the script as a .pyw file, ensuring that we don’t get a console opening when we run the script.

pyinstaller --onefile --icon=IWblocks-black.png IW_Tableau_version_matching.pyw

And That’s It!

I hope you learned something from this blog and got you excited to think about automating your own workflows. I’ve been using it personally ever since I first finished it and has saved me quite a lot of time. Hope it does for you too!

Downloads and Instructions

DISCLAIMER: We want to make it clear that this is a personal project of mine and not a commercial product of InterWorks. As such, we don’t support it or take any responsibility for any parts of the code. We also want to emphasize that this tool is only available for Windows users with default Tableau installations, sorry Mac and Linux folks!

Source code: https://github.com/interworks/Tableau-version-matching

Releases: https://github.com/interworks/Tableau-version-matching/releases

To associate the script with .twb/.twbx files, please follow the gif (courtesy of our colleague Rowan Bradnum) below:

More About the Author

Zoltan Aldott

Analytics Consultant
Incorporating TabPy Into Tableau: InterUrban Demo If you haven’t read it yet, visit the previous blog, “TabPy Extension Use Case in Tableau: InterUrban Demo,” to see what ...
TabPy Extension Use Case in Tableau: InterUrban Demo In the second blog of this series, my colleague Alfonso wrote about a common business scenario where companies are struggling to make a ...

See more from this author →

InterWorks uses cookies to allow us to better understand how the site is used. By continuing to use this site, you consent to this policy. Review Policy OK

×

Interworks GmbH
Ratinger Straße 9
40213 Düsseldorf
Germany
Geschäftsführer: Mel Stephenson

Kontaktaufnahme: markus@interworks.eu
Telefon: +49 (0)211 5408 5301

Amtsgericht Düsseldorf HRB 79752
UstldNr: DE 313 353 072

×

Love our blog? You should see our emails. Sign up for our newsletter!