Introduction

If you’re delving into the world of Python programming, you might find yourself needing to convert your Python scripts into executables (.exe) files, especially for ease of distribution among Windows users. A popular tool for this task is PyInstaller, but there are a few key points and nuances to be aware of during this process.

Why Does Windows Defender Flag .exe Files as Viruses?

A common hiccup when creating .exe files from Python scripts is the reaction of Windows Defender, often flagging these files as potential viruses. This can be alarming, but it’s a known issue, primarily due to the way PyInstaller bundles the Python interpreter and libraries into a single executable file. This bundling often resembles the behaviour of a packed virus, hence triggering antivirus software. It’s important to understand that this is usually a false positive, especially if your script is for internal use. However, if you’re planning to distribute your .exe file, you might encounter trust issues with your users. It’s advisable to look into ways of certifying your software or using alternative distribution methods for a broader audience.

What is a .spec File?

To streamline the process of creating .exe files with PyInstaller, utilising a .spec file is highly recommended. This file saves the configuration settings for your build, simplifying future build commands and ensuring consistency across builds. A .spec file essentially records your build settings, such as hidden imports, binary inclusions, or other options, making it incredibly efficient for repeated use. This is particularly useful for complex scripts where the build process might require specific and consistent parameters.

Here’s an example of a .spec file so you can see the kind of options available to you within it where I’ve added comments to elaborate on the options I find myself using within it:

# -*- mode: python ; coding: utf-8 -*-

a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[], # To include required files/dependencies
    datas=[], # To include required 
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='main', # The file's output name, can still be renamed afterwards
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    version='versioninfo.txt', # Specify version info file for metadata
    icon='icon.ico', # You can set an icon file for the executable
)

If you’d like to learn more about this file type, read Pyinstaller’s documentation here.

The version file for Pyinstaller

For those interested, here’s an example of a versioninfo.txt file that I reference on line 36 of the above .spec file:

# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
  ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(1, 0, 0, 0),
prodvers=(1, 0, 0, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x4,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
  kids=[
StringFileInfo(
  [
  StringTable(
    u'040904B0',
    [StringStruct(u'CompanyName', u'jackwhitworth.com'),
    StringStruct(u'FileDescription', u'My Python File'),
    StringStruct(u'FileVersion', u'1.0.0.0'),
    StringStruct(u'InternalName', u'My Python File'),
    StringStruct(u'LegalCopyright', u'Copyright (c) jackwhitworth.com'),
    StringStruct(u'OriginalFilename', u'My_Python_File.exe'),
    StringStruct(u'ProductName', u'My Python File'),
    StringStruct(u'ProductVersion', u'1.0.0.0')])
  ]), 
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
  ]
)

Pyinstaller’s Operating System Limitations

It’s crucial to note that PyInstaller can only create an executable for the operating system it’s running on. This means you can’t generate a .exe file for Windows if you’re working on a Linux or macOS system. This limitation is due to the dependencies and the environment needed to bundle the Python interpreter and libraries correctly. If you need to create executables for different operating systems, you’ll need access to each of those systems to run PyInstaller separately.

Step-by-Step Guide to Using PyInstaller

  1. Installation: First, ensure you have PyInstaller installed.
    • You can do this via pip, Python’s package manager, with the command: pip install pyinstaller.
  2. Creating Your First Executable:
    • Navigate to your script’s directory in the command prompt.
    • Run pyinstaller yourscriptname.py. This command tells PyInstaller to generate the executable.
    • PyInstaller will create a dist folder where your .exe file will be located.
  3. Creating a .spec File:
    • The first time you run PyInstaller, it creates a .spec file in the same directory as your script.
    • You can edit this file to fine-tune your build process, such as adding hidden imports, specifying additional data files, and more.
    • For subsequent builds, simply run pyinstaller yourscriptname.spec.
  4. Testing Your Executable:
    • After building, test your .exe file thoroughly.
    • Check for any antivirus flags, and if it’s for internal use, you may choose to whitelist the file in your antivirus software.
  5. Troubleshooting Common Issues:
    • If you encounter errors or missing dependencies, refer back to the .spec file to make necessary adjustments.
    • Consult PyInstaller’s documentation for detailed guidance on specific errors.

Conclusion

PyInstaller is a robust tool for Python developers looking to distribute their applications more widely. Despite the challenges posed by antivirus software, it remains a valuable asset for Python to .exe conversion, especially when coupled with the use of .spec files for efficient builds. Always test your executables thoroughly and be aware of the limitations regarding cross-operating system builds.

By following these guidelines, you can confidently use PyInstaller to enhance your Python projects’ portability and accessibility.