I want to share something I find really cool (if you’re not into software, it might seem boring.) It’s an installer system for Windows called NSIS. Why is it cool? As a long-time software engineer, one big challenge I’ve seen is distributing software that actually runs reliably on clients computers.
Consider the hurdles:
- 32-bit vs. 64-bit
- OS versions
- Dependencies
- Runtime libraries
If a program runs in an incompatible environment, it simply won’t work. While the web bypasses many of these issues, it’s not always an option. Web apps are limited: they can’t do things like handle raw sockets. For those cases, you need a native app, and you’re back to handling the same challenges.
NSIS is extremely under-rated
Intro to NSI script
This is what a “hello, world” looks like in NSI. You define a name for the output binary. The main section is like the C main function. The language has special keywords for some functions. MessageBox shows an alert window when you run the installer.
; Use the ANSI compiler Outfile "output.exe" ; Main installer program. Section "MainSection" MessageBox MB_OK "Hello, world!" SectionEnd
Variables
NSI scripts have global variables (declared at the top), in-built numbered variables (similar to the registers in assembly), and a stack (stacks are useful for algorithms.)
; Use the ANSI compiler Outfile "output.exe" Var /GLOBAL SysDrive ; Main installer program. Section "MainSection" ; Init global var. StrCpy $SysDrive "C:" ; Param no variables. StrCpy $0 "test" StrCpy $1 "something" ; Basic stack manipulation. Push "Current top of stack" Push "New top of the stack" Pop $2 ; Take ^ this off the stack. MessageBox MB_OK $2 SectionEnd
Logic
The way logic is done in NSI uses function calls that do comparisons and then executes certain named branches of code. This is convoluted, and a much nicer way to do this is to use a special logic module that provides high-level macros. This allows for more familiar boolean comparisons.
!include "LogicLib.nsh" ; Use the ANSI compiler Outfile "output.exe" ; Main installer program. Section "MainSection" ; Show if. StrCpy $0 = "test" ${If} $0 == "test" MessageBox MB_OK "0 is test" ${EndIf} ; Show if else and basic numerical logic. StrCpy $0 = 10 ${If} $0 > 10 MessageBox MB_OK "0 > 10" ${Else} MessageBox MB_OK "0 is <= 10" ${EndIf} ; Show what a loop looks like. StrCpy $0 0 loop: ${If} $0 >= 10 Goto end_loop ${EndIf} IntOp $0 $0 + 1 Goto loop end_loop: MessageBox MB_OK "left loop" SectionEnd
Functions
In NSI you don’t declare parameters for functions. Since all variables are global you simply set them before the function call and the function takes what it needs directly. Rust users will be shaking! Don’t die though because it simplifies a lot of things. Look how clean the function looks without a prototype. Strange.
; Use the ANSI compiler Outfile "output.exe" Function Hello MessageBox MB_OK $0 FunctionEnd ; Main installer program. Section "MainSection" StrCpy $0 "Hello, world!" Call Hello SectionEnd
Plugins
To use plugins in NSI you need to open the install location for NSI and find the plugins folder (x86 program files/NSIS/Plugins.) There is a sub-folder for ANSI and unicode. Usually plugins provide a specific build for ASCI and unicode — though when they don’t I copy the plugin to both folders.
The code needs the inetc plugin. It can be downloaded from here: https://nsis.sourceforge.io/Inetc_plug-in
; Use the ANSI compiler Outfile "output.exe" ; Main installer program. Section "MainSection" StrCpy $0 "https://www.python.org/ftp/python/3.13.1/python-3.13.1-amd64.exe" StrCpy $1 "python.exe" inetc::get $0 $1 /END ExecWait '"$1"' SectionEnd
Look at how succinct and powerful this code is. In 4 lines of code we have downloaded a file and executed it. In a way that will run on every version of Windows. That’s impressive.
The big idea
A polymorphic installer
Instead of a fixed module installer I could simply get the module name from the file name. I developed the idea even further.
The installer would now use its own icon as the launcher icon for the module. And it was possible to change the URL mirror path for the binary files by editing the file description. What resulted was a reusable, Python 3 module installer that could be customized simply by editing a file name.
Here’s a link to the Github for that: https://github.com/robertsdotpm/win-auto-py3
And my NSI script code: https://github.com/robertsdotpm/win-auto-py3/blob/main/installer.nsi
Here’s the EXE (or build it yourself from the repo): https://github.com/robertsdotpm/win-auto-py3/raw/refs/heads/main/install_1NSERT-PYPI-PKG-HERE.exe
Closing thoughts
IMO, this demonstrates the potential of NSI for new distribution and packaging systems. I hope you found this interesting. The crazy thing is this only took a couple of days to build.
Also, somewhat humorously: theoretically my installer would solve the issue for that guy screaming for an EXE on Github. But in practice sherlock project is a bad example to test the tool with because it uses many modules that use C-extensions. When you mix native code with Python it almost always breaks installations (which kind of defeats the point of Python.)
Leave a Reply