Вопрос: Как реализовать какое-либо действия скрипта при открытии пользователем определённое программы?

Лучший ответ: Напрямую это сделать сложно. Так делают антивирусы, но они должны перехватывать действия операционной системы, что требует работы на уровне драйверов. https://web.archive.org/web/20100127105044/https:/...
Если вы знаете что это за программа и можете писать в реестр, то вот хороший вариант: https://superuser.com/a/1201793
Ну и третий вариант - сделать так, чтобы вместо целевой программы пользователь запускал скрипт, который запустит целевую программу.
Еще можно по таймеру получать список процессов и запускать скрипт при изменении.

Худший ответ: Попробуйте модуль pywinauto отлично работает с интерфейсом Windows, позволяет его автоматизироватьНапример, вот код инсталлера 7-zipwizard=app['7-Zip 19.00 (x64) Setup']
wizard.Install.Click()
time.sleep(5)
wizard.CloseButton.Click()
time.sleep(2)
app=pywinauto.Application().start(r'C:\Program Files\7-Zip\7zFM.exe')
main=app['7-Zip']
main.MenuSelect('Сервис->Настройки...')
settings=app['Настройки']
settings.Button.Click()
settings.ОКButton.Click()
main.Close()Имена кнопок и компонентов на которые надо нажимать можно узнать утилитой swapy, она же автомтически генерит код для pywinauto
Модуль позволяет создавать свои собственные дочерние процессы, а может подключаться к процессам уже ранее запущенных приложений. Ну а раз скрипт уже запущен, то можете в нем хоть print() хоть все что угодно писать. Например я делал сервис киллер notepad Киллер Notepadimport pywinauto, datetime, threading, time
pid_list=[]; log_list=[]

with open("kill-notepad.log", "a") as log:
    log.write(f"{datetime.datetime.now()} | service started, I'am kill all notepad appliction\n")

def logger():
    global log_list
    if len(log_list)>0:
        log_copy=log_list.copy(); log_list=[]
        with open("kill-notepad.log", "a") as log:
            for i in range(len(log_copy)):
                log.write(log_copy.pop(0))

def disabler(main,count):
     for i in range(count-count//255):
            main.set_transparency(alpha=255-i//(count//255))
def alarm(main):
    for i in range(10):
            main.Edit.send_keystrokes("НЕЛЬЗЯ ЗАПУСКАТЬ ЭТУ ПРОГРАММУ\n")

def killer():
    global pid_list
    try:
        app=pywinauto.Application().connect(path=r"c:\windows\system32\notepad.exe")
        main=app.top_window(); count=10000
        if main.process_id() not in pid_list:
            pid_list.append(main.process_id())
        else:
            return
        main.set_transparency(alpha=255)
        threading.Thread(target=alarm(main)).start()
        threading.Thread(target=disabler(main,count)).start()
        log_list.append(f'{datetime.datetime.now()} | destroy {main.element_info}\n')
        app.kill()
    except:
        pass
count=0
while True:
    if count==10:count=0
    time.sleep(1); count+=1
    shout=threading.Thread(target=killer)
    shout.start()
    if count==10:
        report=threading.Thread(target=logger)
        report.start()
Потом можно скрипт упаковать в exe-файл при помощи cx-Freezeупаковываем скриптФайл setup.pyfrom cx_Freeze import setup, Executable
packages = ['win32serviceutil','win32service','win32event','servicemanager','socket','win32timezone','pywinauto', 'sys', 'datetime', 'not-notepad-service']
build_exe_options = {"packages": packages}
executable = [Executable("not-notepad-service.py")]
setup(name = "StopNotepadService",
        version = "1.0",
        description = "Stop NotepadApp and Log",
        options = {"build_exe": build_exe_options},
        executables = executable)
Запускаем сборку python setup.py build
И можно даже потом повесить готовый exe-файл как службу Windows 
при помощи nssm-2.24-101-g897c7ad