Skip to main content

Using inno to package your Python application

So you have just compiled your Windows Python application. You want to make an installer for your users to easily get the application onto their PCs. You have tried using cx_Freeze's bdist_msi option but it is not advanced enough. Perhaps you want to run a script on installation or on uninstall, or want an easy way of adding an icon to the user's desktop.

Now the easiest tool to use is inno. In this post we're going to take a look at how to use inno's setup wizard to package your application, as well as editing the script file.

So the first thing you are going to want to do is to jump onto inno's download page and download inno setup compiler. Once it's done, open the application and select 'Create a new script file using the Script Wizard'. You will then be greeted with the following window:


Leave the box unchecked. We're going to use the Wizard to make the base of our install script.


Fill out all the information in the entry boxes. The application version will be very useful later when you want to update your application.


Here you can change where the default install location will be. You can change this with the dropdown box. You can also change the name of this folder, or disable asking the user for a different install location. It's probably a good idea to leave this unchecked, so the user can select their (x86) folder if they want to.


Here you need to select ALL of the files and folders cx_Freeze made in your folder. Remember to include the lib folder as well as the .dlls cx_Freeze made.


Here you can select the shortcuts to ask the user to make on installation. When the user is installing, the make desktop shortcut checkbox is automatically unchecked. We can change this in the script.


Here you can select a license file to show to the user on installation. The installer won't let the user continue the installation until they accept the terms of the install. You can skip this if you don't have a license file.


Here you can select all of the languages that are available to use in the installer. The installer will ask the user for the language they want to use on install. It's probably a good idea to only select the languages that your application supports.


You can change the place where inno will place the finished installer and the name of the installer here. If you want to, you can change the installer's icon. I'd prefer to keep it as the default one so it's easy to see that the file is an installer. If you want to do this, leave this field blank. You can also setup a password if you want to.


I'd leave this boxed checked. It basically sets a bunch of constants in the setup script to use as variables so you only need to change one thing.

The base setup script has been made. If it asks you if you want to compile the script now, select 'No' because there's still a few things that need changing.


We are going to change the line I highlighted to the following:

 AppVerName={#MyAppName}  

This changes the name of the application in the uninstall applications window to the name of the file without the version number. It is pointless to do this because the version number is shown anyway. The result of this will become clear later.

At the bottom of the [Setup] section you can add an icon to show in the Uninstall application window using UninstallDisplayIcon=. I will demonstrate this later. For example, mine was

 UninstallDisplayIcon=C:\Users\Edward\Documents\workingdir\eehph2\build\exe.win-amd64-3.6\Assets\icon.ico  

Delete 'Flags: unchecked' here to make the 'make desktop checkbox option' checked by default.



Next:


We are going to change these lines to:

 Source: "C:\Users\Edward\Documents\workingdir\eehph2\build\exe.win-amd64-3.6\Assets\*"; DestDir: "{app}\Assets"; Flags: ignoreversion recursesubdirs createallsubdirs  
 Source: "C:\Users\Edward\Documents\workingdir\eehph2\build\exe.win-amd64-3.6\lib\*"; DestDir: "{app}\lib"; Flags: ignoreversion recursesubdirs createallsubdirs  
 Source: "C:\Users\Edward\Documents\workingdir\eehph2\build\exe.win-amd64-3.6\tcl\*"; DestDir: "{app}\tcl"; Flags: ignoreversion recursesubdirs createallsubdirs  
 Source: "C:\Users\Edward\Documents\workingdir\eehph2\build\exe.win-amd64-3.6\tk\*"; DestDir: "{app}\tk"; Flags: ignoreversion recursesubdirs createallsubdirs  

Don't copy and paste this in because your files will be different. What you need to do is add a backslash and the name of the folder to all of the folders you selected to include in the DestDir. If you don't do this, inno will place all of the files loose in the installation folder. Python isn't expecting the application to be used in this way, so your program won't work. For example:

 DestDir: "{app}";  

Would become 

 DestDir: "{app}\lib";  

For the lib folder.

If you want to run a script on installation or on uninstall, add to [Run] and add the [UninstallRun] sections as appropriate. For example I made a script that takes the installation location and saves it in a constant location in the AppData folder. This file is then removed by another script on uninstall. This script needs to know the install location, so it is taken as an argument. These scripts are shown at the end. If your argument is a path, it is very important to escape the string otherwise the spaces or backslashes would mess up the path. This is what mine looked like:

 [Run]  
 Filename: "{app}\on_install_setup.exe"; Parameters: """{app}\"  
 Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent  
 [UninstallRun]  
 Filename: "{app}\cleanup.exe";  

When you are done, press the green go button.


Be aware that the application will automatically install on your PC after it's finished compiling.

You can go to the uninstall application by going to Tools > Add/Remove Programs:


Here you can see how we changed the script earlier helped the program:


Changing AppVerName={#MyAppName} made it so that the version number isn't shown in the Name column, as it is in AutoHotkey and GIMP. Showing the version number is pointless because the version is in the Version column. We can also see that there is an icon there. Without this, it would have the automatic icon like Microsoft Visual C++ does:


Lets have a look at how inno is different to cx_Freeze's bdist_msi:


Here's what cx_Freeze's bdist_msi looks like. You can change the install location... but that's about it. You can add a desktop icon, but the user cannot disable this. There is no way of adding a Start Menu shortcut. Here is the same screen in inno:


I'll let you decide which looks better. Inno also has the following screens:


Licence agreement that won't let you continue until you accept the terms


Option to create a desktop shortcut



It's possible to create a start menu shortcut,

Not to mention the most powerful advantage in my opinion, the setup and uninstall scripts.

To finish off, we're going to look at how to create setup scripts.

Here's the source code for the install script:

1:  import sys  
2:  import os  
3:    
4:  location = sys.argv[1]  
5:    
6:  appdata = os.path.join(os.environ['ALLUSERSPROFILE'], "EEHPH2")  
7:    
8:  if not os.path.exists(appdata):  
9:    os.mkdir(appdata)  
10:    
11:  file = open(os.path.join(appdata, "location.txt"), "w")  
12:  file.write(location)  
13:  file.close()  

It uses sys.argv to get the argument that is inserted into it to the script in the inno script. This is explained in this blog post. It writes the install location onto %ALLUSERSPROFILE%\EEHPH2\location.txt. This is so my application knows where the install location is. This is done because when a user clicks on a file and selects open with my app, the current working directory (cwd) is set to the one in which the file is located, not the application folder. This means the .dlls that the program needs to work are not present, so the program does not work. My program calls a small .exe which changes the cwd to the install location (as is known thanks to this file) and runs the main application as a module. After the user closes, it switches back to the original. I have another script that deletes this file when the user uninstalls:

1:  import shutil  
2:  import os  
3:    
4:  try:  
5:    shutil.rmtree(os.path.join(os.environ["ALLUSERSPROFILE"], "EEHPH2"))  
6:  except:  
7:    pass  

Both of these scripts were compiled using pyinstaller:

 pyinstaller cleanup.py --icon icon.ico --onefile  

The other script was done in the same way.

This post was pretty long; so thanks for getting to this point. If this helped you, please leave a comment below.

Comments

Popular posts from this blog

FPS counter with OpenCV

Showing FPS on the screen is an important part of benchmarking your app. In this post we're going to look at how to calculate the number of frames per second and show it on screen. The theory is that we keep track of the last 5 or so timestamps and average over to get the FPS. We keep deleting the last item so the list only has 5 times in it. Lets have a look at the code: 1: import time 2: 3: times = [] 4: count = 5 5: 6: times.append(time.time()) 7: if len(times) >= count: 8: times = times[-count:] 9: fps = "%.2f FPS" % (count/(times[-1] - times[0])) 10: cv2.putText(frame, fps, (0, 20), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1, cv2.LINE_AA) First import time, which is required for this to work. Then I define two constants which will be used in the function. Remember this code will run every time the videoloop runs, it adds the time to the list. If the list gets too long, get the last bit using indexing. Then work out the av

Getting the icon association for any file or folder with the Python Windows API

I wanted to make a file browser which has the correct file icons for each file type, instead of just getting them from the file name. This is a bad solution, since it has to call images from disc and may be wrong if the name is different. This method uses the Windows API to get the exact icon. Tested in Python 3.6 in Windows 10. from win32com.shell import shell, shellcon from PIL import Image, ImageTk import win32api import win32con import win32ui import win32gui def get_icon(PATH, size): SHGFI_ICON = 0x000000100 SHGFI_ICONLOCATION = 0x000001000 if size == "small": SHIL_SIZE= 0x00001 elif size == "large": SHIL_SIZE= 0x00002 else: raise TypeError("Invalid argument for 'size'. Must be equal to 'small' or 'large'") ret, info = shell.SHGetFileInfo(PATH, 0, SHGFI_ICONLOCATION | SHGFI_ICON | SHIL_SIZE) hIcon, iIcon, dwAttr, name, typeName = info ico_x =