Skip to content

Instantly share code, notes, and snippets.

@az0
Last active February 8, 2019 22:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save az0/06be331b83ba82f6bdbe8763a3cdeb4a to your computer and use it in GitHub Desktop.
Save az0/06be331b83ba82f6bdbe8763a3cdeb4a to your computer and use it in GitHub Desktop.
Workaround for "SAS Message Log" dialog
#
# Purpose:
# Close the "SAS Message Log" dialog caused by the logging message
# "ODBC: COMMIT performed on connection #" because this hangs the SAS process,
# so control is not turned back over to SJS.
#
# Requirement:
# Python version 3
#
# By:
# Andrew Ziem, January 2019
#
#
# See article https://heuristicandrew.blogspot.com/2019/01/sas-message-log-with-odbc-commit.html
#
# Some code thanks to https://gist.github.com/inaz2/6f72f18af15c47ab7432
#
import os
import ctypes
import logging
import time
from ctypes import CFUNCTYPE, POINTER, c_bool, c_int, cdll, create_unicode_buffer
# ctypes functions
EnumWindows = cdll.user32.EnumWindows
EnumWindowsProc = CFUNCTYPE(c_bool, POINTER(c_int), POINTER(c_int))
GetWindowText = cdll.user32.GetWindowTextW
GetWindowTextLength = cdll.user32.GetWindowTextLengthW
IsWindowVisible = cdll.user32.IsWindowVisible
SendMessage = ctypes.windll.user32.SendMessageW
# constant
WM_CLOSE = 0x0010
TARGET_WINDOW_NAME = 'SAS Message Log'
POLLING_FREQUENCY_IN_SECONDS = 30
# environment setup
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
def get_mutex_fn():
"""Return the filename of them mutex"""
import tempfile
return os.path.join(tempfile.gettempdir(), 'close_sas_message_log_mutex')
def is_other_process_running():
"""Is there another process like this?"""
if not os.path.exists(get_mutex_fn()):
return False
file_mtime = os.stat(get_mutex_fn()).st_mtime
file_age_seconds = time.time() - file_mtime
def update_mutex():
"""Let other processes know this process is alive"""
logging.debug('Touching mutex: %s', get_mutex_fn())
from pathlib import Path
Path(get_mutex_fn()).touch()
def print_last_error(rc, ctx):
"""Print last Windows error, if there was an error"""
if not rc == 0:
return
last_error_code = ctypes.GetLastError()
last_err_msg = ctypes.FormatError(last_error_code)
print('error %s in %s: %s' % (last_error_code, ctx, last_err_msg))
def enum_func(hwnd, lParam):
"""Callback function for enumerating windows"""
if IsWindowVisible(hwnd):
length = GetWindowTextLength(hwnd)
buff = create_unicode_buffer(length + 1)
GetWindowText(hwnd, buff, length + 1)
if buff.value:
if buff.value == TARGET_WINDOW_NAME:
logging.warning('Closing window with title "%s"', buff.value)
SendMessage(hwnd, WM_CLOSE, 0, 0)
# True means to keep enumerating
return True
def loop():
"""The main loop to keep checking for the window to close"""
if is_other_process_running():
logger.error('Another detected because of mutex: %s', get_mutex_fn())
return
while True:
logging.debug('Enumerating titles of active windows')
rc = EnumWindows(EnumWindowsProc(enum_func), 0)
print_last_error(rc, 'EnumWindows')
update_mutex()
logging.info('Sleeping for %s seconds' % POLLING_FREQUENCY_IN_SECONDS)
time.sleep(POLLING_FREQUENCY_IN_SECONDS)
try:
loop()
except KeyboardInterrupt:
logging.warning('Keyboard interrupt detected, so deleting mutex')
os.remove(get_mutex_fn())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment