Repository: nvaccess/nvda

Issue:
COM Registration Fixing tool doesn't work
<!--
Please thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.
Issues may be closed if the required information is not present.
https://github.com/nvaccess/nvda/wiki/Github-issue-template-explanation-and-examples
-->

### Steps to reproduce:
1. Install MathPlayer.
2. Uninstall MathPlayer.
3. Read the task bar and notice it says unknown.
4. Run the COM Registration Fixing tool.
5. Read the task bar again and it still says unknown.

### Actual behavior:
The tool doesn't fix NVDA's accessibility support.

### Expected behavior:
The tool fixes NVDA's accessibility support.

### System configuration
#### NVDA installed/portable/running from source:
Installed

#### NVDA version:
Version: alpha-16405,19bb21c2

#### Windows version:
Version 1809 (OS Build 17763.134)

#### Name and version of other software in use when reproducing the issue:

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your PC?
Yes

#### Have you tried any other versions of NVDA?
No



Discussion:
What happens if you execute all the tweaks in https://github.com/nvaccess/nvda/issues/2807#issuecomment-320149243 by hand?
cc: @jcsteh, @michaelDCurran 
There are a lot of them. I haven't tried all of them, but here's a summary of some things I did try.

Testing with a win7 VM, this fixes it, note that this doesn't work on my real win10 machine, and requires an NVDA restart:
    c:\windows\syswow64\regsvr32 c:\windows\syswow64\oleacc.dll

On my win10 machine, this python console command fixes it if NVDA is run as administrator, but I forgot exactly what it does. It seems to return an error, but I'm not sure how to turn the int back into a HRESULT. However, it doesn't work on the win7 VM:
    import comtypes,ctypes;p=comtypes.typeinfo.LoadTypeLibEx(r"c:\windows\syswow64\oleacc.dll");ctypes.windll.oleaut32.RegisterTypeLib(p, ur"c:\windows\syswow64\oleacc.dll", None)

On the win10 machine, Registering c:\windows\syswow64\oleacc.dll and c:\windows\syswow64\oleaut32.dll didn't fix it.
I also tried the ones in system32, along with actxprxy.dll. No luck.
Just to make sure, did you try OneCoreCommonProxyStub.dll on Windows 10 
as well?

@leonardder  Tried that. I didn't realize you linked to a comment, let's try these.

0. Install/uninstall MathPlayer.
1. First reg file - now my start button doesn't say unknown, but that might've been because of the reboot since I didn't test beforehand.
2. Second reg file - no luck.
3. Started an admin cmd. Hey, my Firefox with the instructions still reads, maybe I'm in different bug territory now.
4. Entered both commands, restarted NVDA. No task bar yet.
5. Did that, no taskbar yet.
6. I'm on win10, and this module isn't there.
7. Same as 6.
8. The module "c:\windows\system32\OneCoreCommonProxyStub.dll" was loaded but the entry-point DllRegisterServer was not found.
9. The module "c:\windows\syswow64\OneCoreCommonProxyStub.dll" was loaded but the entry-point DllRegisterServer was not found.

Next steps:
1. Run COM registration fixing tool, nothing, I figured it would at least get it back to saying unknown.
2. Magic python console incantation - got my system back.
3. Don't bother with MathPlayer because I'd have to reboot again and just run the first reg file, my start button doesn't say unknown anymore. I guess that breaks it rather than fixes it.
4. Python console again - back to a working system.
Hmm, so oleaut32.RegisterTypeLib does work why registering it with resvr32 does not, is that correct?
Yes, at least on win10.
That RegisterTypeLib call registers the oleacc typelib. The thing is that on Windows 10, oleacc is not supposed to use the typelib marshaler; it should marshal using oleacc.dll. That's one of the tasks the tool performs: it sets the correct marshaler. I could understand why things would be broken if you run the COM reg tool and don't reboot, since Explorer will still be using the wrong marshaler. However, I would have thought that running the tool and then rebooting would fix things, since now, everything is using oleacc.dll as the marshaler. (It's worth noting that we don't ask the user to reboot, so if that is indeed required, we should fix that.) Technically, you could prove this by just restarting Explorer rather than rebooting.

I guess it's possible that something somewhere still uses the typelib even when oleacc.dll is registered as the marshaler (instead of the typelib marshaler). It certainly wouldn't hurt to add this RegisterTypeLib call; a broken typelib entry is a bad thing regardless.
Actually, you'd have to restart both Explorer and NVDA if you didn't want to reboot, since NVDA will probably be using the wrong marshaler too at that point.
I wonder whether IDispatch still needs the typelib registered correctly, even if oleacc.dll is used as the marshaler. I guess that would make some sense. NVDA shouldn't be using IDispatch directly, but it could still feasibly be causing weirdness.
@jcsteh do you know if there are separate typelib registrations required 
for 64 and 32 bit?

Or is running it from within NVDA (32 bit) enough?


I'm not certain. [This StackOverflow thread](https://stackoverflow.com/questions/9914535/what-decides-whether-registertypelib-writes-to-win32-or-win64) suggests they can be different. On the flip side, that same thread also gives me the impression that you can register a 64 bit typelib even in a 32 bit process.
@tspivey: I want to get this into the COM registration fixing tool for 2019.1, however, with my copy of mathplayer that I have, on my system uninstalling it does not cause any of the problems you mention, though I do admit I have seen the problem a few years back. I thought that Design Science had addressed the issue in their installer.
If you could share your specific copy of the mathplayer installer with me, so that I can reproduce the issue, that would be helpful.
https://www.dropbox.com/s/mxq3nwo6th4ln15/MathPlayerSetup.exe?dl=1
@michaelDCurran Have you made any progress here?
@tspivey does this occur only in case of mathplayer? Or did you see this behavior with installing and uninstalling other programs as well?
Just for the reference: which programs do you have installed on your PC? Is there something like skype for business for example?
Skype for business is also causing some issues related to unregistered COM DLLs.

No, I haven't personally seen it with anything other than mathplayer. But since I've had to help people fix it, it's still a problem.
Hi,

Update: we had a user report that things in web browsers were not read, and the workaround noted by Tyler did work. I think, for 2020.1, the workaround line should be attempted unless we can fine a more refined solution for 2020.2.

Thanks.
This is still confusing, and I can still reproduce this issue using 2020.4.
Is there any news about this?
@michaelDCurran
@josephsl 
Aside from the typelib issue here, it seems the COM registration tool isn't applying the registry patch correctly, but I don't know why.

STR (on a 64 bit system):

1. Press Windows+r, type regedit, press enter.
2. In the address bar, enter:
    `Computer\HKEY_CLASSES_ROOT\Interface\{618736E0-3C3D-11CF-810C-00AA00389B71}\ProxyStubClsid32`
3. Change the (Default) value to:
    `{00020424-0000-0000-C000-000000000046}`
4. NVDA menu -> Tools -> Run COM Registration Fixing tool. Accept the warning, allow administrator access, press OK.
5. Refresh registry editor by pressing f5.
    - Expected: The (Default) value should be `{03022430-ABC4-11D0-BDE2-00AA001A1953}` as specified in the NVDA oleaccProxy.reg patch.
    - Actual: The (Default) value is `{00020424-0000-0000-C000-000000000046}`

What's odd is that if I rename the .reg file, an error gets logged, so the function is definitely running. And if I manually run:
`c:\windows\regedit.exe /s "C:\Program Files (x86)\NVDA\COMRegistrationFixes\oleaccProxy.reg"`
it updates the registry as expected.
Oh! I think I understand what's going wrong here. In 32 bit apps, Windows WOW64 [redirects certain file system paths](https://docs.microsoft.com/en-us/windows/win32/winprog64/file-system-redirector). So, when you run c:\windows\regedit.exe, this gets redirected transparently to c:\windows\syswow64\regedit.exe. The same happens for regsvr32. This means this tool has never worked for the 64 bit registry, only the 32 bit registry.

WOW64 recognises c:\windows\sysnative as a special path which redirects to the native (64 bit) system directory. However, regedit.exe isn't in system, so this may not work for that. If not, we'll have to disable WOW64 file system redirection before our subprocess calls and re-enable it again afterward.
[This thread](https://social.msdn.microsoft.com/Forums/sqlserver/en-US/190d5840-5861-43fc-b95d-ff0e702529bc/how-to-open-64-bit-regeditexe-from-32-bit-application?forum=vbgeneral) confirms that indeed you can't do sysnative\regedit.exe. However, you can apparently do sysnative\regedt32.exe, which apparently redirects to the 64 bit regedit. What a mess.
NVDA keeps saying "unknown" in my Firefox, and the "Run COM Registration Fixing tool" does not take any effect. I use Windows 10 64-bit. I know this issue was mentioned in another thread and the solutions were suggested; however, would you mind sharing step-by-step instructions here for Windows 10 64-bit users? I tried to fix the issue on my end following the suggested comments in other threads, but none of them worked.
Is this recent; i.e. only with the Firefox 89 update? If so, this is due to
a bug in Firefox. Please update to Firefox 89.0.1, which should fix this.

If this isn't recent (i.e. it happens with older versions of Firefox too),
this is due to COM registration issues.

@jcsteh, mine was 89, so have just updated to 89.0.1 following your suggestion. Still, I have the same issue. I also tried with the COM fixing tool and did reboot. No success.
Please try the following:
1. Press Windows+r to open the run dialog.
2. Paste the following:
%ProgramFiles(x86)%\nvda\COMRegistrationFixes\oleaccProxy.reg
3. Allow admin access, then allow registry editor to make the changes.
4. Reboot.

Unfortunately, no success. Still, "unknown".
Okay. Try this (very similar, but a slightly different command):
1. Press Windows+r to open the run dialog.
2. Paste the following:
c:\windows\syswow64\regedit.exe
%ProgramFiles(x86)%\nvda\COMRegistrationFixes\oleaccProxy.reg
3. Allow admin access, then allow registry editor to make the changes.
4. Reboot.

I see the following error three times:

> Registry Editor
> Cannot import C:\Program: Error opening the file. There may be a disk or file system error.
> OK 
My bad. Try this (note the quotes):
c:\windows\syswow64\regedit.exe
"%ProgramFiles(x86)%\nvda\COMRegistrationFixes\oleaccProxy.reg"

With quotes, it was registered, but still it says "unknown" in Firefox after reboot.
> With quotes, it was registered, but still it says "unknown" in Firefox after reboot.

Same thing found by a user on the NVDA list to whom I gave similar 
instructions last week. Something is still missing. I don't believe it was 
Firefox in that case, although I'd have to go back to verify.

Hi,


Just a thought:

Remember that Firefox somewhere has a checkbox to enable/disable 
accessibilité API.

Could it have been unchecked for some obscure reason?




Michel Such ***@***.***>
------------------------------------------------------------------------
Expert NVDA certifié 2019
Le 17/06/2021 à 07:41, Luke Davis a écrit :
> > With quotes, it was registered, but still it says "unknown" in 
> Firefox after reboot.
>
> Same thing found by a user on the NVDA list to whom I gave similar
> instructions last week. Something is still missing. I don't believe it 
> was
> Firefox in that case, although I'd have to go back to verify.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub 
> <https://github.com/nvaccess/nvda/issues/9039#issuecomment-862942862>, 
> or unsubscribe 
> <https://github.com/notifications/unsubscribe-auth/ADYUFDYMPCQ22QDDMSK7HUDTTGDI7ANCNFSM4GJJ724A>.
>

Very strange. It could be the typelib as discussed earlier in this issue,
but I need to dig up the right registry keys to fix that, as calling the
function from a 32 bit executable may not cover the 64 bit case; I'm not
sure.

Hi, you may need to specify 64-bit key access (I found out the hard way while working on Resource Monitor a few years ago when obtaining UBR (update build revision) for a given Windows 10 build). Thanks.


We don't need to do that in this case because we apply the patch using the
two different versions of regedit.exe... although as I noted above, we're
not actually doing what we think we're doing there. The fix is relatively
straightforward, but I don't have a working dev environment since NVDA
moved back to Python 3.7 and I haven't had time to rectify that.

Hi, I think a diff file would suffice in letting us test it in our environments, or perhaps a pseudo-code so one of us can commit it on your behalf? Thanks.



Resolving patch:
diff --git a/source/COMRegistrationFixes/__init__.py b/source/COMRegistrationFixes/__init__.py
--- a/source/COMRegistrationFixes/__init__.py
+++ b/source/COMRegistrationFixes/__init__.py
@@ -1,9 +1,14 @@
 # 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
@@ -11,71 +16,127 @@
 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"))
diff --git a/source/gui/__init__.py b/source/gui/__init__.py
--- a/source/gui/__init__.py
+++ b/source/gui/__init__.py
@@ -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:


Test patch: