Repository: nvaccess/nvda

Issue:
Command line parameter to copy portable configuration on silent installations
### Is your feature request related to a problem? Please describe.
When running NVDA from a portable copy, you can install it from the NVDA menu. A nice convenience feature is the ability to copy the current configuration to the user account. However, it is not possible to do this when running a silent installation for the portable configuration, or when running the launcher with a custom configuration path. This feature would be handy for corporate environments where people would like to provide a custom configuration folder at install time in order to prepopulate the configuration.

### Describe the solution you'd like
Add a --copy-portable-config command line parameter. If provided, the portable configuration will be copied to the user account at (silent) installation time.



Discussion:

Resolving patch:
diff --git a/source/core.py b/source/core.py
--- a/source/core.py
+++ b/source/core.py
@@ -228,6 +228,17 @@ def main():
 	log.debug("loading config")
 	import config
 	config.initialize()
+	if globalVars.appArgs.configPath == config.getUserDefaultConfigPath(useInstalledPathIfExists=True):
+		# Make sure not to offer the ability to copy the current configuration to the user account.
+		# This case always applies to the launcher when configPath is not overridden by the user,
+		# which is the default.
+		# However, if a user wants to run the launcher with a custom configPath,
+		# it is likely that he wants to copy that configuration when installing.
+		# This check also applies to cases where a portable copy is run using the installed configuration,
+		# in which case we want to avoid copying a configuration to itself.
+		# We set the value to C{None} in order for the gui to determine
+		# when to disable the checkbox for this feature.
+		globalVars.appArgs.copyPortableConfig = None
 	if config.conf['development']['enableScratchpadDir']:
 		log.info("Developer Scratchpad mode enabled")
 	if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
@@ -483,7 +494,11 @@ def handlePowerStatusChange(self):
 	globalPluginHandler.initialize()
 	if globalVars.appArgs.install or globalVars.appArgs.installSilent:
 		import gui.installerGui
-		wx.CallAfter(gui.installerGui.doSilentInstall,startAfterInstall=not globalVars.appArgs.installSilent)
+		wx.CallAfter(
+			gui.installerGui.doSilentInstall,
+			copyPortableConfig=globalVars.appArgs.copyPortableConfig,
+			startAfterInstall=not globalVars.appArgs.installSilent
+		)
 	elif globalVars.appArgs.portablePath and (globalVars.appArgs.createPortable or globalVars.appArgs.createPortableSilent):
 		import gui.installerGui
 		wx.CallAfter(gui.installerGui.doCreatePortable,portableDirectory=globalVars.appArgs.portablePath,
diff --git a/source/gui/installerGui.py b/source/gui/installerGui.py
--- a/source/gui/installerGui.py
+++ b/source/gui/installerGui.py
@@ -22,7 +22,15 @@
 import tones
 import systemUtils
 
-def doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,silent=False,startAfterInstall=True):
+
+def doInstall(
+		createDesktopShortcut=True,
+		startOnLogon=True,
+		isUpdate=False,
+		copyPortableConfig=False,
+		silent=False,
+		startAfterInstall=True
+):
 	progressDialog = gui.IndeterminateProgressDialog(gui.mainFrame,
 		# Translators: The title of the dialog presented while NVDA is being updated.
 		_("Updating NVDA") if isUpdate
@@ -55,7 +63,14 @@ def doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,sil
 		# Translators: the title of a retry cancel dialog when NVDA installation fails
 		title=_("File in Use")
 		if winUser.MessageBox(None,message,title,winUser.MB_RETRYCANCEL)==winUser.IDRETRY:
-			return doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,silent,startAfterInstall)
+			return doInstall(
+				createDesktopShortcut=createDesktopShortcut,
+				startOnLogon=startOnLogon,
+				copyPortableConfig=copyPortableConfig,
+				isUpdate=isUpdate,
+				silent=silent,
+				startAfterInstall=startAfterInstall
+			)
 	if res!=0:
 		log.error("Installation failed: %s"%res)
 		# Translators: The message displayed when an error occurs during installation of NVDA.
@@ -88,16 +103,20 @@ def doInstall(createDesktopShortcut,startOnLogon,copyPortableConfig,isUpdate,sil
 	else:
 		wx.GetApp().ExitMainLoop()
 
-def doSilentInstall(startAfterInstall=True):
+
+def doSilentInstall(
+		copyPortableConfig=False,
+		startAfterInstall=True
+):
 	prevInstall=installer.comparePreviousInstall() is not None
 	startOnLogon=globalVars.appArgs.enableStartOnLogon
 	if startOnLogon is None:
 		startOnLogon=config.getStartOnLogonScreen() if prevInstall else True
 	doInstall(
-		installer.isDesktopShortcutInstalled() if prevInstall else True,
-		startOnLogon,
-		False,
-		prevInstall,
+		createDesktopShortcut=installer.isDesktopShortcutInstalled() if prevInstall else True,
+		startOnLogon=startOnLogon,
+		isUpdate=prevInstall,
+		copyPortableConfig=copyPortableConfig,
 		silent=True,
 		startAfterInstall=startAfterInstall
 	)
@@ -181,8 +200,10 @@ def __init__(self, parent, isUpdate):
 		# Translators: The label of a checkbox option in the Install NVDA dialog.
 		createPortableText = _("Copy &portable configuration to current user account")
 		self.copyPortableConfigCheckbox = optionsSizer.addItem(wx.CheckBox(self, label=createPortableText))
-		self.copyPortableConfigCheckbox.Value = False
-		if globalVars.appArgs.launcher:
+		self.copyPortableConfigCheckbox.Value = bool(globalVars.appArgs.copyPortableConfig)
+		if globalVars.appArgs.copyPortableConfig is None:
+			# copyPortableConfig is set to C{None} in the main loop,
+			# when copying the portable configuration should be disabled at all costs.
 			self.copyPortableConfigCheckbox.Disable()
 
 		bHelper = sHelper.addDialogDismissButtons(guiHelper.ButtonHelper(wx.HORIZONTAL))
@@ -213,7 +234,12 @@ def __init__(self, parent, isUpdate):
 
 	def onInstall(self, evt):
 		self.Hide()
-		doInstall(self.createDesktopShortcutCheckbox.Value,self.startOnLogonCheckbox.Value,self.copyPortableConfigCheckbox.Value,self.isUpdate)
+		doInstall(
+			createDesktopShortcut=self.createDesktopShortcutCheckbox.Value,
+			startOnLogon=self.startOnLogonCheckbox.Value,
+			copyPortableConfig=self.copyPortableConfigCheckbox.Value,
+			silent=self.isUpdate
+		)
 		self.Destroy()
 
 	def onCancel(self, evt):
@@ -227,7 +253,9 @@ def onReviewAddons(self, evt):
 		)
 		incompatibleAddons.ShowModal()
 
+
 class InstallingOverNewerVersionDialog(wx.Dialog, DpiScalingHelperMixin):
+
 	def __init__(self):
 		# Translators: The title of a warning dialog.
 		wx.Dialog.__init__(self, gui.mainFrame, title=_("Warning"))


Test patch: