Skip to content

Commit

Permalink
First half of COM Registration Fixing Tool refactor: make the tool ef…
Browse files Browse the repository at this point in the history
…fective on 64-bit windows, and fix more problems (#12560)

Fixes #9039

The COM Registration Fixing Tool seems to have never worked fully on 64bit Windows, as a result of path confusion caused by the use of SysWOW64, and the executable names used.
In addition, modern browsers seem to check a typelib registration that could be damaged by certain uninstallations (e.g. older versions of MathPlayer). We didn't previously do anything about this.
Further background:
In 64 bit Windows, %WINDIR%\System32 contains 64-bit executables. Meanwhile, %WINDIR%\SysWOW64 contains 32-bit executables. That seems inverted from what it should be, but there is a good explanation here for why it's like that.
When a 32-bit application calls something in System32, SysWOW64 assumes that it wants the 32-bit version, and redirects the call to the version in the SysWOW64 directory.
If the 32-bit application really wants the 64-bit version of whatever it's calling, it has to use the virtual directory %WINDIR%\Sysnative, which SysWOW64 provides.
We were calling regedit.exe and regsvr32.exe in contexts where we assumed 32-bit, from either %WINDIR% or %WINDIR%\System32. Because NVDA is 32-bit, that would result in the 32-bit application being called. That's okay.
However when the test was made for 64-bit, executables in %WINDIR%\SysWOW64 were being used, which are also 32-bit.
At least one of those should be 64-bit for COM reregistrations, but neither are.
In no case should we ever call anything in %WINDIR%\SysWOW64 ourselves, which we currently do.

Description of how this pull request fixes the issue:
Taken from the commit that rewrites COMRegistrationFixes/__init__.py:
Repaired the COM Registration Fixing Tool so it is more effective on 64bit Windows (Partial fix of #9039).
• Problems: 
◦ The registerServer function called the 32-bit version of regsvr32.exe, even in 64-bit contexts.
◦ The applyRegistryPatch function called the 32-bit version of regedit, even in 64-bit contexts.
◦ The Win7 32-bit run did not take into account 32-bit only systems (no Program Files (x86) folder).
• Remediation: 
◦ Replaced applyRegistryPatch function with two new functions: apply32bitRegistryPatch and apply64bitRegistryPatch.
◦ Replaced registerServer function with register32bitServer and register64bitServer, to make clear what they do.
◦ The new functions don't check 32/64 bitness; they leave that to the caller and log errors on failure.
◦ Updated to more descriptive error logging.
◦ Adjusted the Windows 7 code to use server registration with proper bitness for each DLL.
• Path remediations: 
◦ Moved the OLEACC_REG_FILE_PATH constant to the top of the file with the rest.
◦ Added Sysnative path to the list of path constants at the top of the file.
◦ Now use Sysnative in the path for intentional 64bit calls.
◦ Now use System32 in the path for 32-bit calls on either 32-bit or 64-bit systems.
◦ Now use reg.exe's import option to load .reg files instead of regedit.exe.
◦ Now check whether to use "Program Files" or "Program Files (x86)" on Win7.
◦ Removed now unused sysWow64 path constant.
• Misc: 
◦ Added docstring note about 32 and 64 bit functions needing attention if NVDA goes 64-bit in the future.
◦ Converted path constants to uppercase-with-underscore style, and corrected case on some Windows paths.
◦ Moved comments with discussion links into module docstring, and rearranged.
◦ Used subprocess.STARTUPINFO to prevent console windows from showing during executions.
◦ In gui/__init__.py update the message shown on tool completion, to recommend a restart.
Separately, @michaelDCurran found a registry key that we should delete to restore browser functionality under certain circumstances. That is included as a separate joint commit.
  • Loading branch information
XLTechie authored Jun 28, 2021
1 parent 161ad1f commit 3f02af2
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 43 deletions.
141 changes: 101 additions & 40 deletions source/COMRegistrationFixes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,142 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2018-2020 NV Access Limited
# Copyright (C) 2018-2021 NV Access Limited, Luke Davis (Open Source Systems, Ltd.)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

"""Utilities to re-register particular system COM interfaces needed by NVDA."""
"""Utilities to re-register particular system COM interfaces needed by NVDA.
Relevant discussions of DLLs, registry keys, and paths, can be found on these issues:
https://github.com/nvaccess/nvda/issues/2807#issuecomment-320149243
https://github.com/nvaccess/nvda/issues/9039
https://github.com/nvaccess/nvda/issues/12560
"""

import os
import subprocess
import winVersion
import globalVars
from logHandler import log

OLEACC_REG_FILE_PATH = os.path.join(globalVars.appDir, "COMRegistrationFixes", "oleaccProxy.reg")
# Particular 64 bit / 32 bit system paths
systemRoot=os.path.expandvars('%SYSTEMROOT%')
system32=os.path.join(systemRoot,'system32')
sysWow64=os.path.join(systemRoot,'syswow64')
systemDrive=os.path.expandvars('%SYSTEMDRIVE%\\')
programFiles=os.path.join(systemDrive,'program files')
programFilesX86=os.path.join(systemDrive,'program files (x86)')
SYSTEM_ROOT = os.path.expandvars("%SYSTEMROOT%")
SYSTEM32 = os.path.join(SYSTEM_ROOT, "System32")
SYSNATIVE = os.path.join(SYSTEM_ROOT, "Sysnative") # Virtual folder for reaching 64-bit exes from 32-bit apps
SYSTEM_DRIVE = os.path.expandvars("%SYSTEMDRIVE%\\")
PROGRAM_FILES = os.path.join(SYSTEM_DRIVE, "Program Files")
PROGRAM_FILES_X86 = os.path.join(SYSTEM_DRIVE, "Program Files (x86)")


def registerServer(fileName,wow64=False):
def register32bitServer(fileName: str) -> None:
"""Registers the COM proxy dll with the given file name, using the 32-bit version of regsvr32.
Note: this function is valid while NVDA remains a 32-bit app. Re-evaluate if we move to 64-bit.
@param fileName: the path to the dll
@type fileName: str
"""
Registers the COM proxy dll with the given file name
Using regsvr32.
# Points to the 32-bit version, on Windows 32-bit or 64-bit.
regsvr32 = os.path.join(SYSTEM32, "regsvr32.exe")
# Make sure a console window doesn't show when running regsvr32.exe
startupInfo = subprocess.STARTUPINFO()
startupInfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupInfo.wShowWindow = subprocess.SW_HIDE
try:
subprocess.check_call([regsvr32, "/s", fileName], startupinfo=startupInfo)
except subprocess.CalledProcessError as e:
log.error(f"Error registering {fileName} in a 32-bit context: {e}")
else:
log.debug(f"Registered {fileName} in a 32-bit context.")


def register64bitServer(fileName: str) -> None:
"""Registers the COM proxy dll with the given file name, using the 64-bit version of regsvr64.
Note: this function is valid while NVDA remains a 32-bit app. Re-evaluate if we move to 64-bit.
@param fileName: the path to the dll
@type fileName: str
@param wow64: If true then the 32 bit (wow64) version of regsvr32 will be used.
@type wow64: bool
"""
regsvr32=os.path.join(sysWow64 if wow64 else system32,'regsvr32.exe')
# SysWOW64 provides a virtual directory to allow 32-bit programs to reach 64-bit executables.
regsvr32 = os.path.join(SYSNATIVE, "regsvr32.exe")
# Make sure a console window doesn't show when running regsvr32.exe
startupInfo = subprocess.STARTUPINFO()
startupInfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupInfo.wShowWindow = subprocess.SW_HIDE
try:
subprocess.check_call([regsvr32,'/s',fileName])
subprocess.check_call([regsvr32, "/s", fileName], startupinfo=startupInfo)
except subprocess.CalledProcessError as e:
log.error("Error registering %s, %s"%(fileName,e))
log.error(f"Error registering {fileName} in a 64-bit context: {e}")
else:
log.debug("Registered %s"%fileName)
log.debug(f"Registered {fileName} in a 64-bit context.")


def applyRegistryPatch(fileName,wow64=False):
def apply32bitRegistryPatch(fileName: str) -> None:
"""Applies the registry patch with the given file name, using 32-bit regExe.
Note: this function is valid while NVDA remains a 32-bit app. Re-evaluate if we move to 64-bit.
@param fileName: the path to the .reg file
@type fileName: str
"""
Applies the registry patch with the given file name
using regedit.
if not os.path.isfile(fileName):
raise FileNotFoundError(f"Cannot apply 32-bit registry patch: {fileName} not found.")
# On 32-bit systems, reg.exe is in System32. On 64-bit systems, SysWOW64 will redirect to 32-bit version.
regExe = os.path.join(SYSTEM_ROOT, "System32", "reg.exe")
# Make sure a console window doesn't show when running reg.exe
startupInfo = subprocess.STARTUPINFO()
startupInfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupInfo.wShowWindow = subprocess.SW_HIDE
try:
subprocess.check_call([regExe, "import", fileName], startupinfo=startupInfo)
except subprocess.CalledProcessError as e:
log.error(f"Error applying 32-bit registry patch from {fileName} with {regExe}: {e}")
else:
log.debug(f"Applied 32-bit registry patch from {fileName}")


def apply64bitRegistryPatch(fileName: str) -> None:
"""Applies the registry patch with the given file name, using 64-bit regExe.
Note: this function is valid while NVDA remains a 32-bit app. Re-evaluate if we move to 64-bit.
@param fileName: the path to the .reg file
@type fileName: str
"""
if not os.path.isfile(fileName):
raise FileNotFoundError(f"Cannot apply registry patch, {fileName} not found.")
regedit=os.path.join(sysWow64 if wow64 else systemRoot,'regedit.exe')
raise FileNotFoundError(f"Cannot apply 64-bit registry patch: {fileName} not found.")
# On 64-bit systems, SysWOW64 provides 32-bit apps with a virtual directory to reach 64-bit executables.
regExe = os.path.join(SYSNATIVE, "reg.exe")
# Make sure a console window doesn't show when running reg.exe
startupInfo = subprocess.STARTUPINFO()
startupInfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupInfo.wShowWindow = subprocess.SW_HIDE
try:
subprocess.check_call([regedit,'/s',fileName])
subprocess.check_call([regExe, "import", fileName], startupinfo=startupInfo)
except subprocess.CalledProcessError as e:
log.error("Error applying registry patch: %s with %s, %s"%(fileName,regedit,e))
log.error(f"Error applying 64-bit registry patch from {fileName} with {regExe}: {e}")
else:
log.debug("Applied registry patch: %s with %s"%(fileName,regedit))
log.debug(f"Applied 64-bit registry patch from {fileName}")


OLEACC_REG_FILE_PATH = os.path.join(globalVars.appDir, "COMRegistrationFixes", "oleaccProxy.reg")
def fixCOMRegistrations():
"""Registers most common COM proxies, in case they have accidentally been unregistered or overwritten by
3rd party software installs or uninstalls.
"""
Registers most common COM proxies, in case they had accidentally been unregistered or overwritten by 3rd party software installs/uninstalls.
"""
is64bit=os.environ.get("PROCESSOR_ARCHITEW6432","").endswith('64')
is64bit = os.environ.get("PROCESSOR_ARCHITEW6432", "").endswith("64")
winVer = winVersion.getWinVer()
OSMajorMinor = (winVer.major, winVer.minor)
log.debug("Fixing COM registration for Windows %s.%s, %s"%(OSMajorMinor[0],OSMajorMinor[1],"64 bit" if is64bit else "32 bit"))
# Commands taken from NVDA issue #2807 comment https://github.com/nvaccess/nvda/issues/2807#issuecomment-320149243
log.debug(
f"Fixing COM registrations for Windows {OSMajorMinor[0]}.{OSMajorMinor[1]}, "
"{} bit.".format("64" if is64bit else "32")
)
# OLEACC (MSAA) proxies
applyRegistryPatch(OLEACC_REG_FILE_PATH)
apply32bitRegistryPatch(OLEACC_REG_FILE_PATH)
if is64bit:
applyRegistryPatch(OLEACC_REG_FILE_PATH, wow64=True)
apply64bitRegistryPatch(OLEACC_REG_FILE_PATH)
# IDispatch and other common OLE interfaces
registerServer(os.path.join(system32,'oleaut32.dll'))
registerServer(os.path.join(system32,'actxprxy.dll'))
register32bitServer(os.path.join(SYSTEM32, "oleaut32.dll"))
register32bitServer(os.path.join(SYSTEM32, "actxprxy.dll"))
if is64bit:
registerServer(os.path.join(sysWow64,'oleaut32.dll'),wow64=True)
registerServer(os.path.join(sysWow64,'actxprxy.dll'),wow64=True)
register64bitServer(os.path.join(SYSTEM32, "oleaut32.dll"))
register64bitServer(os.path.join(SYSTEM32, "actxprxy.dll"))
# IServiceProvider on windows 7 can become unregistered
if OSMajorMinor==(6,1): # Windows 7
registerServer(os.path.join(programFiles,'Internet Explorer','ieproxy.dll'))
if OSMajorMinor == (6, 1): # Windows 7
# There was no "Program Files (x86)" in Windows 7 32-bit, so we cover both cases
register32bitServer(os.path.join(
PROGRAM_FILES_X86 if is64bit else PROGRAM_FILES,
"Internet Explorer", "ieproxy.dll"
))
if is64bit:
registerServer(os.path.join(programFilesX86,'Internet Explorer','ieproxy.dll'),wow64=True)
register64bitServer(os.path.join(PROGRAM_FILES, "Internet Explorer", "ieproxy.dll"))
Binary file modified source/COMRegistrationFixes/oleaccProxy.reg
Binary file not shown.
11 changes: 8 additions & 3 deletions source/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,16 @@ def onRunCOMRegistrationFixesCommand(self, evt):
log.error("Could not execute fixCOMRegistrations command",exc_info=True)
progressDialog.done()
del progressDialog
# Translators: The message displayed when the COM Registration Fixing tool completes.
gui.messageBox(_("COM Registration Fixing tool complete"),
messageBox(
_(
# Translators: The message displayed when the COM Registration Fixing tool completes.
"The COM Registration Fixing tool has finished. "
"It is highly recommended that you restart your computer now, to make sure the changes take full effect."
),
# Translators: The title of a dialog presented when the COM Registration Fixing tool is complete.
_("COM Registration Fixing Tool"),
wx.OK)
wx.OK
)

def onConfigProfilesCommand(self, evt):
if isInMessageBox:
Expand Down
1 change: 1 addition & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ What's New in NVDA
-
- The braille input works properly with the following contracted tables: Arabic grade 2, Spanish grade 2, Urdu grade 2, Chinese (China, Mandarin) grade 2. (#12541)
-
- The COM Registration Fixing Tool now resolves more issues, especially on 64 bit Windows. (#1256)


== Changes for Developers ==
Expand Down

0 comments on commit 3f02af2

Please sign in to comment.