Back to Blog
Python OnrampPythonDeploymentPyInstallerTask SchedulerSolar EngineeringBeginner

Shipping Python Scripts for Solar: From VS Code to a Coworker's Desktop

How to run a Python solar script outside VS Code, package it for a coworker who doesn't have Python installed, and schedule it to run overnight — the three deployment paths every solar engineer needs.

Leaf Engineering
Leaf Automation
April 9, 2026
~40 min read

Shipping Python Scripts for Solar: From VS Code to a Coworker's Desktop

You have a Python script that works. F5 in VS Code, the CSV opens, panel positions print to the terminal. Now your project manager wants to run it on the warehouse report, your coworker on the estimating team wants a copy, and you'd like it to run automatically against whatever SolarEdge reports land in the inbox overnight. VS Code has no answer for any of those. The command line does, PyInstaller does, and Task Scheduler does.

By the end of this post you'll have three working deployment paths — PowerShell and a requirements.txt for teammates who can install Python, a standalone .exe via PyInstaller for teammates who can't, and a Task Scheduler job that runs your script overnight without anyone clicking anything. Plan for 40 minutes.

Why F5 in VS Code isn't a deliverable

F5 is fine while you're writing code. It's the fastest way to iterate on a script you're actively developing. But the moment you stop writing and start using the script as a tool, F5 is the wrong abstraction for three reasons:

  • It requires VS Code. Your coworker on the estimating team doesn't have VS Code, isn't going to install it, and shouldn't have to. An 800 MB development environment is not the right dependency for a script that reads a CSV.
  • It's manual. A script you have to open in an editor and press a key to run is a script you'll forget about. The batch of 30 SolarEdge reports that arrives every Friday afternoon deserves better than "open VS Code, navigate to the folder, press F5 for each one."
  • It hides the real entry point. When you run a script via F5, VS Code assembles the underlying command for you. The moment that command needs to change — different input file, different output directory, different Python version — you're guessing at what F5 was actually doing.

F5 is for building the script. The three paths below are for using it.

The script we'll ship

For concreteness we'll use a slightly modified version of the count_panels.py script from the first post in this series. If you skipped that one, the script reads a CSV of panel positions and prints a summary:

# count_panels.py — count panels in an AutoCAD DATAEXTRACTION CSV
import csv
import sys

csv_path = sys.argv[1] if len(sys.argv) > 1 else r"C:\temp\panels.csv"

panels = []
with open(csv_path, "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        panels.append({
            "id": row["panel_id"],
            "x": float(row["x"]),
            "y": float(row["y"]),
            "type": row["type"],
        })

print(f"Found {len(panels)} panels in {csv_path}")
xs = [p["x"] for p in panels]
ys = [p["y"] for p in panels]
print(f"Bounding box: x = {min(xs):.1f} to {max(xs):.1f}, y = {min(ys):.1f} to {max(ys):.1f}")

The only change from the original: sys.argv[1] lets you pass the CSV path as a command-line argument instead of hard-coding it. If you don't pass one, it falls back to C:\temp\panels.csv. This one-line change is what makes every shipping path below possible — without it, the script is pinned to a single hardcoded file forever.

Path 1: PowerShell and a requirements file

This is the lightest-weight shipping path. It assumes your coworker is willing to install Python once and follow a two-line README.

Running from PowerShell

Open PowerShell. Navigate to wherever count_panels.py lives:

cd C:\temp

Run it the normal way:

python count_panels.py

That's it. F5 in VS Code is a shortcut for that exact command with some debugger setup wrapped around it. You'll see the same "Found 6 panels" output you got in VS Code.

Pass a different CSV by tacking the path on the end:

python count_panels.py C:\projects\warehouse\panels.csv

If you get 'python' is not recognized as an internal or external command, the PATH checkbox from Step 1 of the install post wasn't checked. Fix Python's install before continuing — nothing else in this post will work until python runs from a fresh PowerShell window.

requirements.txt — telling pip what to install

count_panels.py uses only the standard library, so there's nothing to install. But the moment you write a script that uses pdfplumber or NumPy or scikit-learn, your coworker needs those libraries installed in their Python too. The way you tell them is a requirements.txt file.

Create a file next to count_panels.py called requirements.txt:

pdfplumber>=0.10.0
numpy>=1.24

List every third-party library your script imports, one per line, with a version pin. Your coworker runs:

pip install -r requirements.txt

pip reads the file and installs everything at once. This is how every Python project on GitHub ships dependencies. Write a requirements.txt for every script you distribute, even if the list is empty today.

Virtual environments: the short version

If you ever end up with two scripts that need different versions of the same library — one uses pdfplumber 0.9.0, the other needs 0.11.0 — you'll want virtual environments. A virtual environment is an isolated Python install with its own set of libraries, created next to your project. A full walkthrough belongs in its own post, but the minimum you need to know is:

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
python count_panels.py

Those four commands create an isolated environment in .venv\, switch your shell into it, install your dependencies, and run the script. Every library pip installs after activate goes into .venv\ instead of the system-wide Python. For a single-script deployment on a coworker's machine you can usually skip this. For anything more ambitious, don't.

A batch file shortcut

A PowerShell command your coworker has to remember is almost as bad as F5. Wrap it in a batch file so they can double-click it instead.

Create run_count_panels.bat in the same folder:

@echo off
cd /d "%~dp0"
python count_panels.py %*
pause

Three lines that matter:

  • cd /d "%~dp0" changes the working directory to wherever the .bat file lives, so the script runs relative to its own folder no matter where the user double-clicked from.
  • python count_panels.py %* runs the script with any command-line arguments passed to the batch file — useful for drag-and-drop, where Windows passes the dropped file path as the first argument.
  • pause keeps the window open at the end so the user can read the output before it disappears.

Drop run_count_panels.bat next to count_panels.py, zip the folder, email it to your coworker. They unzip, double-click the .bat, they see the output. The only install they need is Python — which the README mentions in one line.

Path 2: A standalone .exe with PyInstaller

Sometimes the coworker isn't going to install Python. They're on a locked-down IT image, or they work in accounting, or they just don't want to. For those cases you bundle the entire Python interpreter into a single executable.

Install PyInstaller

From PowerShell, in the folder that has count_panels.py:

pip install pyinstaller

PyInstaller takes a Python script and produces a .exe containing the script, the Python interpreter, and every library the script imports. The result runs on any Windows machine without Python installed.

Build the executable

pyinstaller --onefile count_panels.py

The --onefile flag packages everything into a single .exe instead of a folder of support files. This is the convenient option for distribution — one file, email-attachable.

PyInstaller creates three things in the current folder:

  • build\ — intermediate files. Ignore.
  • dist\count_panels.exe — the executable you ship. This is the file.
  • count_panels.spec — a build configuration file. Commit this to git if you're using version control, otherwise ignore.

Run it

.\dist\count_panels.exe C:\projects\warehouse\panels.csv

No Python required on the machine running it. The .exe is self-contained.

The three caveats no one mentions

PyInstaller executables are big. A hello-world script is typically 10–20 MB. A script that imports pdfplumber and NumPy can be 60–80 MB. This is normal — the entire Python interpreter plus every library you imported is inside the file. It's not a bug.

They start slowly. On first run, a --onefile executable unpacks itself to a temporary directory, which takes 2–5 seconds. Subsequent runs are faster but never instant. For a script that runs once and exits, this is fine. For anything the user expects to feel snappy, use --onedir (which produces a folder instead of a single file) and ship the folder.

Antivirus software will sometimes flag them. PyInstaller executables look suspicious to Windows Defender and commercial AV products because self-extracting executables are a common malware pattern. If your coworker's antivirus quarantines the file, the fix is to add an exception in their antivirus settings — or to sign the executable with a code-signing certificate, which is a separate rabbit hole and probably not worth it for internal tools.

Path 3: Task Scheduler for unattended runs

The third shipping path is for scripts that should run without anyone watching. The SolarEdge PDF inbox that fills up overnight. The weekly report that needs to be regenerated every Monday at 6am. The folder that needs to be cleaned up on the first of every month.

Windows Task Scheduler handles all of this. It's already installed. You don't need a third-party tool.

Create the task

Press the Windows key, type Task Scheduler, press Enter. In the right-hand pane, click Create Basic Task....

Walk through the wizard:

  1. NameCount Panels Nightly (or whatever you want).
  2. TriggerDaily, time of 2:00 AM.
  3. ActionStart a program.
  4. Program/scriptpython (just the word, not a path, assuming Python is on PATH).
  5. Add argumentscount_panels.py C:\reports\latest.csv
  6. Start inC:\temp\ (or wherever count_panels.py lives — this matters, see the next section).

Click Finish. The task is registered.

The "Start in" trap

The single most common Task Scheduler failure: the task runs, produces no output, and disappears. The cause is almost always that the Start in field was empty or wrong. When Start in is blank, Windows runs your program from C:\Windows\System32\, which means every relative path in your script is now relative to the System32 folder. Your script can't find count_panels.py, can't find panels.csv, and exits silently.

Always set Start in to the folder containing your script. Always use absolute paths inside the script for anything that matters — input files, output files, log files. Both together is belt-and-suspenders, and you want both.

Logging to a file

Task Scheduler doesn't show you what your script printed. If something goes wrong at 2am, you'll never see the error. Fix this by running the command through cmd.exe with output redirection:

  • Program/script: cmd.exe
  • Add arguments: /c python count_panels.py C:\reports\latest.csv > C:\logs\count_panels.log 2>&1
  • Start in: C:\temp\

The > C:\logs\count_panels.log 2>&1 part tells Windows to write all output — stdout and stderr — to that log file. After the first overnight run, open the log file to see what happened. When the job fails silently, the log is the first place you'll look.

Test the task before you trust it

Right-click the task in Task Scheduler and pick Run. This runs the task right now regardless of the schedule. Check that the output file got created and the log file has the expected content. If both look right, the scheduled run at 2am will work too.

If you skip this step, you'll find out at 8am Monday that the task has been failing silently for a week. Test it.

The errors you're going to hit

'python' is not recognized — PATH again, same as the install post. Either Python isn't installed, or the "Add to PATH" checkbox wasn't checked during install. Reinstall Python with the box checked.

ModuleNotFoundError: No module named 'pdfplumber' — The Python you're running from PowerShell isn't the same Python VS Code was using. If you installed pdfplumber via VS Code's integrated terminal but are now running from a system PowerShell window, they're different environments. Run pip install pdfplumber from the PowerShell window you're actually using, or activate the virtual environment the library is installed in.

pyinstaller: command not found after pip install pyinstaller — PATH issue, but for pip-installed tools. The script exists but its folder isn't on PATH. Run python -m PyInstaller --onefile count_panels.py instead — python -m runs a library as a script and bypasses the PATH problem.

PyInstaller .exe is 80 MB — Normal. See the caveats section. Nothing you can do about it without switching packaging tools, and the alternatives are all worse.

PyInstaller .exe runs on your machine but fails on your coworker's — Almost always a missing Visual C++ runtime or a different Windows version. Test on a Windows VM or a coworker's machine before declaring victory.

Task Scheduler says the task ran but nothing happenedStart in field was blank or wrong. See the trap section. Also check the log file if you set one up; if you didn't set one up, set one up.

Task Scheduler task runs when you test manually but not on schedule — Usually because the trigger is set to "only when user is logged on" and your user isn't logged on at 2am. In the task's General tab, check "Run whether user is logged on or not" and provide your password when prompted.

PermissionError: [Errno 13] Permission denied in a scheduled task — The task is running under a user account that doesn't have write access to the output folder. Either change the folder permissions or change the user the task runs as.

Where to go next

You now have three ways to get a Python script off your dev machine and into actual use. Pick the one that fits the situation:

  • A coworker who can install Python → Path 1 (PowerShell + requirements.txt + batch file shortcut)
  • A coworker who can't → Path 2 (PyInstaller .exe)
  • Unattended overnight processing → Path 3 (Task Scheduler)

What to read next, depending on what you're shipping:

  1. Parsing SolarEdge Designer PDFs for AutoCAD with Python and pdfplumber — the production PDF extractor. Once you have this working, it's the kind of script that makes sense to run on a schedule against an inbox folder. (~45 minutes)
  2. Stringing Solar Panels for AutoCAD with Python — the 200-line sweep clustering solver. Package it as a .exe and your non-coder teammates can string arrays without touching Python. (~45 minutes)
  3. K-Means for AutoCAD Solar Homerun Routing: When It Works, When It Fails — the homerun routing deep-dive. Same story: write once, ship once, run anywhere. (~30 minutes)

If shipping Python scripts to your team sounds like more work than you want to do, Branch runs inside AutoCAD as a drop-in plugin — no Python, no PyInstaller, no Task Scheduler. You install it once, your whole team gets access, and the only thing you ship is the finished drawing. We do the drafting. You do the engineering.

Start Free Trial — 14 days free