Repository: nvaccess/nvda

Issue:
Can not copy settings to system profile [fresh install]
<!--
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. Uninstall NVDA
1. Remove user profile from `%appdata%/nvda`
1. Remove system configuration profile from `C:\Program Files (x86)\NVDA\systemConfig`
1. Install NVDA
1. Attempt to "Use currently saved settings on the logon and other secure screens (requires administrator privileges)" (button on the general settings panel [`NVDA+control+g`])

### Actual behavior:
An error sound is heard, and the following error in the log:

```
ERROR - unhandled exception (18:46:34.341):
Traceback (most recent call last):
  File "gui\settingsDialogs.pyc", line 730, in onCopySettings
WindowsError: [Error 3] The system cannot find the path specified: u'C:\\Users\\Reef\\AppData\\Roaming\\nvda\\appModules\\*.*'
```

### Expected behavior:
The configuration should be copied to the system profile.

### System configuration
#### NVDA installed/portable/running from source: Installed

#### NVDA version:
2019.1

#### Windows version:
Windows 10

#### Name and version of other software in use when reproducing the issue:
None

#### 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?
2018.4 - The issue does not occur



Discussion:
Hi, perhaps a leftover from pre-scratchpad commits? Thanks.
Thanks Joseph. Yep, I have a fix on it's way.

Resolving patch:
diff --git a/source/config/__init__.py b/source/config/__init__.py
--- a/source/config/__init__.py
+++ b/source/config/__init__.py
@@ -137,13 +137,15 @@ def getSystemConfigPath():
 			pass
 	return None
 
+SCRATCH_PAD_ONLY_DIRS = ('appModules','brailleDisplayDrivers','globalPlugins','synthDrivers')
+
 def getScratchpadDir(ensureExists=False):
 	""" Returns the path where custom appModules, globalPlugins and drivers can be placed while being developed."""
 	path=os.path.join(globalVars.appArgs.configPath,'scratchpad')
 	if ensureExists:
 		if not os.path.isdir(path):
 			os.makedirs(path)
-		for subdir in ('appModules','brailleDisplayDrivers','globalPlugins','synthDrivers'):
+		for subdir in SCRATCH_PAD_ONLY_DIRS:
 			subpath=os.path.join(path,subdir)
 			if not os.path.isdir(subpath):
 				os.makedirs(subpath)
@@ -269,13 +271,20 @@ def setSystemConfigToCurrentConfig():
 def _setSystemConfig(fromPath):
 	import installer
 	toPath=os.path.join(sys.prefix.decode('mbcs'),'systemConfig')
+	log.debug("Copying config to systemconfig dir: %s", toPath)
 	if os.path.isdir(toPath):
 		installer.tryRemoveFile(toPath)
-	for curSourceDir,subDirs,files in os.walk(fromPath):
-		if curSourceDir==fromPath:
-			curDestDir=toPath
+	for curSourceDir, subDirs, files in os.walk(fromPath):
+		if curSourceDir == fromPath:
+			curDestDir = toPath
+			# Don't copy from top-level config dirs we know will be ignored due to security risks.
+			removeSubs = set(SCRATCH_PAD_ONLY_DIRS).intersection(subDirs)
+			for subPath in removeSubs:
+				log.debug("Ignored folder that may contain unpackaged addons: %s", subPath)
+				subDirs.remove(subPath)
 		else:
-			curDestDir=os.path.join(toPath,os.path.relpath(curSourceDir,fromPath))
+			relativePath = os.path.relpath(curSourceDir, fromPath)
+			curDestDir = os.path.join(toPath, relativePath)
 		if not os.path.isdir(curDestDir):
 			os.makedirs(curDestDir)
 		for f in files:
diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py
--- a/source/gui/settingsDialogs.py
+++ b/source/gui/settingsDialogs.py
@@ -726,21 +726,29 @@ def makeSettings(self, settingsSizer):
 			settingsSizerHelper.addItem(item)
 
 	def onCopySettings(self,evt):
-		for packageType in ('addons','appModules','globalPlugins','brailleDisplayDrivers','synthDrivers'):
-			if len(os.listdir(os.path.join(globalVars.appArgs.configPath,packageType)))>0:
-				if gui.messageBox(
-					# Translators: A message to warn the user when attempting to copy current settings to system settings.
-					_("Add-ons were detected in your user settings directory. Copying these to the system profile could be a security risk. Do you still wish to copy your settings?"),
-					# Translators: The title of the warning dialog displayed when trying to copy settings for use in secure screens.
-					_("Warning"),wx.YES|wx.NO|wx.ICON_WARNING,self
-				)==wx.NO:
-					return
-				break
-		progressDialog = gui.IndeterminateProgressDialog(gui.mainFrame,
-		# Translators: The title of the dialog presented while settings are being copied
-		_("Copying Settings"),
-		# Translators: The message displayed while settings are being copied to the system configuration (for use on Windows logon etc)
-		_("Please wait while settings are copied to the system configuration."))
+		addonsDirPath = os.path.join(globalVars.appArgs.configPath, 'addons')
+		if os.path.isdir(addonsDirPath) and 0 < len(os.listdir(addonsDirPath)):
+			# Translators: A message to warn the user when attempting to copy current
+			# settings to system settings.
+			message = _(
+				"Add-ons were detected in your user settings directory. "
+				"Copying these to the system profile could be a security risk. "
+				"Do you still wish to copy your settings?"
+			)
+			# Translators: The title of the warning dialog displayed when trying to
+			# copy settings for use in secure screens.
+			title = _("Warning")
+			style = wx.YES | wx.NO | wx.ICON_WARNING
+			if wx.NO == gui.messageBox(message, title, style, self):
+				return
+		progressDialog = gui.IndeterminateProgressDialog(
+			gui.mainFrame,
+			# Translators: The title of the dialog presented while settings are being copied
+			_("Copying Settings"),
+			# Translators: The message displayed while settings are being copied
+			# to the system configuration (for use on Windows logon etc)
+			_("Please wait while settings are copied to the system configuration.")
+		)
 		while True:
 			try:
 				gui.ExecAndPump(config.setSystemConfigToCurrentConfig)


Test patch: