Skip to content

Auto-run after installation and Auto-run after Windows login #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.pyc
__pycache__
.idea/
build/
MANIFEST
dist/
Expand Down
28 changes: 28 additions & 0 deletions examples/run_auto/guessnumber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""A fun number guessing game!"""

import random

def main():
number = random.randint(1, 100)
guesses = 0

print("I'm thinking of a number, between 1 and 100. Can you guess what it is?")
while True:
guesses += 1
guess = input('= ')
try:
guess = int(guess)
except ValueError:
print("Base 10 integers only, please.")
continue

if guess > number:
print("Too high!")
elif guess < number:
print("Too low!")
else:
print("That's right, {}. You got it in {} guesses.".format(number, guesses))
break

print()
input("Press enter to quit.")
21 changes: 21 additions & 0 deletions examples/run_auto/installer.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Application]
name=Guess the Number
version=1.0
entry_point=guessnumber:main
# We need to set this to get a console:
console=true
run_after_install=true
run_on_windows_start=true

[Python]
version=3.6.3
bitness=64
format=bundled

[Include]
packages=guessnumber

# This optional section adds a command which can be run from the Windows
# command prompt.
[Command guessnumber]
entry_point=guessnumber:main
27 changes: 20 additions & 7 deletions nsist/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,13 @@ class InstallerBuilder(object):
:param str nsi_template: Path to a template NSI file to use
"""
def __init__(self, appname, version, shortcuts, *, publisher=None,
icon=DEFAULT_ICON, packages=None, extra_files=None,
py_version=DEFAULT_PY_VERSION, py_bitness=DEFAULT_BITNESS,
py_format='bundled', inc_msvcrt=True, build_dir=DEFAULT_BUILD_DIR,
installer_name=None, nsi_template=None,
exclude=None, pypi_wheel_reqs=None, extra_wheel_sources=None,
local_wheels=None, commands=None, license_file=None):
icon=DEFAULT_ICON, packages=None, extra_files=None,
py_version=DEFAULT_PY_VERSION, py_bitness=DEFAULT_BITNESS,
py_format='bundled', inc_msvcrt=True, build_dir=DEFAULT_BUILD_DIR,
installer_name=None, nsi_template=None,
exclude=None, pypi_wheel_reqs=None, extra_wheel_sources=None,
local_wheels=None, commands=None, license_file=None,
run_after_install=False, run_on_windows_start=False):
self.appname = appname
self.version = version
self.publisher = publisher
Expand All @@ -123,6 +124,8 @@ def __init__(self, appname, version, shortcuts, *, publisher=None,
self.local_wheels = local_wheels or []
self.commands = commands or {}
self.license_file = license_file
self.run_after_install = run_after_install
self.run_on_windows_start = run_on_windows_start

# Python options
self.py_version = py_version
Expand Down Expand Up @@ -314,7 +317,7 @@ def prepare_shortcuts(self):
else:
shutil.copy2(sc['script'], self.build_dir)

target = '$INSTDIR\\Python\\python{}.exe'
target = r'$INSTDIR\Python\python{}.exe'
sc['target'] = target.format('' if sc['console'] else 'w')
sc['parameters'] = '"%s"' % ntpath.join('$INSTDIR', sc['script'])
files.add(os.path.basename(sc['script']))
Expand Down Expand Up @@ -460,6 +463,16 @@ def run_nsis(self):
print("http://nsis.sourceforge.net/Download")
return 1

nsis_dir = os.path.dirname(makensis)
nsis_inc_nsh = os.path.abspath(os.path.join(nsis_dir, "include", "nsProcess.nsh"))
nsis_plg_dll = os.path.abspath(os.path.join(nsis_dir, "Plugins", "x86-ansi", "nsProcess.dll"))
nsis_plg_dllw = os.path.abspath(os.path.join(nsis_dir, "Plugins", "x86-unicode", "nsProcess.dll"))

if not os.path.exists(nsis_inc_nsh) or not os.path.exists(nsis_plg_dll) or not os.path.exists(nsis_plg_dllw):
print("NsProcess plugin was not found. Install NsProcess plugin and try again.")
print("https://nsis.sourceforge.io/NsProcess_plugin")
return 2

logger.info('\n~~~ Running makensis ~~~')
return call([makensis, self.nsi_file])

Expand Down
4 changes: 4 additions & 0 deletions nsist/configreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def _check_invalid_keys(self, section_name, section):
('console', False),
('extra_preamble', False),
('license_file', False),
('run_after_install', False),
('run_on_windows_start', False),
]),
'Build': SectionValidator([
('directory', False),
Expand Down Expand Up @@ -239,4 +241,6 @@ def get_installer_builder_args(config):
args['nsi_template'] = config.get('Build', 'nsi_template', fallback=None)
args['exclude'] = config.get('Include', 'exclude', fallback='').strip().splitlines()
args['local_wheels'] = config.get('Include', 'local_wheels', fallback='').strip().splitlines()
args['run_after_install'] = appcfg.get('run_after_install', None)
args['run_on_windows_start'] = appcfg.get('run_on_windows_start', None)
return args
65 changes: 65 additions & 0 deletions nsist/pyapp.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,32 @@ SetCompressor lzma
!define MUI_ICON "[[icon]]"
!define MUI_UNICON "[[icon]]"

[% if ib.run_after_install is not none %]
; Run an application shortcut after an install
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Start a shortcut"
!define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink"
[% endif %]

!include "nsProcess.nsh"
!define APP_EXE "python.exe"
!define AppName "${PRODUCT_NAME}"

!macro KillProcess
${nsProcess::FindProcess} "${APP_EXE}" $R0

${If} $R0 == 0
DetailPrint "${AppName} is running. Closing it down"
${nsProcess::KillProcess} "${APP_EXE}" $R0
DetailPrint "Waiting for ${AppName} to close"
Sleep 2000
${Else}
DetailPrint "${APP_EXE} was not found to be running"
${EndIf}

${nsProcess::Unload}
!macroend

; UI pages
[% block ui_pages %]
!insertmacro MUI_PAGE_WELCOME
Expand Down Expand Up @@ -151,6 +177,14 @@ Section "!${PRODUCT_NAME}" sec_app
WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"NoRepair" 1

[% if ib.run_on_windows_start is not none %]
; Running a .exe file on Windows Start
[% for scname in ib.shortcuts %]
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Run" \
"${PRODUCT_NAME}" "$SMPROGRAMS\[[scname]].lnk"
[% endfor %]
[% endif %]

; Check if we need to reboot
IfRebootFlag 0 noreboot
MessageBox MB_YESNO "A reboot is required to finish the installation. Do you wish to reboot now?" \
Expand All @@ -160,6 +194,8 @@ Section "!${PRODUCT_NAME}" sec_app
SectionEnd

Section "Uninstall"
!insertmacro KillProcess

SetRegView [[ib.py_bitness]]
SetShellVarContext all
IfFileExists "$INSTDIR\${USER_INSTALL_MARKER}" 0 +3
Expand Down Expand Up @@ -200,6 +236,9 @@ Section "Uninstall"
[% endblock uninstall_shortcuts %]
RMDir $INSTDIR
DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
[% if ib.run_on_windows_start is not none %]
DeleteRegKey /ifempty HKLM "Software\Microsoft\Windows\CurrentVersion\Run"
[% endif %]
SectionEnd

[% endblock sections %]
Expand All @@ -219,6 +258,24 @@ Function .onMouseOverSection
FunctionEnd

Function .onInit
; https://nsis.sourceforge.io/Auto-uninstall_old_before_installing_new
ReadRegStr $R0 SHCTX \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"UninstallString"
StrCmp $R0 "" done

MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
"${PRODUCT_NAME} is already installed. $\n$\nClick 'OK' to remove the \
previous version or 'Cancel' to cancel this upgrade." \
IDOK uninst
Abort

; Run the uninstaller
uninst:
ClearErrors
ExecWait $R0

done:
; Multiuser.nsh breaks /D command line parameter. Parse /INSTDIR instead.
; Cribbing from https://nsis-dev.github.io/NSIS-Forums/html/t-299280.html
${GetParameters} $0
Expand Down Expand Up @@ -247,3 +304,11 @@ Function correct_prog_files
StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}"
FunctionEnd
[% endif %]

[% if ib.run_after_install is not none %]
Function LaunchLink
[% for scname in ib.shortcuts %]
ExecShell "" "$SMPROGRAMS\[[scname]].lnk"
[% endfor %]
FunctionEnd
[% endif %]