diff --git a/build.gradle b/build.gradle
index 73dade74c8031bc8a68fbb3d8072b19d7b3bd761..78832236939b32124502cb5dd2c27a64600aa4b7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ def vSwagger = '2.4.0'
 def vHibernate = '4.3.11.Final'
 def vEhcache = '2.6.11'
 def vJackson = '2.6.5'
-def vPostgreSQL = '9.4.1207'
+def vPostgreSQL = '9.4.1209.jre7'
 def vServletApi = '3.1.0'
 def vTomcat = '7.0.68'
 def vCargo = '1.4.18'
diff --git a/src/main/java/org/nrg/xapi/rest/settings/AutomationApi.java b/src/main/java/org/nrg/xapi/rest/settings/AutomationApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..f44c50c0b8ce440868217d71de0de1f360207e9b
--- /dev/null
+++ b/src/main/java/org/nrg/xapi/rest/settings/AutomationApi.java
@@ -0,0 +1,129 @@
+package org.nrg.xapi.rest.settings;
+
+import io.swagger.annotations.*;
+import org.nrg.framework.annotations.XapiRestController;
+import org.nrg.prefs.exceptions.InvalidPreferenceName;
+import org.nrg.xdat.rest.AbstractXapiRestController;
+import org.nrg.xdat.security.services.RoleHolder;
+import org.nrg.xdat.security.services.UserManagementServiceI;
+import org.nrg.xnat.preferences.AutomationPreferences;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import java.util.Map;
+
+@Api(description = "Automation Service API")
+@XapiRestController
+@RequestMapping(value = "/automation")
+public class AutomationApi extends AbstractXapiRestController {
+    @Autowired
+    public AutomationApi(final AutomationPreferences preferences, final UserManagementServiceI userManagementService, final RoleHolder roleHolder) {
+        super(userManagementService, roleHolder);
+        _preferences = preferences;
+    }
+
+    @ApiOperation(value = "Returns the full map of automation settings for this XNAT application.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = String.class, responseContainer = "Map")
+    @ApiResponses({@ApiResponse(code = 200, message = "Automation settings successfully retrieved."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Insufficient privileges to retrieve the requested setting."),
+                   @ApiResponse(code = 500, message = "An unexpected error occurred.")})
+    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+    public ResponseEntity<Map<String, Object>> getAllAutomationPreferences() {
+        if (_log.isDebugEnabled()) {
+            _log.info("User " + getSessionUser().getUsername() + " requested the system automation settings.");
+        }
+
+        final HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+
+        return new ResponseEntity<>(_preferences.getPreferenceMap(), HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Sets a map of automation properties.", notes = "Sets the automation properties specified in the map.", response = Void.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Automation properties successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Not authorized to set automation properties."),
+                   @ApiResponse(code = 500, message = "An unexpected error occurred.")})
+    @RequestMapping(consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.POST)
+    public ResponseEntity<Void> setBatchAutomationPreferences(@ApiParam(value = "The map of automation preferences to be set.", required = true) @RequestBody final Map<String, String> properties) {
+        if (_log.isDebugEnabled()) {
+            _log.info("User " + getSessionUser().getUsername() + " requested to set a batch of automation preferences.");
+        }
+
+        final HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+
+        // Is this call initializing the system?
+        for (final String name : properties.keySet()) {
+            try {
+                _preferences.set(properties.get(name), name);
+                if (_log.isInfoEnabled()) {
+                    _log.info("Set property {} to value: {}", name, properties.get(name));
+                }
+            } catch (InvalidPreferenceName invalidPreferenceName) {
+                _log.error("Got an invalid preference name error for the preference: {}, failed to set value to: {}", name, properties.get(name));
+            }
+        }
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Returns whether internal scripting is enabled for this XNAT application.", notes = "Internal scripting may be used by XNAT itself even when disabled, but this setting indicates whether users and administrators can configure and execute scripts internally to the application process. Access to this setting is restricted to site administrators.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Internal scripting setting successfully retrieved."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Insufficient privileges to retrieve the requested setting."),
+                   @ApiResponse(code = 500, message = "An unexpected error occurred.")})
+    @RequestMapping(value = "enabled", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+    public ResponseEntity<Boolean> isInternalScriptingEnabled() {
+        if (_log.isDebugEnabled()) {
+            _log.debug("User " + getSessionUser().getUsername() + " requested the internal scripting enabled setting.");
+        }
+
+        final HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+
+        return new ResponseEntity<>(_preferences.isInternalScriptingEnabled(), HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "Sets the internal scripting enabled flag for this XNAT application to the submitted value.", notes = "Internal scripting may be used by XNAT itself even when disabled, but this setting indicates whether users and administrators can configure and execute scripts internally to the application process. Access to this setting is restricted to site administrators.", response = Boolean.class)
+    @ApiResponses({@ApiResponse(code = 200, message = "Internal scripting setting successfully set."),
+                   @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."),
+                   @ApiResponse(code = 403, message = "Insufficient privileges to change the requested setting."),
+                   @ApiResponse(code = 500, message = "An unexpected error occurred.")})
+    @RequestMapping(value = "enabled/{setting}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PUT)
+    public ResponseEntity<Boolean> setInternalScriptingEnabled(@PathVariable("setting") final boolean setting) {
+        if (_log.isDebugEnabled()) {
+            _log.warn("User {} requested to change the internal scripting enabled setting to {}.", getSessionUser().getUsername(), setting);
+        }
+
+        final HttpStatus status = isPermitted();
+        if (status != null) {
+            return new ResponseEntity<>(status);
+        }
+
+        // Only change the setting if they're different.
+        if (_preferences.isInternalScriptingEnabled() != setting) {
+            _preferences.setInternalScriptingEnabled(setting);
+        }
+
+        return new ResponseEntity<>(_preferences.isInternalScriptingEnabled(), HttpStatus.OK);
+    }
+
+    private static final Logger _log = LoggerFactory.getLogger(AutomationApi.class);
+
+    private final AutomationPreferences _preferences;
+}
diff --git a/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java b/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java
index 8f3afdcdb6a1440e1677d7cccfada6426aebb7d1..dde0ada29d7a8d5c63544bffa53dc1651b9173de 100644
--- a/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/ApplicationConfig.java
@@ -16,7 +16,7 @@ import org.nrg.xdat.services.ThemeService;
 import org.nrg.xdat.services.impl.ThemeServiceImpl;
 import org.nrg.xnat.initialization.InitializingTask;
 import org.nrg.xnat.initialization.InitializingTasksExecutor;
-import org.nrg.xnat.restlet.XnatRestlet;
+import org.nrg.xnat.preferences.AutomationPreferences;
 import org.nrg.xnat.restlet.XnatRestletExtensions;
 import org.nrg.xnat.restlet.XnatRestletExtensionsBean;
 import org.nrg.xnat.restlet.actions.importer.ImporterHandlerPackages;
@@ -30,12 +30,14 @@ import java.util.HashSet;
 import java.util.List;
 
 @Configuration
-@ComponentScan({"org.nrg.automation.repositories", "org.nrg.config.daos", "org.nrg.dcm.xnat", "org.nrg.dicomtools.filters",
-                "org.nrg.framework.datacache.impl.hibernate", "org.nrg.framework.services.impl", "org.nrg.notify.daos",
-                "org.nrg.prefs.repositories", "org.nrg.xdat.daos", "org.nrg.xdat.services.impl.hibernate", "org.nrg.xft.daos",
-                "org.nrg.xft.event.listeners", "org.nrg.xft.services", "org.nrg.xnat.configuration", "org.nrg.xnat.daos",
-                "org.nrg.xnat.event.listeners", "org.nrg.xnat.helpers.merge", "org.nrg.xnat.initialization.tasks",
-                "org.nrg.xnat.services.impl.hibernate", "org.nrg.xnat.spawner.repositories", "org.nrg.automation.daos"})
+@ComponentScan({"org.nrg.automation.daos", "org.nrg.automation.repositories", "org.nrg.config.daos", "org.nrg.dcm.xnat",
+                "org.nrg.dicomtools.filters", "org.nrg.framework.datacache.impl.hibernate",
+                "org.nrg.framework.services.impl", "org.nrg.notify.daos", "org.nrg.prefs.repositories",
+                "org.nrg.xdat.daos", "org.nrg.xdat.services.impl.hibernate", "org.nrg.xft.daos",
+                "org.nrg.xft.event.listeners", "org.nrg.xft.services", "org.nrg.xnat.configuration",
+                "org.nrg.xnat.daos", "org.nrg.xnat.event.listeners", "org.nrg.xnat.helpers.merge",
+                "org.nrg.xnat.initialization.tasks", "org.nrg.xnat.services.impl.hibernate",
+                "org.nrg.xnat.spawner.repositories"})
 @Import({FeaturesConfig.class, ReactorConfig.class})
 @ImportResource("WEB-INF/conf/mq-context.xml")
 public class ApplicationConfig {
@@ -59,6 +61,11 @@ public class ApplicationConfig {
         return new NotificationsPreferences(preferenceService, eventService, configFolderPaths);
     }
 
+    @Bean
+    public AutomationPreferences automationPreferences(final NrgEventService service) {
+        return new AutomationPreferences(service);
+    }
+
     @Bean
     public PETTracerUtils petTracerUtils(final ConfigService configService) throws Exception {
         return new PETTracerUtils(configService);
diff --git a/src/main/java/org/nrg/xnat/configuration/OrmConfig.java b/src/main/java/org/nrg/xnat/configuration/OrmConfig.java
index fd4aa23e9eb2c13b47bf0aa0be2c7f24294165ff..519d95074b11158c87eafa4d996ffe50849107bc 100644
--- a/src/main/java/org/nrg/xnat/configuration/OrmConfig.java
+++ b/src/main/java/org/nrg/xnat/configuration/OrmConfig.java
@@ -18,6 +18,7 @@ import org.springframework.orm.hibernate4.HibernateTransactionManager;
 import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
 import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.support.TransactionTemplate;
 
 import javax.sql.DataSource;
 import java.io.IOException;
@@ -26,9 +27,6 @@ import java.util.Properties;
 @Configuration
 @EnableTransactionManagement(proxyTargetClass = true)
 public class OrmConfig {
-
-    public static final String XNAT_ENTITIES_PACKAGES = "META-INF/xnat/entities/**/*-entity-packages.txt";
-
     @Bean
     public ImprovedNamingStrategy namingStrategy() {
         return new PrefixedTableNamingStrategy("xhbm");
@@ -80,6 +78,12 @@ public class OrmConfig {
         return new HibernateTransactionManager(sessionFactory(environment, dataSource).getObject());
     }
 
+    @Bean
+    public TransactionTemplate transactionTemplate(final PlatformTransactionManager transactionManager) {
+        return new TransactionTemplate(transactionManager);
+    }
+
+    private static final String     XNAT_ENTITIES_PACKAGES       = "META-INF/xnat/entities/**/*-entity-packages.txt";
     private static final Properties DEFAULT_HIBERNATE_PROPERTIES = new Properties() {{
         setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect");
         setProperty("hibernate.hbm2ddl.auto", "update");
diff --git a/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java b/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java
index e5d2d2a0c488abc3599e836ed9c255a5092d33e0..68d3fcc75b8ae8ac811a1eaf1f0794a83b8f1645 100644
--- a/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java
+++ b/src/main/java/org/nrg/xnat/event/entities/ScriptLaunchRequestEvent.java
@@ -6,8 +6,8 @@ import javax.persistence.Entity;
 import javax.persistence.PrimaryKeyJoinColumn;
 import javax.persistence.Transient;
 
+import org.nrg.automation.event.AutomationCompletionEventI;
 import org.nrg.automation.event.AutomationEventImplementerI;
-import org.nrg.automation.event.entities.AutomationCompletionEvent;
 import org.nrg.automation.event.entities.PersistentEvent;
 import org.nrg.framework.event.EventClass;
 
@@ -25,8 +25,9 @@ public class ScriptLaunchRequestEvent extends PersistentEvent implements Automat
 	private static final long serialVersionUID = 7465778737330635218L;
 	
 	/** The automation completion event. */
-	private AutomationCompletionEvent automationCompletionEvent;
+	private AutomationCompletionEventI automationCompletionEvent;
 	
+	/** The parameter map. */
 	private Map<String,Object> parameterMap = Maps.newHashMap();
 	
 	/**
@@ -41,7 +42,7 @@ public class ScriptLaunchRequestEvent extends PersistentEvent implements Automat
 	 */
 	@Override
 	@Transient
-	public AutomationCompletionEvent getAutomationCompletionEvent() {
+	public AutomationCompletionEventI getAutomationCompletionEvent() {
 		return automationCompletionEvent;
 	}
 
@@ -49,21 +50,30 @@ public class ScriptLaunchRequestEvent extends PersistentEvent implements Automat
 	 * @see org.nrg.xft.event.AutomationEventImplementerI#setAutomationCompletionEvent(org.nrg.xft.event.entities.AutomationCompletionEvent)
 	 */
 	@Override
-	public void setAutomationCompletionEvent(AutomationCompletionEvent automationCompletionEvent) {
+	public void setAutomationCompletionEvent(AutomationCompletionEventI automationCompletionEvent) {
 		this.automationCompletionEvent = automationCompletionEvent;
 	}
 
+	/* (non-Javadoc)
+	 * @see org.nrg.automation.event.AutomationEventImplementerI#getParameterMap()
+	 */
 	@Override
 	@Transient
 	public Map<String, Object> getParameterMap() {
 		return this.parameterMap;
 	}
 
+	/* (non-Javadoc)
+	 * @see org.nrg.automation.event.AutomationEventImplementerI#setParameterMap(java.util.Map)
+	 */
 	@Override
 	public void setParameterMap(Map<String, Object> parameterMap) {
 		this.parameterMap = parameterMap;
 	}
 
+	/* (non-Javadoc)
+	 * @see org.nrg.automation.event.AutomationEventImplementerI#addParameterToParameterMap(java.lang.String, java.lang.Object)
+	 */
 	@Override
 	public void addParameterToParameterMap(String parameter, Object value) {
 		this.parameterMap.put(parameter, value);
diff --git a/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java b/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java
index 68034b3e9ebd5c127a8f074a6bb00965bfa1233b..113c83e078dcc34c23d7705d7284f637465a71a8 100644
--- a/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java
+++ b/src/main/java/org/nrg/xnat/event/listeners/AutomationEventScriptHandler.java
@@ -7,6 +7,7 @@ import org.hibernate.exception.ConstraintViolationException;
 import org.nrg.automation.entities.Script;
 import org.nrg.automation.entities.ScriptOutput;
 import org.nrg.automation.entities.ScriptOutput.Status;
+import org.nrg.automation.event.AutomationCompletionEventI;
 import org.nrg.automation.event.AutomationEventImplementerI;
 import org.nrg.automation.event.entities.AutomationCompletionEvent;
 import org.nrg.automation.event.entities.AutomationEventIds;
@@ -76,57 +77,58 @@ public class AutomationEventScriptHandler implements Consumer<Event<AutomationEv
     /**
      * The _service.
      */
-    @Autowired
-    @Lazy
     private ScriptRunnerService _service;
     
-    /**
-     * The _eventService.
-     */
-    @Autowired
- 	private NrgEventService _eventService;
-
     /**
      * The _script trigger service.
      */
-    @Autowired
-    @Lazy
     private ScriptTriggerService _scriptTriggerService;
 
     /**
      * The _data source.
      */
-    @Inject
     private DataSource _dataSource;
 
     /**
      * Automation filters service.
      */
-    @Inject
     private AutomationFiltersService _filtersService;
 
     /**
      * Persistent event service.
      */
-    @Inject
     private PersistentEventService _persistentEventService;
     
     /** The _ids service. */
-    @Inject
     private AutomationEventIdsService _idsService;
     
     /** The _ids ids service. */
-    @Inject
     private AutomationEventIdsIdsService _idsIdsService;
 
     /**
-     * Instantiates a new automated script handler.
+     * Instantiates a new automation event script handler.
      *
      * @param eventBus the event bus
+     * @param service the service
+     * @param scriptTriggerService the script trigger service
+     * @param dataSource the data source
+     * @param filtersService the filters service
+     * @param persistentEventService the persistent event service
+     * @param idsService the ids service
+     * @param idsIdsService the ids ids service
      */
-    @Inject
-    public AutomationEventScriptHandler(EventBus eventBus) {
+    @Autowired
+    public AutomationEventScriptHandler(EventBus eventBus, ScriptRunnerService service, ScriptTriggerService scriptTriggerService,
+    		DataSource dataSource, AutomationFiltersService filtersService, PersistentEventService persistentEventService,
+    		AutomationEventIdsService idsService, AutomationEventIdsIdsService idsIdsService) {
         eventBus.on(type(AutomationEventImplementerI.class), this);
+        this._service = service;
+        this._scriptTriggerService = scriptTriggerService;
+        this._dataSource = dataSource;
+        this._filtersService = filtersService;
+        this._persistentEventService = persistentEventService;
+        this._idsService = idsService;
+        this._idsIdsService = idsIdsService;
     }
 
     /**
@@ -137,7 +139,7 @@ public class AutomationEventScriptHandler implements Consumer<Event<AutomationEv
         /** Update script trigger table for XNAT 1.7.  Drop constraints on any columns other than id and trigger_id */
         if (_scriptTriggerService instanceof HibernateScriptTriggerService) {
 
-            List<String> cleanUpQuery = (new JdbcTemplate(_dataSource)).query(
+            final List<String> cleanUpQuery = (new JdbcTemplate(_dataSource)).query(
                     "SELECT DISTINCT 'ALTER TABLE '||tc.table_name||' DROP CONSTRAINT '||tc.constraint_name||';'" +
                     "  FROM information_schema.table_constraints tc " +
                     "  LEFT JOIN information_schema.constraint_column_usage cu " +
@@ -150,7 +152,7 @@ public class AutomationEventScriptHandler implements Consumer<Event<AutomationEv
                     });
             if (!cleanUpQuery.isEmpty()) {
                 logger.info("Cleaning up pre XNAT 1.7 constraints on the xhbm_script_trigger and xhbm_event tables");
-                for (String query : cleanUpQuery) {
+                for (final String query : cleanUpQuery) {
                     if (query.contains("xhbm_script_trigger")) {
                         logger.info("Execute clean-up query (" + query + ")");
                         new JdbcTemplate(_dataSource).execute(query);
@@ -299,7 +301,7 @@ public class AutomationEventScriptHandler implements Consumer<Event<AutomationEv
         }
         final String eventName = eventID.replaceAll("\\*OPEN\\*", "(").replaceAll("\\*CLOSE\\*", ")");
         //check to see if this has been handled before
-        final AutomationCompletionEvent automationCompletionEvent = automationEvent.getAutomationCompletionEvent();
+        final AutomationCompletionEventI automationCompletionEvent = automationEvent.getAutomationCompletionEvent();
         for (final Script script : getScripts(automationEvent.getExternalId(), eventClass, eventID, filterMap)) {
             try {
                 final String action = "Executed script " + script.getScriptId();
@@ -320,63 +322,14 @@ public class AutomationEventScriptHandler implements Consumer<Event<AutomationEv
                 WorkflowUtils.save(scriptWrk, scriptWrk.buildEvent());
 
                 final AutomatedScriptRequest request = new AutomatedScriptRequest(automationEvent.getSrcStringifiedId(), automationEvent.getSrcEventClass(), user, script.getScriptId(), eventName,
-                                                                                  scriptWrk.getWorkflowId().toString(), automationEvent.getEntityType(), automationEvent.getSrcStringifiedId(), automationEvent.getExternalId(), automationEvent.getParameterMap());
+                                                                                  scriptWrk.getWorkflowId().toString(), automationEvent.getEntityType(), automationEvent.getSrcStringifiedId(), automationEvent.getExternalId(),
+                                                                                  automationEvent.getParameterMap(), automationCompletionEvent);
 
-                // We're running this here now, so we can return script output
-                //XDAT.sendJmsRequest(request);
-                final ScriptOutput scriptOut = executeScriptRequest(request);
-                if (automationCompletionEvent != null && scriptOut != null) {
-                    automationCompletionEvent.getScriptOutputs().add(scriptOut);
-                }
+                XDAT.sendJmsRequest(request);
             } catch (Exception e1) {
                 logger.error("Script launch exception", e1);
             }
         }
-        if (automationCompletionEvent != null) { 
-            if (_eventService != null) {
-                automationCompletionEvent.setEventCompletionTime(System.currentTimeMillis());
-                _eventService.triggerEvent(automationCompletionEvent);
-                List<String> notifyList = automationCompletionEvent.getNotificationList();
-                if (notifyList != null && !notifyList.isEmpty()) {
-                	final String scriptOut = 
-                	(automationCompletionEvent.getScriptOutputs() != null && automationCompletionEvent.getScriptOutputs().size() > 0) ?
-                			scriptOutputToHtmlString(automationCompletionEvent.getScriptOutputs()) :
-                				"<h3>No output was returned from the script run</h3>";	
-                    final String EMAIL_SUBJECT = "Automation Results";
-                    AdminUtils.sendUserHTMLEmail(EMAIL_SUBJECT, scriptOut, false, notifyList.toArray(new String[0]));
-                }
-            }
-        }
-    }
-
-    /**
-     * Script output to html string.
-     *
-     * @param scriptOutputs the script outputs
-     * @return the string
-     */
-    private String scriptOutputToHtmlString(List<ScriptOutput> scriptOutputs) {
-        if (scriptOutputs == null) {
-            return "";
-        }
-        StringBuilder sb = new StringBuilder();
-        for (ScriptOutput scriptOut : scriptOutputs) {
-            sb.append("<br><b>SCRIPT EXECUTION RESULTS</b><br>");
-            sb.append("<br><b>FINAL STATUS:  ").append(scriptOut.getStatus()).append("</b><br>");
-            if (scriptOut.getStatus().equals(Status.ERROR) && scriptOut.getResults() != null && scriptOut.getResults().toString().length() > 0) {
-                sb.append("<br><b>SCRIPT RESULTS</b><br>");
-                sb.append(scriptOut.getResults().toString().replace("\n", "<br>"));
-            }
-            if (scriptOut.getOutput() != null && scriptOut.getOutput().length() > 0) {
-                sb.append("<br><b>SCRIPT STDOUT</b><br>");
-                sb.append(scriptOut.getOutput().replace("\n", "<br>"));
-            }
-            if (scriptOut.getErrorOutput() != null && scriptOut.getErrorOutput().length() > 0) {
-                sb.append("<br><b>SCRIPT STDERR/EXCEPTION</b><br>");
-                sb.append(scriptOut.getErrorOutput().replace("\n", "<br>"));
-            }
-        }
-        return sb.toString();
     }
 
     /**
@@ -411,70 +364,4 @@ public class AutomationEventScriptHandler implements Consumer<Event<AutomationEv
         return scripts;
     }
 
-    /**
-     * Execute script request.
-     *
-     * @param request the request
-     *
-     * @return the script output
-     *
-     * @throws Exception the exception
-     */
-    private ScriptOutput executeScriptRequest(AutomatedScriptRequest request) throws Exception {
-        final PersistentWorkflowI workflow = WorkflowUtils.getUniqueWorkflow(request.getUser(), request.getScriptWorkflowId());
-        if (workflow == null) {
-            logger.warn("Didn't find the workflow indicated by an automated script request: " + request.getScriptWorkflowId() + " (requested by " + request.getUser().getUsername() + ")");
-            return null;
-        }
-
-        workflow.setStatus(PersistentWorkflowUtils.IN_PROGRESS);
-        WorkflowUtils.save(workflow, workflow.buildEvent());
-
-        final Map<String, Object> parameters = Maps.newHashMap();
-        parameters.put("user", request.getUser());
-        parameters.put("scriptId", request.getScriptId());
-        parameters.put("event", request.getEvent());
-        parameters.put("srcEventId", request.getSrcEventId());
-        parameters.put("srcEventClass", request.getSrcEventClass());
-        parameters.put("srcWorkflowId", request.getSrcEventId());
-        parameters.put("scriptWorkflowId", request.getScriptWorkflowId());
-        parameters.put("dataType", request.getDataType());
-        parameters.put("dataId", request.getDataId());
-        parameters.put("externalId", request.getExternalId());
-        parameters.put("workflow", workflow);
-        if (request.getArgumentMap() != null && !request.getArgumentMap().isEmpty()) {
-            parameters.putAll(request.getArgumentMap());
-        }
-
-        ScriptOutput scriptOut = null;
-        try {
-            scriptOut = _service.runScript(_service.getScript(request.getScriptId()), null, parameters, false);
-            if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
-                WorkflowUtils.complete(workflow, workflow.buildEvent());
-            }
-        } catch (NrgServiceException | NrgServiceRuntimeException e) {
-            final String message = String.format("Failed running the script %s by user %s for event %s on data type %s instance %s from project %s  (Exception=%s)",
-                                                 request.getScriptId(),
-                                                 request.getUser().getLogin(),
-                                                 request.getEvent(),
-                                                 request.getDataType(),
-                                                 request.getDataId(),
-                                                 request.getExternalId(),
-                                                 e.toString());
-            if (scriptOut==null) {
-            	scriptOut = new ScriptOutput();
-            	scriptOut.setStatus(Status.ERROR);
-            	scriptOut.setOutput(message);
-            }
-            if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
-                WorkflowUtils.fail(workflow, workflow.buildEvent());
-            }
-            AdminUtils.sendAdminEmail("Script execution failure", message);
-            logger.error(message, e);
-            if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
-                WorkflowUtils.fail(workflow, workflow.buildEvent());
-            }
-        }
-        return scriptOut;
-    }
 }
diff --git a/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java b/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java
index d4f2e81074eb65019a470b0ea81a4f75c669d0d6..4b717f6b528f5865e35ac65c0f831b7fe071bc6e 100644
--- a/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java
+++ b/src/main/java/org/nrg/xnat/initialization/DatabaseConfig.java
@@ -22,16 +22,6 @@ import java.util.Properties;
  */
 @Configuration
 public class DatabaseConfig {
-
-    public static final String DEFAULT_DATASOURCE_URL          = "jdbc:postgresql://localhost/xnat";
-    public static final String DEFAULT_DATASOURCE_USERNAME     = "xnat";
-    public static final String DEFAULT_DATASOURCE_PASSWORD     = "xnat";
-    public static final String DEFAULT_DATASOURCE_CLASS        = BasicDataSource.class.getName();
-    public static final String DEFAULT_DATASOURCE_DRIVER       = Driver.class.getName();
-    public static final String DEFAULT_DATASOURCE_INITIAL_SIZE = "20";
-    public static final String DEFAULT_DATASOURCE_MAX_TOTAL    = "40";
-    public static final String DEFAULT_DATASOURCE_MAX_IDLE     = "10";
-
     @Bean
     public DataSource dataSource(final Environment environment) throws NrgServiceException {
         final Properties properties = Beans.getNamespacedProperties(environment, "datasource", true);
@@ -117,4 +107,13 @@ public class DatabaseConfig {
     }
 
     private static final Logger _log = LoggerFactory.getLogger(DatabaseConfig.class);
+
+    private static final String DEFAULT_DATASOURCE_URL          = "jdbc:postgresql://localhost/xnat";
+    private static final String DEFAULT_DATASOURCE_USERNAME     = "xnat";
+    private static final String DEFAULT_DATASOURCE_PASSWORD     = "xnat";
+    private static final String DEFAULT_DATASOURCE_CLASS        = BasicDataSource.class.getName();
+    private static final String DEFAULT_DATASOURCE_DRIVER       = Driver.class.getName();
+    private static final String DEFAULT_DATASOURCE_INITIAL_SIZE = "20";
+    private static final String DEFAULT_DATASOURCE_MAX_TOTAL    = "40";
+    private static final String DEFAULT_DATASOURCE_MAX_IDLE     = "10";
 }
diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java b/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java
index fbc9f06307285389fe59adca04a19ef67beb7b47..da4903621481ce2efee196b11f80c4999093201c 100644
--- a/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java
+++ b/src/main/java/org/nrg/xnat/initialization/tasks/EncryptXnatPasswords.java
@@ -83,10 +83,11 @@ public class EncryptXnatPasswords extends AbstractInitializingTask {
             }
             return passwords;
         }
+
         final ShaPasswordEncoder encoder = new ShaPasswordEncoder(256);
     }
 
-    private static Logger logger = LoggerFactory.getLogger(EncryptXnatPasswords.class);
+    private static final Logger logger = LoggerFactory.getLogger(EncryptXnatPasswords.class);
 
     private final JdbcTemplate _template;
 }
diff --git a/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java b/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java
new file mode 100644
index 0000000000000000000000000000000000000000..c950f6ce928b1588b8b82ce38d89241478dbe3e0
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/initialization/tasks/MigrateDatabaseTables.java
@@ -0,0 +1,101 @@
+/*
+ * org.nrg.xnat.initialization.tasks.XnatPasswordEncrypter
+ * XNAT http://www.xnat.org
+ * Copyright (c) 2016, Washington University School of Medicine
+ * All Rights Reserved
+ *
+ * Released under the Simplified BSD.
+ */
+package org.nrg.xnat.initialization.tasks;
+
+import com.google.common.base.Joiner;
+import org.nrg.framework.orm.DatabaseHelper;
+import org.nrg.framework.utilities.BasicXnatResourceLocator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+@Component
+public class MigrateDatabaseTables extends AbstractInitializingTask {
+    @Autowired
+    public MigrateDatabaseTables(final JdbcTemplate template, final TransactionTemplate transactionTemplate) {
+        super();
+        _db = new DatabaseHelper(template, transactionTemplate);
+    }
+
+    @Override
+    public String getTaskName() {
+        return "Migrate XNAT database tables";
+    }
+
+    @Override
+    public void run() {
+        try {
+            final Map<String, Map<String, String>> tables = new HashMap<>();
+            for (final Resource resource : BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/migration/**/*-tables.properties")) {
+                final Properties properties = PropertiesLoaderUtils.loadProperties(resource);
+                for (final String property : properties.stringPropertyNames()) {
+                    final String[] atoms = property.split("\\.", 2);
+                    if (atoms.length < 2) {
+                        _log.error("The properties file {} contains a malformed key: {}. Keys in table migration properties files should take the form: \"table.column=column_type\".", resource.getFilename(), property);
+                        continue;
+                    }
+
+                    final String table  = atoms[0];
+                    final String column = atoms[1];
+
+                    final Map<String, String> columns;
+                    if (tables.containsKey(table)) {
+                        columns = tables.get(table);
+                    } else {
+                        columns = new HashMap<>();
+                        tables.put(table, columns);
+                    }
+                    if (columns.containsKey(column)) {
+                        _log.error("The properties for table {} defines the column {} as column type {}. This column has already been defined elsewhere as type: {}.", table, column, columns.get(column));
+                        continue;
+                    }
+                    columns.put(column, properties.getProperty(property));
+                }
+            }
+            for (final String table : tables.keySet()) {
+                final Map<String, String> columns = tables.get(table);
+                for (final String column : columns.keySet()) {
+                    final String value = columns.get(column);
+                    try {
+                        _db.setColumnDatatype(table, column, value);
+                    } catch (SQLWarning e) {
+                        final String message = e.getMessage();
+                        if (message.startsWith(SQL_WARNING_TABLE)) {
+                            _log.error("The table {} was defined, but that table doesn't appear to exist in the database. The following columns were to be checked: {}", table, Joiner.on(", ").join(columns.keySet()));
+                        } else {
+                            _log.error("The column {}.{} was defined, but that column doesn't appear to exist. Note that the table migration does not create new columns. The column was defined as: {}", table, column, value);
+                        }
+                    }
+                }
+            }
+            complete();
+        } catch (IOException e) {
+            _log.error("An error occurred attempting to read table migration properties files", e);
+        } catch (SQLException e) {
+            _log.error("An error occurred accessing the database", e);
+        }
+    }
+
+    private static final Logger _log              = LoggerFactory.getLogger(MigrateDatabaseTables.class);
+    private static final String SQL_WARNING_TABLE = "The requested table";
+
+    private final DatabaseHelper _db;
+}
diff --git a/src/main/java/org/nrg/xnat/preferences/AutomationPreferences.java b/src/main/java/org/nrg/xnat/preferences/AutomationPreferences.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e1f88846ef049f1e2aeb7fdbe083220059b90bd
--- /dev/null
+++ b/src/main/java/org/nrg/xnat/preferences/AutomationPreferences.java
@@ -0,0 +1,39 @@
+package org.nrg.xnat.preferences;
+
+import org.nrg.framework.services.NrgEventService;
+import org.nrg.prefs.annotations.NrgPreference;
+import org.nrg.prefs.annotations.NrgPreferenceBean;
+import org.nrg.prefs.exceptions.InvalidPreferenceName;
+import org.nrg.xdat.preferences.EventTriggeringAbstractPreferenceBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@NrgPreferenceBean(toolId = AutomationPreferences.AUTOMATION_TOOL_ID,
+                   toolName = "XNAT Automation Preferences",
+                   description = "Manages preferences and settings for the XNAT automation services.",
+                   properties = "META-INF/xnat/preferences/automation.properties",
+                   strict = false)
+public class AutomationPreferences extends EventTriggeringAbstractPreferenceBean {
+    public static final String AUTOMATION_TOOL_ID = "automation";
+
+    @Autowired
+    public AutomationPreferences(final NrgEventService eventService) {
+        super(eventService);
+    }
+
+    @NrgPreference(defaultValue = "true")
+    public boolean isInternalScriptingEnabled() {
+        return getBooleanValue("internalScriptingEnabled");
+    }
+
+    public void setInternalScriptingEnabled(final boolean internalScriptingEnabled) {
+        try {
+            setBooleanValue(internalScriptingEnabled, "internalScriptingEnabled");
+        } catch (InvalidPreferenceName e) {
+            _log.error("Invalid preference name internalScriptingEnabled: something is very wrong here.", e);
+        }
+    }
+
+    private static final Logger _log = LoggerFactory.getLogger(AutomationPreferences.class);
+}
diff --git a/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java b/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java
index ab66f40b14bd3c90299eb2d8c2444b57f2b41f8e..8d2adf27114af9da1f4999fac872bba6e96b65b6 100644
--- a/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java
+++ b/src/main/java/org/nrg/xnat/restlet/resources/SecureResource.java
@@ -1544,7 +1544,7 @@ public abstract class SecureResource extends Resource {
 
     protected final static TypeReference<ArrayList<String>> TYPE_REFERENCE_LIST_STRING = new TypeReference<ArrayList<String>>() {};
 
-    private static Map<String, List<FilteredResourceHandlerI>> handlers = Maps.newConcurrentMap();
+    private static final Map<String, List<FilteredResourceHandlerI>> handlers = Maps.newConcurrentMap();
 
     /**
      * Get a list of the possible handlers.  This allows additional handlers to be injected at a later date or via a module.
@@ -1555,19 +1555,21 @@ public abstract class SecureResource extends Resource {
      */
     public static List<FilteredResourceHandlerI> getHandlers(String _package, List<FilteredResourceHandlerI> _defaultHandlers) throws InstantiationException, IllegalAccessException {
         if (handlers.get(_package) == null) {
-            handlers.put(_package, _defaultHandlers);
+            synchronized (handlers) {
+                handlers.put(_package, _defaultHandlers);
 
-            //ordering here is important.  the last match wins
-            List<Class<?>> classes;
-            try {
-                classes = Reflection.getClassesForPackage(_package);
-            } catch (Exception exception) {
-                throw new RuntimeException(exception);
-            }
+                //ordering here is important.  the last match wins
+                List<Class<?>> classes;
+                try {
+                    classes = Reflection.getClassesForPackage(_package);
+                } catch (Exception exception) {
+                    throw new RuntimeException(exception);
+                }
 
-            for (Class<?> clazz : classes) {
-                if (FilteredResourceHandlerI.class.isAssignableFrom(clazz)) {
-                    handlers.get(_package).add((FilteredResourceHandlerI) clazz.newInstance());
+                for (Class<?> clazz : classes) {
+                    if (FilteredResourceHandlerI.class.isAssignableFrom(clazz)) {
+                        handlers.get(_package).add((FilteredResourceHandlerI) clazz.newInstance());
+                    }
                 }
             }
         }
diff --git a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java
index c6204cebb86d50eac498735689fe7e80ecd4f789..a0c0e286a7f4549d896b840ce2ec8df64f69d3b1 100644
--- a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java
+++ b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequest.java
@@ -1,6 +1,7 @@
 package org.nrg.xnat.services.messaging.automation;
 
 import org.json.JSONObject;
+import org.nrg.automation.event.AutomationCompletionEventI;
 import org.nrg.xft.security.UserI;
 
 import com.google.common.collect.Maps;
@@ -46,6 +47,8 @@ public class AutomatedScriptRequest implements Serializable {
 	/** The _argument map. */
 	private final Map<String,Object> _argumentMap = Maps.newHashMap();
 	
+	private final AutomationCompletionEventI _automationCompletionEvent;
+
 	/**
 	 * Instantiates a new automated script request.
 	 *
@@ -58,8 +61,12 @@ public class AutomatedScriptRequest implements Serializable {
 	 * @param dataType the data type
 	 * @param dataId the data id
 	 * @param externalId the external id
+	 * @param argumentMap the argument map
+	 * @param automationCompletionEvent the automation completion event
 	 */
-	public AutomatedScriptRequest(final String srcEventId, final String srcEventClass, final UserI user, final String scriptId, final String event, final String scriptWorkflow, final String dataType, final String dataId, final String externalId) {
+	public AutomatedScriptRequest(String srcEventId, String srcEventClass, UserI user, String scriptId,
+			String event, String scriptWorkflow, String dataType, String dataId, String externalId,
+			Map<String, Object> argumentMap, AutomationCompletionEventI automationCompletionEvent) {
 		_srcEventId = srcEventId;
 		_srcEventClass = srcEventClass;
 		_user = user;
@@ -69,6 +76,27 @@ public class AutomatedScriptRequest implements Serializable {
 		_dataType = dataType;
 		_dataId = dataId;
 		_externalId = externalId;
+		_automationCompletionEvent = automationCompletionEvent;
+		if (argumentMap != null) {
+			_argumentMap.putAll(argumentMap);
+		}
+	}
+	
+	/**
+	 * Instantiates a new automated script request.
+	 *
+	 * @param srcEventId the src event id
+	 * @param srcEventClass the src event class
+	 * @param user the user
+	 * @param scriptId the script id
+	 * @param event the event
+	 * @param scriptWorkflow the script workflow
+	 * @param dataType the data type
+	 * @param dataId the data id
+	 * @param externalId the external id
+	 */
+	public AutomatedScriptRequest(final String srcEventId, final String srcEventClass, final UserI user, final String scriptId, final String event, final String scriptWorkflow, final String dataType, final String dataId, final String externalId) {
+		this(srcEventId, srcEventClass, user, scriptId, event, scriptWorkflow, dataType, dataId, externalId, null, null);
 	}
 	
 	/**
@@ -86,10 +114,7 @@ public class AutomatedScriptRequest implements Serializable {
 	 * @param argumentMap the argument map
 	 */
 	public AutomatedScriptRequest(final String srcEventId, final String srcEventClass, final UserI user, final String scriptId, final String event, final String scriptWorkflow, final String dataType, final String dataId, final String externalId, Map<String,Object> argumentMap) {
-		this(srcEventId, srcEventClass, user, scriptId, event, scriptWorkflow, dataType, dataId, externalId);
-		if (argumentMap != null) {
-			_argumentMap.putAll(argumentMap);
-		}
+		this(srcEventId, srcEventClass, user, scriptId, event, scriptWorkflow, dataType, dataId, externalId, argumentMap, null);
 	}	
 
 	/**
@@ -181,6 +206,16 @@ public class AutomatedScriptRequest implements Serializable {
 	public Map<String,Object> getArgumentMap() {
 		return _argumentMap;
 	}
+	
+	
+	/**
+	 * Gets the automation completion event.
+	 *
+	 * @return the automation completion event
+	 */
+	public AutomationCompletionEventI getAutomationCompletionEvent() {
+		return _automationCompletionEvent;
+	}
 
 	/**
 	 * Gets the argument json.
diff --git a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java
index 27386704d0be736bee76639627c865dae5039e6f..ea7ebaded96f2c37432267578e85a47e97bc1625 100644
--- a/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java
+++ b/src/main/java/org/nrg/xnat/services/messaging/automation/AutomatedScriptRequestListener.java
@@ -1,7 +1,11 @@
 package org.nrg.xnat.services.messaging.automation;
 
+import org.nrg.automation.entities.ScriptOutput;
+import org.nrg.automation.entities.ScriptOutput.Status;
+import org.nrg.automation.event.AutomationCompletionEventI;
 import org.nrg.automation.services.ScriptRunnerService;
 import org.nrg.framework.exceptions.NrgServiceException;
+import org.nrg.framework.services.NrgEventService;
 import org.nrg.xdat.turbine.utils.AdminUtils;
 import org.nrg.xft.event.persist.PersistentWorkflowI;
 import org.nrg.xft.event.persist.PersistentWorkflowUtils;
@@ -11,6 +15,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -36,6 +41,7 @@ public class AutomatedScriptRequestListener {
         final PersistentWorkflowI workflow = WorkflowUtils.getUniqueWorkflow(request.getUser(), request.getScriptWorkflowId());
         workflow.setStatus(PersistentWorkflowUtils.IN_PROGRESS);
         WorkflowUtils.save(workflow, workflow.buildEvent());
+        final AutomationCompletionEventI automationCompletionEvent = request.getAutomationCompletionEvent();
 
         final Map<String, Object> parameters = new HashMap<>();
         parameters.put("user", request.getUser());
@@ -54,11 +60,18 @@ public class AutomatedScriptRequestListener {
         parameters.put("externalId", request.getExternalId());
         parameters.put("workflow", workflow);
         parameters.put("arguments", request.getArgumentJson());
+        if (request.getArgumentMap() != null && !request.getArgumentMap().isEmpty()) {
+            parameters.putAll(request.getArgumentMap());
+        }
+        ScriptOutput scriptOut = null;
         try {
-            _service.runScript(_service.getScript(request.getScriptId()), parameters);
+            scriptOut = _service.runScript(_service.getScript(request.getScriptId()), parameters);
             if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
                 WorkflowUtils.complete(workflow, workflow.buildEvent());
             }
+            if (automationCompletionEvent != null && scriptOut != null) {
+                automationCompletionEvent.getScriptOutputs().add(scriptOut);
+            }
         } catch (NrgServiceException e) {
             final String message = String.format("Failed running the script %s by user %s for event %s on data type %s instance %s from project %s",
                     request.getScriptId(),
@@ -67,12 +80,62 @@ public class AutomatedScriptRequestListener {
                     request.getDataType(),
                     request.getDataId(),
                     request.getExternalId());
+            if (scriptOut==null) {
+            	scriptOut = new ScriptOutput();
+            	scriptOut.setStatus(Status.ERROR);
+            	scriptOut.setOutput(message);
+            }
             AdminUtils.sendAdminEmail("Script execution failure", message);
             logger.error(message, e);
             if (PersistentWorkflowUtils.IN_PROGRESS.equals(workflow.getStatus())) {
                 WorkflowUtils.fail(workflow, workflow.buildEvent());
             }
         }
+        if (automationCompletionEvent != null) {
+            if (_eventService != null) {
+                automationCompletionEvent.setEventCompletionTime(System.currentTimeMillis());
+                _eventService.triggerEvent(automationCompletionEvent);
+                final List<String> notifyList = automationCompletionEvent.getNotificationList();
+                if (notifyList != null && !notifyList.isEmpty()) {
+                	final String scriptOutStr = 
+                 	(automationCompletionEvent.getScriptOutputs() != null && automationCompletionEvent.getScriptOutputs().size() > 0) ?
+                   			scriptOutputToHtmlString(automationCompletionEvent.getScriptOutputs()) :
+                   				"<h3>No output was returned from the script run</h3>";	
+                    final String EMAIL_SUBJECT = "Automation Results";
+                    AdminUtils.sendUserHTMLEmail(EMAIL_SUBJECT, scriptOutStr, false, notifyList.toArray(new String[0]));
+                }
+            }
+        }
+    }
+    
+    /**
+     * Script output to html string.
+     *
+     * @param scriptOutputs the script outputs
+     * @return the string
+     */
+    private String scriptOutputToHtmlString(List<ScriptOutput> scriptOutputs) {
+        if (scriptOutputs == null) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (ScriptOutput scriptOut : scriptOutputs) {
+            sb.append("<br><b>SCRIPT EXECUTION RESULTS</b><br>");
+            sb.append("<br><b>FINAL STATUS:  ").append(scriptOut.getStatus()).append("</b><br>");
+            if (scriptOut.getStatus().equals(Status.ERROR) && scriptOut.getResults() != null && scriptOut.getResults().toString().length() > 0) {
+                sb.append("<br><b>SCRIPT RESULTS</b><br>");
+                sb.append(scriptOut.getResults().toString().replace("\n", "<br>"));
+            }
+            if (scriptOut.getOutput() != null && scriptOut.getOutput().length() > 0) {
+                sb.append("<br><b>SCRIPT STDOUT</b><br>");
+                sb.append(scriptOut.getOutput().replace("\n", "<br>"));
+            }
+            if (scriptOut.getErrorOutput() != null && scriptOut.getErrorOutput().length() > 0) {
+                sb.append("<br><b>SCRIPT STDERR/EXCEPTION</b><br>");
+                sb.append(scriptOut.getErrorOutput().replace("\n", "<br>"));
+            }
+        }
+        return sb.toString();
     }
 
     /** The Constant logger. */
@@ -81,4 +144,8 @@ public class AutomatedScriptRequestListener {
     /** The _service. */
     @Inject
     private ScriptRunnerService _service;
+	
+    /** The _event service. */
+    @Inject
+	private NrgEventService _eventService;
 }
diff --git a/src/main/java/org/nrg/xnat/turbine/modules/actions/AcceptProjectAccess.java b/src/main/java/org/nrg/xnat/turbine/modules/actions/AcceptProjectAccess.java
index 86a61a48fd8dafafa21e0323a0eb53c30c20bc60..d3ee58d874a3e42a520abeb38cb918a519ddbf25 100644
--- a/src/main/java/org/nrg/xnat/turbine/modules/actions/AcceptProjectAccess.java
+++ b/src/main/java/org/nrg/xnat/turbine/modules/actions/AcceptProjectAccess.java
@@ -10,8 +10,6 @@
  */
 package org.nrg.xnat.turbine.modules.actions;
 
-import java.util.List;
-
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.turbine.Turbine;
@@ -24,13 +22,15 @@ import org.nrg.xdat.turbine.utils.TurbineUtils;
 import org.nrg.xft.security.UserI;
 import org.nrg.xnat.turbine.utils.ProjectAccessRequest;
 
+import java.util.List;
+
 public class AcceptProjectAccess extends SecureAction {
 
 	@Override
 	public void doPerform(RunData data, Context context) throws Exception {
-		UserI user = XDAT.getUserDetails();
+		UserI user = (UserI) context.get("user"); //Check the user in the context before XDAT.getUserDetails() because XDAT.getUserDetails() may still be the guest user at this point.
 		if (user == null) {
-			user = (UserI) context.get("user");
+			user = XDAT.getUserDetails();
         }
 		if (user.getUsername().equals("guest")) {
 			data.getParameters().add("nextPage", data.getTemplateInfo().getScreenTemplate());
diff --git a/src/main/resources/META-INF/xnat/migration/xnat-1.6-tables.properties b/src/main/resources/META-INF/xnat/migration/xnat-1.6-tables.properties
new file mode 100644
index 0000000000000000000000000000000000000000..d7e0df9a0e3c9396d806467133b24728b38eb7c5
--- /dev/null
+++ b/src/main/resources/META-INF/xnat/migration/xnat-1.6-tables.properties
@@ -0,0 +1 @@
+xhbm_alias_token.secret=varchar(255)
\ No newline at end of file
diff --git a/src/main/resources/META-INF/xnat/preferences/automation.properties b/src/main/resources/META-INF/xnat/preferences/automation.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1724138ee1372818dba1f457c6d3d2a8984b5229
--- /dev/null
+++ b/src/main/resources/META-INF/xnat/preferences/automation.properties
@@ -0,0 +1,2 @@
+# Declares default values for the automation service.
+internalScriptingEnabled=true
diff --git a/src/main/resources/config/site/siteConfiguration.properties b/src/main/resources/META-INF/xnat/preferences/site-config.properties
similarity index 100%
rename from src/main/resources/config/site/siteConfiguration.properties
rename to src/main/resources/META-INF/xnat/preferences/site-config.properties
diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
index aa8e83ea147d18e1cc7c479e0cf970724c983631..3005662d87379ced5542292deed0693d2fba8f46 100644
--- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -27,34 +27,42 @@ siteDescriptionPage:
         type: text
         name: siteDescriptionPage
         size: 30
-    after: "<p class='description'>Specify a velocity template file to display on the login page</p>"
+    after: >
+        <p class="description">Specify a velocity template file to display on the login page</p>
 
 siteDescriptionText:
     tag: textarea|data-code-editor;data-code-language=html
     element:
         name: siteDescriptionText
         rows: 8
-    after: "<p class='description'>Specify a simple text description of this site.</p>"
+    after: >
+        <p class="description">Specify a simple text description of this site.</p>
 
 passwordExpirationInterval:
     tag: input
-    before: '<br>'
     element:
         type: text
         id: passwordExpirationInterval
         name: passwordExpirationInterval
         label: Password Expiration (Interval)
     after: >
-      <p class='description'>Interval of time after which unchanged passwords expire and users have to change them. Uses <a target="_blank" href="http://www.postgresql.org/docs/9.0/static/functions-datetime.html">PostgreSQL interval notation</a></p>
+      <p class="description">Interval of time after which unchanged passwords expire and users have to change them.
+      Uses <a target="_blank" href="http://www.postgresql.org/docs/9.0/static/functions-datetime.html">PostgreSQL interval notation</a></p>
 
 passwordExpirationDate:
     tag: input
-    before: '<br>'
     element:
         type: text
         id: passwordExpirationDate
         name: passwordExpirationDate
         label: Password Expiration (Date)
+    after:
+        datePicker:
+            tag: span#datePicker
+            content: >
+              &nbsp;&nbsp;<span id='openCal-passwordExpirationDate' class='icon icon-sm icon-calendar'></span>
+              <br>
+              <p class='description'>Expire passwords that were last changed before this date and require those users to change them.</p>
 
 siteDescriptionType:
     kind: panel.element
@@ -435,22 +443,17 @@ passwords:
                             tag: label.pad5h|for=passwordExpirationTypeDate
                             content: Date
                 passwordExpirationInterval:
-                    tag: div.input-bundle.interval.inline
+                    tag: div.input-bundle.interval
                     contents:
                         ${passwordExpirationInterval}
                 passwordExpirationDate:
-                    tag: div.input-bundle.date.inline
-                    after:
-                        label:
-                            tag: span#datePicker
-                            content: >
-                              &nbsp;&nbsp;<span id='openCal-passwordExpirationDate' class='icon icon-sm icon-calendar'></span><br><p class='description'>Expire passwords that were last changed before this date and require those users to change them.</p>
+                    tag: div.input-bundle.date
                     contents:
                         ${passwordExpirationDate}
                 pwExpTypeJs:
                     tag: script
                     element:
-                        src: ~/scripts/xnat/admin/pwExpType.js
+                        src: ~/scripts/xnat/admin/passwords.js
         passwordReuseRestriction:
             kind: panel.select.single
             id: passwordReuseRestriction
@@ -1167,6 +1170,21 @@ fileSystem:
         ${checksums}
 #        ${checksumsPropertyChangedListener}
 
+automationSettings:
+    kind: panel.form
+    name: automationSettings
+    label: Automation
+    method: POST
+    contentType: json
+    action: /xapi/automation
+    load: /xapi/automation
+    contents:
+        internalScriptingEnabled:
+            kind: panel.input.checkbox
+            name: internalScriptingEnabled
+            label: Internal Scripting Enabled
+            description: "When enabled, administrators can create and edit scripts that run internally to the XNAT process. This can be a powerful feature, but also can pose security hazards that may be unacceptable for certain deployments. For these situations, configurable internal scripting can be disabled (XNAT itself may still use some scripting for feature implementation, but these scripts can not be modified or updated by users)."
+
 misc:
     kind: panel.form
     name: misc
@@ -1324,6 +1342,13 @@ adminPage:
             group: advanced
             contents:
                 ${fileSystem}
+        automationTab:
+            kind: tab
+            name: automationTab
+            label: Automation
+            group: advanced
+            contents:
+                ${automationSettings}
         misc:
             kind: tab
             name: misc
diff --git a/src/main/webapp/scripts/project/projResourceMgmt.js b/src/main/webapp/scripts/project/projResourceMgmt.js
index d8b71d1273120a4cc14c4b30a13bfe32af7a472e..70944351714414799552727e93954cc627854ff4 100644
--- a/src/main/webapp/scripts/project/projResourceMgmt.js
+++ b/src/main/webapp/scripts/project/projResourceMgmt.js
@@ -157,7 +157,8 @@ XNAT.app.pResources={
 					      			scriptId: scriptToRun,
 					      			eventClass: "org.nrg.xft.event.entities.WorkflowStatusEvent",
 					      			filters: { "status":["Complete"] },
-					      			description: "Run " + scriptToRun + " upon " + props.name + " upload." };
+					      			//description: "Run " + scriptToRun + " upon " + props.name + " upload." };
+					      			description: props.name + " --> " + scriptToRun };
 					var eventHandlerAjax = $.ajax({
 						type : "PUT",
 						url:serverRoot+"/data/projects/" + this.id + "/automation/handlers?XNAT_CSRF=" + window.csrfToken,
diff --git a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js
index c8cad9d783ab720578c3bf22a0cea3439e084d92..459d59afbb213b4552e16f3e21c35fd62443b056 100644
--- a/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js
+++ b/src/main/webapp/scripts/uploaders/AutomationBasedUploader.js
@@ -93,6 +93,25 @@ if(typeof XNAT.app.abu.abuConfigs === 'undefined'){
 				}
 			}
 			
+			// NOTE: $.inArray didn't work here
+			var alreadyContains = function(array, val){
+				outer:
+				for (var i=0; i<array.length; i++) {
+					if (Object.keys(val).length !== Object.keys(array[i]).length) {
+						continue;
+					}
+					for (var j=0; j<Object.keys(val).length; j++) {
+						var key = (Object.keys(val))[j];
+						var tval = val[key];
+						var cval = (array[i])[key];
+						if (tval !== cval) {
+							continue outer;
+						}
+					}
+					return true;
+				}
+				return false;
+			};
 			var tempConfigs2=new Array();
 			//allow filtering of links
 			jq.each(tmpConfigs,function(i1,v1){
@@ -106,15 +125,15 @@ if(typeof XNAT.app.abu.abuConfigs === 'undefined'){
 							}
 						}
 					});
-					if(matched){
+					if(matched && (!alreadyContains(tempConfigs2,v1))){
 						tempConfigs2.push(v1);
 					}
-				}else{
+				}else if (!alreadyContains(tempConfigs2,v1)){
 					tempConfigs2.push(v1);
 				}
 			});
 			return tempConfigs2;
-		}
+		},
 
 
 	}
@@ -452,7 +471,15 @@ XNAT.app.abu.populateWhatToDoSelect = function(){
 								}
 								if (doAssign) {
 									$('#whatToDoSelect').append('<option value="resource- ' + resourceConfigs[h].name + ':launch-' + currEvent.triggerId + '" class="' + currEvent.scope + '">' + 
-				                                                ((typeof resourceConfigs[h].description !== 'undefined' && resourceConfigs[h].description.length>0) ? resourceConfigs[h].description : resourceConfigs[h].name) + " --> " + 
+				                                                (
+											(typeof currEvent == 'undefined' || typeof currEvent.description == 'undefined' || currEvent.description.indexOf("-->")<0)
+											/*
+											Per XNAT-4333, Use name rather than description
+ 											? (((typeof resourceConfigs[h].description !== 'undefined' && resourceConfigs[h].description.length>0) ? resourceConfigs[h].description : resourceConfigs[h].name) + " --> ") : ""
+											*/
+ 											? (resourceConfigs[h].name + " --> ") : ""
+										) +
+
 									 ((typeof(currEvent.description) !== 'undefined' && currEvent.description.length>0) ? currEvent.description : currEvent.scriptId) + '</option>');
 									resourceMatch = true;
 								}
@@ -468,7 +495,11 @@ XNAT.app.abu.populateWhatToDoSelect = function(){
 				}
 			} 
 			if (!resourceMatch) {
+				/*
+				Per XNAT-4333, Use name rather than description
 				$("#whatToDoSelect").append('<option value="resource-' + resourceConfigs[h].name + '">' + ((typeof resourceConfigs[h].description !== 'undefined' && resourceConfigs[h].description.length>0) ? resourceConfigs[h].description : resourceConfigs[h].name) + '</option>');
+				*/
+				$("#whatToDoSelect").append('<option value="resource-' + resourceConfigs[h].name + '">' + resourceConfigs[h].name + '</option>');
 			}
 		}
 	} 
@@ -571,10 +602,15 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){
 							XNAT.app.abu.sendWorkflowWhenDone();
 						}
 					}
+					var isCancel = ($(".abu-done-button-cancel").length>0);
+					var fileUploaded = ($(".abu-done-button-file-uploaded").length>0);
 					xmodal.close(XNAT.app.abu.abuConfigs.modalOpts.id);
-					//if (abu._fileUploader.uploadsStarted>0 && abu._fileUploader.uploadsInProgress==0) {
-					//	window.location.reload(true);
-					//}
+					if (abu._fileUploader.uploadsStarted>0 && abu._fileUploader.uploadsInProgress==0 && !isCancel && fileUploaded) {
+						setTimeout(function(){
+							window.location.reload(true);
+						},20);
+						xmodal.message('Please Wait','Reloading page.  Please wait.');
+					}
 				},
 			uploadStartedFunction:function(){
 					if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)>=0 && (typeof(XNAT.app.abu.buildPath) == 'undefined' || XNAT.app.abu.buildPath == '')) {
@@ -594,11 +630,18 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){
 			uploadCompletedFunction:function(){
 					var eventHandler = $('#eventHandlerSelect').val();
 					if (typeof eventHandler !== 'undefined' && eventHandler != null && eventHandler.length>0) {
+						if ($(".abu-upload-complete-text").length==0) {
+							$("#abu-done-button").removeClass("abu-button-disabled");
+						} else {
+							$("#abu-done-button").addClass("abu-button-disabled");
+						}
 						$("#abu-process-button").removeClass("abu-button-disabled");
 						$("#abu-process-button-text").html("Process Files");
 						$("#abu-process-button").css("visibility","visible");
 					} else {
 						$("#abu-done-button-text").html("Done");
+						$("#abu-done-button-text").addClass("abu-done-button-done");
+						$("#abu-done-button-text").removeClass("abu-done-button-cancel");
 						$("#abu-done-button").removeClass("abu-button-disabled");
 					}
 				},
@@ -625,6 +668,8 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){
 				} else {
 					$("#abu-done-button").removeClass("abu-button-disabled");
 					$("#abu-done-button-text").html("Done");
+					$("#abu-done-button-text").addClass("abu-done-button-done");
+					$("#abu-done-button-text").removeClass("abu-done-button-cancel");
 				}
 				$(".upload-area").css("display","none");
 				$(".whattodo-area").css("display","none");
@@ -633,6 +678,8 @@ XNAT.app.abu.initializeAbuUploader = function(usageType){
 				$("#abu-upload-button").css("display","none");
 				$("#abu-process-button-text").html("Run script");
 				$("#abu-done-button-text").html("Cancel");
+				$("#abu-done-button-text").addClass("abu-done-button-cancel");
+				$("#abu-done-button-text").removeClass("abu-done-button-done");
 				if ($('#eventHandlerSelect option').size()>1 && $('#eventHandlerSelect').val()=="") {
 					$("#abu-process-button").addClass("abu-button-disabled");
 					//$("#abu-process-button-text").html("&nbsp;");
@@ -768,7 +815,7 @@ XNAT.app.abu.validatePassedParameters=function() {
 			if (this.paramsToPass[j].name==paramText) {
 				var paramVal = $($(".passParamInput").get(i)).val();
 				paramData[paramText] = paramVal;
-				var paramRequired = (this.paramsToPass[j].required==undefined || this.paramsToPass[j].required.toString().toLowerCase() != 'false') ? true : false;
+				var paramRequired = (this.paramsToPass[j].required==undefined || this.paramsToPass[j].required.toString().toLowerCase() != 'false');
 				if (paramRequired && (typeof(paramVal)=='undefined' || paramVal.length<1)) {
 					errorText+="ERROR:  " + paramText + " is required;  ";
 				}
@@ -797,7 +844,7 @@ XNAT.app.abu.updateResourceStats=function() {
 		if (abu._fileUploader._currentAction.indexOf("import-handler=" + XNAT.app.abu.importHandler)<0) {
 			var updateStatsUrl = "/data/services/refresh/catalog?resource=" + 
 				abu._fileUploader._currentAction.replace(/\/files[\/?].*$/i,'').replace(/^\/data\//i,"/archive/").replace(/^\/REST\//i,"/archive/" +
-				"&options=populateStats") + "&XNAT_CSRF=" + window.csrfToken;
+				"&options=populateStats") + "&options=populateStats,append,delete,checksum&XNAT_CSRF=" + window.csrfToken;
 			var updateStatsAjax = $.ajax({
 				type : "POST",
 				url:serverRoot+updateStatsUrl,
@@ -1028,7 +1075,8 @@ XNAT.app.abu.continueProcessing=function() {
 
 }
 XNAT.app.abu.processReturnedText = function(ins,returnToBr){
-	return ins.replace(/[\r\n]+/g,((returnToBr) ? "<br>" : '')).replace(/\/$/,'').replace(/<\/?script>/,"script");
+	return (typeof ins !== "undefined" && ins != null) ?
+		ins.toString().replace(/[\r\n]+/g,((returnToBr) ? "<br>" : '')).replace(/\/$/,'').replace(/<\/?script>/,"script") : "";
 }
 
 XNAT.app.abu.removeUploaderConfiguration=function(configEvent,scope) {
diff --git a/src/main/webapp/scripts/uploaders/fileuploader.js b/src/main/webapp/scripts/uploaders/fileuploader.js
index c00698bc354151bc9f30a0f83551af2c6e58f928..df079ef852d85fd8520e25ab514548ac59493fd1 100644
--- a/src/main/webapp/scripts/uploaders/fileuploader.js
+++ b/src/main/webapp/scripts/uploaders/fileuploader.js
@@ -21,6 +21,8 @@ abu.FileUploader = function(o){
 	this.DRAG_AND_DROP_ON = true;
 	this.uploadsInProgress = 0;
 	this.uploadsStarted = 0;
+	this.overwriteConfirmIssued = false;
+	this.doOverwrite = false;
 	$(this._options.element).html(""); 
 
 	this.buildUploaderDiv = function() {
@@ -35,7 +37,7 @@ abu.FileUploader = function(o){
 				'Upload files<input multiple="multiple" type="file" id="file-upload-input" class="abu-button-input">' + 
 			'</div>' +
 			'<div id="abu-done-button" class="abu-done-button" style="position: relative; overflow: hidden; direction: ltr;">' + 
-				'<span id="abu-done-button-text">Cancel</span><input type="image" name="done" class="abu-button-input" style="width:105px">' +
+				'<span id="abu-done-button-text" class="abu-done-button-cancel">Cancel</span><input type="image" name="done" class="abu-button-input" style="width:105px">' +
 			'</div>' +
 			'<div id="abu-process-button" class="abu-process-button " style="position: relative; overflow: hidden; direction: ltr;">' +
 				'<span id="abu-process-button-text">Process Files</span>' +
@@ -80,6 +82,7 @@ abu.FileUploader = function(o){
 		); 
 		$("#abu-upload-button").mouseenter(function() { $(this).addClass("abu-upload-button-hover"); });
 		$("#abu-upload-button").mouseleave(function() { $(this).removeClass("abu-upload-button-hover"); });
+		$("#abu-upload-button").click(function() { $("#abu-done-button").removeClass("abu-button-disabled"); });
 		$("#abu-done-button").click(this._options.doneFunction);
 		$("#abu-done-button").mouseenter(function() { $(this).addClass("abu-done-button-hover"); });
 		$("#abu-done-button").mouseleave(function() { $(this).removeClass("abu-done-button-hover"); });
@@ -177,9 +180,12 @@ abu.FileUploader = function(o){
 		}	
 		$("#file-upload-input").change(function(eventData) {
 			this._options.uploadStartedFunction();
-			var fileA = eventData.target.files;
+			this.overwriteConfirmIssued = false;
 			if (typeof eventData.target.files !== 'undefined') {
 				var fileA = eventData.target.files;
+				if (fileA.length==0) {
+					$("#abu-done-button").removeClass("abu-button-disabled");
+				} 
 				this.doFileUpload(fileA);
 			}
 		}.bind(this));
@@ -187,6 +193,8 @@ abu.FileUploader = function(o){
 
 	this.processingComplete = function() {
 		$("#abu-done-button-text").html("Done");
+		$("#abu-done-button-text").addClass("abu-done-button-done");
+		$("#abu-done-button-text").removeClass("abu-done-button-cancel");
 		//$("#abu-upload-button").css("display","None");
 		$("#abu-process-button").addClass("abu-button-disabled");
 		//$("#abu-process-button-text").html("&nbsp;");
@@ -248,6 +256,45 @@ abu.FileUploader = function(o){
 		$(formSelector).on("submit",function(e, uploader) {
 			 $(this).ajaxSubmit({
 				beforeSend: function(arr, $form, options) {
+					var formURL = $form.url;
+					if (typeof formURL !== 'undefined' && formURL.toLowerCase().indexOf("overwrite=true")>=0 && formURL.indexOf("/files")>0) {
+						// See if file already exists
+						var dupURL = formURL.substring(0,formURL.indexOf("/files")+6) + "?format=json";
+						this.fileName = formURL.substring(formURL.indexOf("/files")+7);
+						if (this.fileName.indexOf("?")>0) {
+							this.fileName = this.fileName.substring(0,this.fileName.indexOf("?"))
+						}
+						this.isOverwrite = false;
+						this.doOverwrite = false;
+						$.ajax({
+							type: "GET",
+							url: dupURL,
+							async: false,
+							dataType: 'json',
+						}).done(function(data, textStatus, jqXHR) {
+							//console.log(data);
+							if (typeof data.ResultSet !== 'undefined' && typeof data.ResultSet.Result !== 'undefined' && Array.isArray(data.ResultSet.Result)) {
+								var resultArr = data.ResultSet.Result;
+								for (var i=0; i<resultArr.length; i++) {
+									if (typeof resultArr[i].Name !== 'undefined' && (resultArr[i].Name == this.fileName  ||
+										(typeof resultArr[i].URI !== 'undefined' && resultArr[i].URI.endsWith('/' + this.fileName)))) {
+										this.isOverwrite = true;
+										if (!uploader.overwriteConfirmIssued) {
+											this.doOverwrite = confirm("\nDo you want to overwrite existing files?\n\n" +
+													"One or more files you are uploading already exist on the sever.  Press 'OK' to overwrite files or 'Cancel' " +
+													"to leave existing files in place.\n\nNOTE:  New files will still be uploaded if you choose not to " + 	
+													"overwrite existing files.\n");
+											uploader.doOverwrite = this.doOverwrite;
+											uploader.overwriteConfirmIssued = true;
+										} else {
+											this.doOverwrite = uploader.doOverwrite;
+										}
+										break;
+									}
+								}
+							}
+						}.bind(this));
+					}
 					$form.data = formData;
 					$form.processData=false;
 					$form.contentType=false;
@@ -257,6 +304,18 @@ abu.FileUploader = function(o){
 					percent.html(percentVal);
 					uploader.uploadsInProgress++;
 					uploader.uploadsStarted++;
+					if (this.isOverwrite && !this.doOverwrite) {
+			 			status.html('<span class="abu-upload-fail">File exists.  Upload cancelled at user request.</a>');
+			 			status.css("display","inline-block");
+			 			$(infoSelector).find(".abu-progress").css("display","none");
+						uploader.uploadsInProgress--;
+						if (uploader.uploadsInProgress==0) {
+							uploader._options.uploadCompletedFunction();
+						}
+						arr.abort();
+						return false;
+					} 
+					return true;
 				},
 				uploadProgress: function(event, position, total, percentComplete) {
 					var percentVal = percentComplete + '%';
@@ -288,10 +347,13 @@ abu.FileUploader = function(o){
 					} 
 					if (!isDuplicate) {
 						if (typeof result.status !== 'undefined' || result.length > 150) {
-				 			status.html('<a href="javascript:abu._fileUploader.showReturnedText(\'' + $(status).attr('id') + '\')" class="underline abu-upload-complete abu-upload-complete-text">Upload complete</a>');
+				 			status.html('<a href="javascript:abu._fileUploader.showReturnedText(\'' + $(status).attr('id') + '\')" class="underline abu-upload-complete abu-upload-complete-text">Upload complete' + 
+								((this.isOverwrite) ? ' (Existing file overwritten) ' : '') + '</a>');
 						} else {
-				 			status.html('<span class="abu-upload-complete abu-upload-complete-text">Upload complete</span>');
+				 			status.html('<span class="abu-upload-complete abu-upload-complete-text">Upload complete' + 
+								((this.isOverwrite) ? ' (Existing file overwritten) ' : '') + '</span>');
 						}
+						$("#abu-done-button-text").addClass("abu-done-button-file-uploaded");
 					} else {
 			 			status.html('<a href="javascript:abu._fileUploader.showReturnedText(\'' + $(status).attr('id') + '\')" class="underline abu-upload-fail">Duplicate file and overwrite=false.  Not uploaded.</a>');
 					}
diff --git a/src/main/webapp/scripts/xnat/admin/passwords.js b/src/main/webapp/scripts/xnat/admin/passwords.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc31a06be43efd93d3da11e2bd56b1bf607d2b3b
--- /dev/null
+++ b/src/main/webapp/scripts/xnat/admin/passwords.js
@@ -0,0 +1,95 @@
+// interactions with 'Security Passwords' section of admin ui
+console.log('passwordExpirationType.js');
+
+(function(){
+
+    var container$ = $('div[data-name="passwordExpiration"]');
+    var bundles$ = container$.find('div.input-bundle');
+
+
+    // Password Expiration
+    (function(){
+
+        var fieldInterval$ =
+                container$.find('#passwordExpirationInterval')
+                    .css({ marginTop: '10px' });
+
+        var oldInterval = fieldInterval$.val();
+
+        var fieldDate$ =
+                container$.find('#passwordExpirationDate')
+                    .attr({
+                        size: 10,
+                        placeholder: 'MM/DD/YYYY'
+                    })
+                    .css({
+                        marginTop: '10px',
+                        fontFamily: 'Courier, monospace'
+                    })
+                    .mask('99/99/9999', { placeholder: 'MM/DD/YYYY' })
+                    .datetimepicker({
+                        timepicker: false,
+                        // today is max date, disallow future date selection
+                        // maxDate:    '+1970/01/01',
+                        format:     'm/d/Y'
+                    });
+
+        var openCal$ =
+                container$.find('#openCal-passwordExpirationDate')
+                    .click(function(){
+                        fieldDate$.datetimepicker('show');
+                    });
+
+        container$.find('input[name="passwordExpirationType"]').on('change', function(){
+
+            // Does the interval need to be set to "-1" to disable expiration?
+
+            // var value = (this.value || 'interval').toLowerCase();
+            // if (value === 'disabled') {
+            //     fieldInterval$.val(-1);
+            // }
+            // else {
+            //     fieldInterval$.val(oldInterval)
+            // }
+            changeExpirationType(this.value);
+        });
+
+        changeExpirationType(XNAT.data.siteConfig.passwordExpirationType);
+
+        function changeExpirationType(value){
+            value = (value || 'interval').toLowerCase();
+            bundles$.hide();
+            bundles$.filter('.' + value).show();
+        }
+
+    })();
+
+
+    // Password Reuse
+    (function(){
+
+        var durationContainer$ = $('div[data-name="passwordHistoryDuration"]');
+        var durationInput$ = durationContainer$.find('input#passwordHistoryDuration');
+
+        $('#passwordReuseRestriction').on('change', function(){
+            changePasswordReuseType(this.value);
+        });
+
+        changePasswordReuseType(XNAT.data.siteConfig.passwordReuseRestriction);
+
+        function changePasswordReuseType(value){
+            value = (value || 'none').toLowerCase();
+            if (value === 'historical'){
+                durationContainer$.removeClass('disabled');
+                durationInput$.prop('disabled', false).removeClass('disabled');
+            }
+            else {
+                durationContainer$.addClass('disabled');
+                durationInput$.prop('disabled', true).addClass('disabled');
+            }
+        }
+
+    })();
+
+
+})();