Skip to main content

Overview

Platzi Viewer can be compiled into standalone Windows executables using PyInstaller. Two build scripts are provided:
  • Desktop Application (build_desktop_exe.ps1) - Single-file .exe with native window
  • Portable Bundle (build_portable_exe.ps1) - Directory-based bundle that opens in browser

Prerequisites

  • Windows 10/11
  • Python 3.8+ with pip
  • PowerShell 5.1 or higher
  • PyInstaller 6.2.0+

Installation

1

Clone the repository

git clone <repository-url>
cd platzi-viewer
2

Create virtual environment

python -m venv .venv
.venv\Scripts\Activate.ps1
3

Install dependencies

pip install -r requirements.txt
pip install pyinstaller
For desktop build, also install:
pip install PyQt6 PyQt6-WebEngine
# OR
pip install pywebview
4

Place credentials file

Copy your Google Service Account credentials:
cp \path\to\service_account.json .

Build Types

Single-file executable with native window - Best user experience, no browser required.
1

Run build script

.\build_desktop_exe.ps1
2

Locate executable

Output: dist/PlatziViewerDesktop.exe
3

Run the application

.\dist\PlatziViewerDesktop.exe
Opens in a native desktop window (PyQt6/pywebview).

Portable Bundle

Directory-based bundle with separate executable - Opens in default browser, includes all resources.
1

Run build script

.\build_portable_exe.ps1
2

Locate bundle

Output: dist/PlatziViewer/ directory
3

Run the application

.\dist\PlatziViewer\PlatziViewer.exe
Opens in your default web browser.

Build Scripts

Desktop EXE Script (build_desktop_exe.ps1)

Features:
  • Single-file executable (--onefile)
  • Windowed mode (--windowed) - No console window
  • Embeds all resources using --add-data
  • Auto-detects UI backend (pywebview or PyQt6)
  • Optional code signing support
Key Configuration (build_desktop_exe.ps1:51-70):
& $python -m PyInstaller `
  --noconfirm `
  --clean `
  --onefile `
  --windowed `
  --name "PlatziViewerDesktop" `
  --icon "favicon.ico" `
  --add-data "index.html;." `
  --add-data "css;css" `
  --add-data "js;js" `
  --add-data "favicon.ico;." `
  --add-data "courses_cache.json;." `
  --hidden-import "drive_service" `
  --hidden-import "google.oauth2.service_account" `
  desktop_app.py

Portable EXE Script (build_portable_exe.ps1)

Features:
  • Directory-based bundle (--onedir)
  • Console-visible for debugging
  • Copies resources post-build
  • Includes data files (cache, progress)
  • Embeds service account if present
Key Configuration (build_portable_exe.ps1:40-51):
& $python -m PyInstaller `
  --noconfirm `
  --clean `
  --onedir `
  --name "PlatziViewer" `
  --icon "favicon.ico" `
  --hidden-import "drive_service" `
  --hidden-import "google.oauth2.service_account" `
  app_launcher.py

Build Process

Both scripts follow a similar workflow:
1

Dependency validation

Verifies required Python packages are installed:
  • requests
  • google.oauth2.service_account
  • googleapiclient.discovery
  • google.auth.transport.requests
For desktop build:
  • webview (pywebview) OR
  • PyQt6.QtWebEngineWidgets (PyQt6)
2

Clean previous builds

# Removes:
Remove-Item "build" -Recurse -Force
Remove-Item "dist/PlatziViewer*" -Recurse -Force
Remove-Item "*.spec" -Force
3

Run PyInstaller

Compiles Python code and dependencies into executable.
4

Copy resources (portable only)

Copy-Item "index.html" $distDir
Copy-Item "css" $distDir -Recurse
Copy-Item "js" $distDir -Recurse
Copy-Item "favicon.ico" $distDir
Copy-Item "courses_cache.json" $distDir
Copy-Item "service_account.json" $distDir  # If exists
5

Code signing (desktop only, optional)

If environment variables are set:
$env:PLATZI_SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\bin\...\signtool.exe"
$env:PLATZI_CERT_PATH = "C:\secure\cert.pfx"
$env:PLATZI_CERT_PASSWORD = "password"
Automatically signs with:
signtool sign /f $certPath /p $password `
  /tr "http://timestamp.digicert.com" `
  /td SHA256 /fd SHA256 `
  "dist/PlatziViewerDesktop.exe"

PyInstaller Configuration

Hidden Imports

Both scripts specify hidden imports to ensure all dependencies are included:
--hidden-import "drive_service"
--hidden-import "google.oauth2.service_account"
--hidden-import "googleapiclient.discovery"
--hidden-import "google.auth.transport.requests"
--hidden-import "requests"
Desktop build adds UI backend imports:
# For pywebview
--hidden-import "webview.platforms.edgechromium"
--hidden-import "webview.platforms.winforms"
--hidden-import "webview.platforms.cef"

# For PyQt6
--hidden-import "PyQt6.QtWebEngineWidgets"
--hidden-import "PyQt6.QtWebEngineCore"

Data Files (Desktop Build)

Resources are embedded using --add-data:
--add-data "index.html;."      # HTML template
--add-data "css;css"            # Stylesheets
--add-data "js;js"              # JavaScript
--add-data "favicon.ico;."     # Window icon
--add-data "courses_cache.json;."  # Course data
Optional:
--add-data "service_account.json;."  # Credentials (if exists)

Distribution

Desktop Application

Single file to distribute:
dist/PlatziViewerDesktop.exe  (~50-150 MB)
User requirements:
  • Windows 10/11 (64-bit)
  • No Python installation needed
  • No browser required (uses native window)
First-run setup:
  1. Run PlatziViewerDesktop.exe
  2. Creates PlatziData/ directory alongside executable
  3. Looks for service_account.json in same directory
Directory structure after first run:
C:\Users\Username\Documents\
  ├── PlatziViewerDesktop.exe
  ├── service_account.json        # User provides
  └── PlatziData/
      ├── progress.json
      ├── courses_cache.json
      └── QtWebEngine/            # Browser cache (PyQt6)

Portable Bundle

Entire directory to distribute:
dist/PlatziViewer/  (~40-100 MB)
  ├── PlatziViewer.exe           # Main executable
  ├── index.html
  ├── favicon.ico
  ├── favicon.svg
  ├── css/
  ├── js/
  ├── service_account.json       # Included if present during build
  ├── courses_cache.json
  ├── _internal/                 # PyInstaller runtime
  └── ... (DLLs, Python runtime)
User requirements:
  • Windows 10/11 (64-bit)
  • No Python installation needed
  • Modern web browser (Chrome, Edge, Firefox)
Usage:
  1. Extract entire PlatziViewer/ directory
  2. Run PlatziViewer.exe
  3. Opens in default browser

Code Signing (Optional)

The desktop build script supports automatic code signing to avoid Windows SmartScreen warnings.

Setup Certificate

1

Obtain code signing certificate

Purchase from a trusted CA (DigiCert, Sectigo, etc.) or use self-signed for testing.
2

Set environment variables

$env:PLATZI_SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
$env:PLATZI_CERT_PATH = "C:\secure\mycert.pfx"
$env:PLATZI_CERT_PASSWORD = "your-password"
Or permanently:
[System.Environment]::SetEnvironmentVariable('PLATZI_SIGNTOOL_PATH', 'C:\Path\...', 'User')
3

Build with signing

.\build_desktop_exe.ps1
Output includes:
[OK] Ejecutable firmado: dist/PlatziViewerDesktop.exe

Signing Command (build_desktop_exe.ps1:104-108)

signtool sign `
  /f $certPath `
  /p $certPassword `
  /tr "http://timestamp.digicert.com" `
  /td SHA256 `
  /fd SHA256 `
  "dist/PlatziViewerDesktop.exe"

Troubleshooting

Build Fails: Missing Dependencies

Faltan dependencias Python para el build: requests, google.oauth2.service_account
Solution:
pip install -r requirements.txt

Build Fails: Missing UI Backend (Desktop)

Falta backend de UI para desktop_app.py. Instala pywebview o PyQt6 + PyQt6-WebEngine.
Solution:
pip install PyQt6 PyQt6-WebEngine
# OR
pip install pywebview

Executable Won’t Start

Windows SmartScreen:
  • Click “More info” → “Run anyway”
  • OR sign the executable (see Code Signing)
Missing DLLs:
  • Ensure all files in dist/PlatziViewer/_internal/ are present (portable)
  • Try running from command line to see error messages

Service Account Not Found

Desktop EXE:
ERROR: No se encontró service_account.json
Place service_account.json in same directory as .exe:
C:\Apps\PlatziViewerDesktop.exe
C:\Apps\service_account.json
Portable EXE: Rerun build with service_account.json present:
cp \secure\service_account.json .
.\build_portable_exe.ps1

Large Executable Size

Desktop EXE (~150 MB):
  • Includes Python runtime + PyQt6/pywebview
  • Normal for single-file bundles
Reduce size:
  • Use portable bundle instead (distributes size across multiple files)
  • Use pywebview instead of PyQt6 (smaller)
  • Exclude unnecessary files with --exclude-module

Build Script Permission Denied

# Enable script execution
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Clean Build Issues

Force clean all build artifacts:
Remove-Item -Recurse -Force build, dist, *.spec
.\build_desktop_exe.ps1

Advanced Configuration

Custom Icon

Replace favicon.ico before building:
cp custom-icon.ico favicon.ico
.\build_desktop_exe.ps1

Exclude Development Files

Modify PyInstaller command to exclude:
--exclude-module pytest `
--exclude-module black `
--exclude-module mypy

Add Custom Data Files

Desktop build:
--add-data "custom_config.json;."
Portable build:
Copy-Item "custom_config.json" $distDir

UPX Compression (Reduce Size)

Install UPX and enable compression:
# Download UPX from https://upx.github.io/
# Add to PATH, then:
--upx-dir "C:\upx"

CI/CD Integration

GitHub Actions Example

name: Build Windows Executable

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pyinstaller PyQt6 PyQt6-WebEngine
      
      - name: Build executable
        run: .\build_desktop_exe.ps1
      
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: PlatziViewerDesktop
          path: dist/PlatziViewerDesktop.exe

Comparison

FeatureDesktop EXEPortable Bundle
File StructureSingle .exeDirectory with multiple files
Size~50-150 MB~40-100 MB
Startup TimeFast (2-3s)Medium (3-5s)
User InterfaceNative windowBrowser window
Distribution1 fileZip directory
UpdatesReplace 1 fileReplace directory
Debug ConsoleHiddenVisible (shows logs)
CredentialsExternal fileCan be embedded
Code SigningSupportedNot implemented

Best Practices

Security

  • Never commit built executables to version control
  • Store credentials separately or encrypt
  • Sign executables for production distribution
  • Use environment variables for sensitive build options

Testing

  • Test on clean Windows installation
  • Verify all resources load correctly
  • Check antivirus compatibility
  • Test without Python installed

Distribution

  • Compress with 7-Zip or zip for smaller downloads
  • Provide SHA256 checksums
  • Include README with setup instructions
  • Host on GitHub Releases or CDN

Maintenance

  • Keep build scripts in version control
  • Document build environment requirements
  • Test builds on multiple Windows versions
  • Maintain changelog for each build

Next Steps

Desktop Application

Learn about running from source

Docker Deployment

Deploy in containerized environment

Build docs developers (and LLMs) love