diff --git a/ATxCommon/Serializables/ServiceConfig.cs b/ATxCommon/Serializables/ServiceConfig.cs
index acc1bc8a88b3355f256b95146b8ec9037ba06538..c7e622a57aba46aba2317eb4a3db11947444b6e1 100644
--- a/ATxCommon/Serializables/ServiceConfig.cs
+++ b/ATxCommon/Serializables/ServiceConfig.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Configuration;
 using System.IO;
 using System.Xml;
+using System.Xml.Linq;
 using System.Xml.Serialization;
 using NLog;
 
@@ -16,35 +17,6 @@ namespace ATxCommon.Serializables
     {
         private static readonly Logger Log = LogManager.GetCurrentClassLogger();
 
-        public ServiceConfig() {
-            Log.Trace("ServiceConfig() constructor, setting defaults.");
-            // set values for the optional XML elements:
-            SmtpPort = 25;
-            GraceNotificationDelta = 720;
-
-            EnforceInheritedACLs = true;
-        }
-
-        /// <summary>
-        /// Dummy method raising an exception (this class must not be serialized).
-        /// </summary>
-        public static void Serialize(string file, ServiceConfig c) {
-            // the config is never meant to be written by us, therefore:
-            throw new SettingsPropertyIsReadOnlyException("The config file must not be written by the service!");
-        }
-
-        public static ServiceConfig Deserialize(string file) {
-            Log.Debug("Trying to read service configuration XML file: [{0}]", file);
-            var serializer = new XmlSerializer(typeof(ServiceConfig));
-            ServiceConfig config;
-            using (var reader = XmlReader.Create(file)) {
-                config = (ServiceConfig) serializer.Deserialize(reader);
-            }
-            ValidateConfiguration(config);
-            Log.Debug("Finished deserializing service configuration XML file.");
-            return config;
-        }
-
 
         #region required configuration parameters
 
@@ -53,11 +25,6 @@ namespace ATxCommon.Serializables
         /// </summary>
         public string HostAlias { get; set; }
 
-        /// <summary>
-        /// A human friendly name for the target, to be used in emails etc.
-        /// </summary>
-        public string DestinationAlias { get; set; }
-
         /// <summary>
         /// The base drive for the spooling directories (incoming and managed).
         /// </summary>
@@ -68,18 +35,17 @@ namespace ATxCommon.Serializables
         /// </summary>
         public string IncomingDirectory { get; set; }
 
-        /// <summary>
-        /// The name of a marker file to be placed in all **sub**directories
-        /// inside the IncomingDirectory.
-        /// </summary>
-        public string MarkerFile { get; set; }
-
         /// <summary>
         /// A directory on SourceDrive to hold the three subdirectories "DONE",
         /// "PROCESSING" and "UNMATCHED" used during and after transfers.
         /// </summary>
         public string ManagedDirectory { get; set; }
 
+        /// <summary>
+        /// A human friendly name for the target, to be used in emails etc.
+        /// </summary>
+        public string DestinationAlias { get; set; }
+
         /// <summary>
         /// Target path to transfer files to. Usually a UNC location.
         /// </summary>
@@ -91,78 +57,85 @@ namespace ATxCommon.Serializables
         /// </summary>
         public string TmpTransferDir { get; set; }
 
-        /// <summary>
-        /// The email address to be used as "From:" when sending mail notifications.
-        /// </summary>
-        public string EmailFrom { get; set; }
-
-        /// <summary>
-        /// The interval (in ms) for checking for new files and system parameters.
-        /// </summary>
-        public int ServiceTimer { get; set; }
-
         /// <summary>
         /// Maximum allowed CPU usage across all cores in percent. Running transfers will be paused
         /// if this limit is exceeded.
         /// </summary>
         public int MaxCpuUsage { get; set; }
-        
+
         /// <summary>
         /// Minimum amount of free RAM (in MB) required for the service to operate.
         /// </summary>
         public int MinAvailableMemory { get; set; }
 
+        #endregion
+
+
+        #region optional configuration parameters
+
         /// <summary>
-        /// Minimum amount of time in minutes between two mail notifications to the admin address.
+        /// Switch on debug log messages. Default: false.
         /// </summary>
-        public int AdminNotificationDelta { get; set; }
+        public bool Debug { get; set; } = false;
 
         /// <summary>
-        /// Minimum amount of time in minutes between two low-storage-space notifications.
+        /// The interval (in ms) for checking for new files and system parameters. Default: 1000.
         /// </summary>
-        public int StorageNotificationDelta { get; set; }
+        public int ServiceTimer { get; set; } = 1000;
 
         /// <summary>
-        /// GracePeriod: number of days after data in the "DONE" location expires,
-        /// which will trigger a summary email to the admin address.
+        /// The name of a marker file to be placed in all **sub**directories
+        /// inside the IncomingDirectory.
         /// </summary>
-        public int GracePeriod { get; set; }
+        public string MarkerFile { get; set; }
 
         /// <summary>
-        /// Flag whether to send explicit mail notifications to the admin on selected events.
+        /// Number of days after data in the "DONE" location expires. Default: 30.
         /// </summary>
-        public bool SendAdminNotification { get; set; }
-        
+        public int GracePeriod { get; set; } = 30;
+
         /// <summary>
-        /// Flag whether to send a mail notification to the user upon completed transfers.
+        /// Whether to enforce ACL inheritance when moving files and directories, see 
+        /// https://support.microsoft.com/en-us/help/320246 for more details. Default: false.
         /// </summary>
-        public bool SendTransferNotification { get; set; }
+        // ReSharper disable once InconsistentNaming
+        public bool EnforceInheritedACLs { get; set; } = false;
 
         /// <summary>
-        /// Switch on debug log messages.
+        /// Limit RoboCopy transfer bandwidth (mostly for testing purposes). Default: 0.
         /// </summary>
-        public bool Debug { get; set; }
-
-        [XmlArray]
-        [XmlArrayItem(ElementName = "DriveToCheck")]
-        public List<DriveToCheck> SpaceMonitoring { get; set; }
+        /// See the RoboCopy documentation for more details.
+        public int InterPacketGap { get; set; } = 0;
 
+        /// <summary>
+        /// A list of process names causing transfers to be suspended if running.
+        /// </summary>
         [XmlArray]
         [XmlArrayItem(ElementName = "ProcessName")]
         public List<string> BlacklistedProcesses { get; set; }
 
+        /// <summary>
+        /// A list of drives and thresholds to monitor free space.
+        /// </summary>
+        [XmlArray]
+        [XmlArrayItem(ElementName = "DriveToCheck")]
+        public List<DriveToCheck> SpaceMonitoring { get; set; }
+
         #endregion
 
 
-        #region optional configuration parameters
+        #region optional configuration parameters - notification settings
 
         /// <summary>
-        /// SMTP server used to send mails (if configured) and Fatal/Error log messages.
-        /// 
-        /// No mails will be sent if this is omitted.
+        /// SMTP server to send mails and Fatal/Error log messages. No mails if omitted.
         /// </summary>
         public string SmtpHost { get; set; }
 
+        /// <summary>
+        /// SMTP port for sending emails. Default: 25.
+        /// </summary>
+        public int SmtpPort { get; set; } = 25;
+
         /// <summary>
         /// SMTP username to authenticate when sending emails (if required).
         /// </summary>
@@ -174,45 +147,53 @@ namespace ATxCommon.Serializables
         public string SmtpPasswortCredential { get; set; }
 
         /// <summary>
-        /// SMTP port for sending emails (25 will be used if this entry is omitted).
+        /// The email address to be used as "From:" when sending mail notifications.
         /// </summary>
-        public int SmtpPort { get; set; }
+        public string EmailFrom { get; set; }
 
         /// <summary>
-        /// A string to be added as a prefix to the subject when sending emails.
+        /// A prefix to be added to any email subject. Default: "[AutoTx Service] ".
         /// </summary>
-        public string EmailPrefix { get; set; }
+        public string EmailPrefix { get; set; } = "[AutoTx Service] ";
 
         /// <summary>
         /// The mail recipient address for admin notifications (including "Fatal" log messages).
         /// </summary>
         public string AdminEmailAdress { get; set; }
-        
+
         /// <summary>
         /// The mail recipient address for debug notifications (including "Error" log messages).
         /// </summary>
         public string AdminDebugEmailAdress { get; set; }
 
         /// <summary>
-        /// Minimum time in minutes between two mails about expired folders in the grace location.
+        /// Send an email to the user upon completed transfers. Default: true.
         /// </summary>
-        public int GraceNotificationDelta { get; set; }
+        public bool SendTransferNotification { get; set; } = true;
 
         /// <summary>
-        /// RoboCopy parameter for limiting the bandwidth (mostly for testing purposes).
+        /// Send email notifications to the admin on selected events. Default: true.
         /// </summary>
-        /// See the RoboCopy documentation for more details.
-        public int InterPacketGap { get; set; }
+        public bool SendAdminNotification { get; set; } = true;
+
+        /// <summary>
+        /// Minimum time in minutes between two notifications to the admin. Default: 60.
+        /// </summary>
+        public int AdminNotificationDelta { get; set; } = 60;
+
+        /// <summary>
+        /// Minimum time in minutes between two mails about expired folders. Default: 720 (12h).
+        /// </summary>
+        public int GraceNotificationDelta { get; set; } = 720;
 
         /// <summary>
-        /// EnforceInheritedACLs: whether to enforce ACL inheritance when moving files and
-        /// directories, see https://support.microsoft.com/en-us/help/320246 for more details.
+        /// Minimum time in minutes between two low-space notifications. Default: 720 (12h).
         /// </summary>
-        public bool EnforceInheritedACLs { get; set; }
+        public int StorageNotificationDelta { get; set; } = 720;
 
         #endregion
 
-        
+
         #region wrappers for derived parameters
 
         /// <summary>
@@ -248,55 +229,189 @@ namespace ATxCommon.Serializables
         #endregion
 
 
+        /// <summary>
+        /// ServiceConfig constructor, currently empty.
+        /// </summary>
+        private ServiceConfig() {
+            Log.Trace("ServiceConfig() constructor.");
+        }
+
+        /// <summary>
+        /// Dummy method raising an exception (this class must not be serialized).
+        /// </summary>
+        public static void Serialize(string file, ServiceConfig c) {
+            // the config is never meant to be written by us, therefore:
+            throw new SettingsPropertyIsReadOnlyException("The config file must not be written by the service!");
+        }
+
+        /// <summary>
+        /// Load the host specific and the common XML configuration files, combine them and
+        /// deserialize them into a ServiceConfig object. The host specific configuration file's
+        /// name is defined as the hostname with an ".xml" suffix.
+        /// </summary>
+        /// <param name="path">The path to the configuration files.</param>
+        /// <returns>A ServiceConfig object with validated settings.</returns>
+        public static ServiceConfig Deserialize(string path) {
+            Log.Trace("ServiceConfig.Deserialize({0})", path);
+            ServiceConfig config;
+
+            var commonFile = Path.Combine(path, "config.common.xml");
+            var specificFile = Path.Combine(path, Environment.MachineName + ".xml");
+
+            // for parsing the configuration from two separate files we are using the default
+            // behaviour of the .NET XmlSerializer on duplicates: only the first occurrence is
+            // used, all other ones are silentley being discarded - this way we simply append the
+            // contents of the common config file to the host-specific and deserialize then:
+            Log.Debug("Loading host specific configuration XML file: [{0}]", specificFile);
+            var combined = XElement.Load(specificFile);
+            // the common configuration file is optional, so check if it exists at all:
+            if (File.Exists(commonFile)) {
+                Log.Debug("Loading common configuration XML file: [{0}]", commonFile);
+                var common = XElement.Load(commonFile);
+                combined.Add(common.Nodes());
+                Log.Trace("Combined XML structure:\n\n{0}\n\n", combined);
+            }
+
+            using (var reader = XmlReader.Create(new StringReader(combined.ToString()))) {
+                Log.Debug("Trying to parse combined XML...");
+                var serializer = new XmlSerializer(typeof(ServiceConfig));
+                config = (ServiceConfig) serializer.Deserialize(reader);
+            }
+
+            ValidateConfiguration(config);
+            
+            Log.Debug("Successfully parsed a valid configuration from [{0}].", path);
+            return config;
+        }
+
         /// <summary>
         /// Validate the configuration, throwing exceptions on invalid parameters.
         /// </summary>
         private static void ValidateConfiguration(ServiceConfig c) {
-            if (string.IsNullOrEmpty(c.SourceDrive) ||
-                string.IsNullOrEmpty(c.IncomingDirectory) ||
-                string.IsNullOrEmpty(c.ManagedDirectory))
-                throw new ConfigurationErrorsException("mandatory parameter missing!");
+            Log.Debug("Validating configuration...");
+            var errmsg = "";
+
+            string CheckEmpty(string value, string name) {
+                // if the string is null terminate the validation immediately since this means the
+                // file doesn't contain a required parameter at all:
+                if (value == null) {
+                    var msg = $"mandatory parameter missing: <{name}>";
+                    Log.Error(msg);
+                    throw new ConfigurationErrorsException(msg);
+                }
+
+                if (string.IsNullOrWhiteSpace(value))
+                    return $"mandatory parameter unset: <{name}>\n";
+
+                return string.Empty;
+            }
 
-            if (c.SourceDrive.Substring(1) != @":\")
-                throw new ConfigurationErrorsException("SourceDrive must be a drive " +
-                                                       @"letter followed by a colon and a backslash, e.g. 'D:\'!");
+            string CheckMinValue(int value, string name, int min) {
+                if (value == 0)
+                    return $"<{name}> is unset (or set to 0), minimal accepted value is {min}\n";
+
+                if (value < min)
+                    return $"<{name}> must not be smaller than {min}\n";
+
+                return string.Empty;
+            }
+
+            string CheckLocalDrive(string value, string name) {
+                var driveType = new DriveInfo(value).DriveType;
+                if (driveType != DriveType.Fixed)
+                    return $"<{name}> ({value}) must be a local fixed drive, not '{driveType}'!\n";
+                return string.Empty;
+            }
+
+            void WarnOnHighValue(int value, string name, int thresh) {
+                if (value > thresh)
+                    SubOptimal(value.ToString(), name, "value is set very high, please check!");
+            }
+
+            void SubOptimal(string value, string name, string msg) {
+                Log.Warn(">>> Sub-optimal setting detected: <{0}> [{1}] {2}", name, value, msg);
+            }
+
+            void LogAndThrow(string msg) {
+                msg = $"Configuration issues detected:\n{msg}";
+                Log.Error(msg);
+                throw new ConfigurationErrorsException(msg);
+            }
+
+            // check if all required parameters are there and non-empty / non-zero:
+            errmsg += CheckEmpty(c.HostAlias, nameof(c.HostAlias));
+            errmsg += CheckEmpty(c.SourceDrive, nameof(c.SourceDrive));
+            errmsg += CheckEmpty(c.IncomingDirectory, nameof(c.IncomingDirectory));
+            errmsg += CheckEmpty(c.ManagedDirectory, nameof(c.ManagedDirectory));
+            errmsg += CheckEmpty(c.DestinationAlias, nameof(c.DestinationAlias));
+            errmsg += CheckEmpty(c.DestinationDirectory, nameof(c.DestinationDirectory));
+            errmsg += CheckEmpty(c.TmpTransferDir, nameof(c.TmpTransferDir));
+
+            errmsg += CheckMinValue(c.ServiceTimer, nameof(c.ServiceTimer), 1000);
+            errmsg += CheckMinValue(c.MaxCpuUsage, nameof(c.MaxCpuUsage), 5);
+            errmsg += CheckMinValue(c.MinAvailableMemory, nameof(c.MinAvailableMemory), 256);
+
+            // if any of the required parameter checks failed we terminate now as many of the
+            // string checks below would fail on empty strings:
+            if (!string.IsNullOrWhiteSpace(errmsg)) 
+                LogAndThrow(errmsg);
 
-            // make sure SourceDrive is a local (fixed) disk:
-            var driveInfo = new DriveInfo(c.SourceDrive);
-            if (driveInfo.DriveType != DriveType.Fixed)
-                throw new ConfigurationErrorsException("SourceDrive (" + c.SourceDrive +
-                                                       ") must be a local (fixed) drive, OS reports '" +
-                                                       driveInfo.DriveType + "')!");
 
+            ////////// REQUIRED PARAMETERS SETTINGS VALIDATION //////////
+
+            // SourceDrive
+            if (c.SourceDrive.Substring(1) != @":\")
+                errmsg += "<SourceDrive> must be of form [X:\\]\n!";
+            errmsg += CheckLocalDrive(c.SourceDrive, nameof(c.SourceDrive));
 
             // spooling directories: IncomingDirectory + ManagedDirectory
             if (c.IncomingDirectory.StartsWith(@"\"))
-                throw new ConfigurationErrorsException("IncomingDirectory must not start with a backslash!");
+                errmsg += "<IncomingDirectory> must not start with a backslash!\n";
             if (c.ManagedDirectory.StartsWith(@"\"))
-                throw new ConfigurationErrorsException("ManagedDirectory must not start with a backslash!");
+                errmsg += "<ManagedDirectory> must not start with a backslash!\n";
 
+            // DestinationDirectory
             if (!Directory.Exists(c.DestinationDirectory))
-                throw new ConfigurationErrorsException("can't find destination: " + c.DestinationDirectory);
+                errmsg += $"can't find (or reach) destination: {c.DestinationDirectory}\n";
 
+            // TmpTransferDir
             var tmpTransferPath = Path.Combine(c.DestinationDirectory, c.TmpTransferDir);
             if (!Directory.Exists(tmpTransferPath))
-                throw new ConfigurationErrorsException("temporary transfer dir doesn't exist: " + tmpTransferPath);
+                errmsg += $"can't find (or reach) temporary transfer dir: {tmpTransferPath}\n";
 
-            if (c.ServiceTimer < 1000)
-                throw new ConfigurationErrorsException("ServiceTimer must not be smaller than 1000 ms!");
 
+            ////////// OPTIONAL PARAMETERS SETTINGS VALIDATION //////////
 
-            // NON-CRITICAL stuff is simply reported to the logs:
-            if (!c.DestinationDirectory.StartsWith(@"\\")) {
-                ReportNonOptimal("DestinationDirectory", c.DestinationDirectory, "is not a UNC path!");
+            // EmailFrom
+            if (!string.IsNullOrWhiteSpace(c.SmtpHost) &&
+                string.IsNullOrWhiteSpace(c.EmailFrom))
+                errmsg += "<EmailFrom> must not be empty if <SmtpHost> is configured!\n";
+            
+            // DriveName
+            foreach (var driveToCheck in c.SpaceMonitoring) {
+                errmsg += CheckLocalDrive(driveToCheck.DriveName, nameof(driveToCheck.DriveName));
             }
-        }
 
-        /// <summary>
-        /// Print a standardized msg about a non-optimal configuration setting to the log.
-        /// </summary>
-        private static void ReportNonOptimal(string attribute, string value, string msg) {
-            Log.Warn(">>> Non-optimal setting detected: <{0}> [{1}] {2}", attribute, value, msg);
+
+            ////////// WEAK CHECKS ON PARAMETERS SETTINGS //////////
+            // those checks are non-critical and are simply reported to the logs
+
+            WarnOnHighValue(c.ServiceTimer, nameof(c.ServiceTimer), 10000);
+            WarnOnHighValue(c.MaxCpuUsage, nameof(c.MaxCpuUsage), 75);
+            WarnOnHighValue(c.MinAvailableMemory, nameof(c.MinAvailableMemory), 8192);
+            WarnOnHighValue(c.AdminNotificationDelta, nameof(c.AdminNotificationDelta), 1440);
+            WarnOnHighValue(c.GraceNotificationDelta, nameof(c.GraceNotificationDelta), 10080);
+            WarnOnHighValue(c.StorageNotificationDelta, nameof(c.StorageNotificationDelta), 10080);
+            WarnOnHighValue(c.GracePeriod, nameof(c.GracePeriod), 100);
+
+            if (!c.DestinationDirectory.StartsWith(@"\\"))
+                SubOptimal(c.DestinationDirectory, "DestinationDirectory", "is not a UNC path!");
+
+
+            if (string.IsNullOrWhiteSpace(errmsg))
+                return;
+
+            LogAndThrow(errmsg);
         }
 
         /// <summary>
@@ -305,43 +420,64 @@ namespace ATxCommon.Serializables
         /// <returns>A string with details on the configuration.</returns>
         public string Summary() {
             var msg =
+                "############### REQUIRED PARAMETERS ###############\n" +
                 $"HostAlias: {HostAlias}\n" +
                 $"SourceDrive: {SourceDrive}\n" +
                 $"IncomingDirectory: {IncomingDirectory}\n" +
-                $"MarkerFile: {MarkerFile}\n" +
                 $"ManagedDirectory: {ManagedDirectory}\n" +
-                $"GracePeriod: {GracePeriod} (" +
-                TimeUtils.DaysToHuman(GracePeriod, false) + ")\n" +
+                $"DestinationAlias: {DestinationAlias}\n" +
                 $"DestinationDirectory: {DestinationDirectory}\n" +
                 $"TmpTransferDir: {TmpTransferDir}\n" +
-                $"EnforceInheritedACLs: {EnforceInheritedACLs}\n" +
+                $"MaxCpuUsage: {MaxCpuUsage}%\n" +
+                $"MinAvailableMemory: {MinAvailableMemory} MB\n" +
+                "\n" +
+                "############### OPTIONAL PARAMETERS ###############\n" +
+                $"Debug: {Debug}\n" +
                 $"ServiceTimer: {ServiceTimer} ms\n" +
+                $"MarkerFile: {MarkerFile}\n" +
+                $"GracePeriod: {GracePeriod} days (" +
+                TimeUtils.DaysToHuman(GracePeriod, false) + ")\n" +
+                $"EnforceInheritedACLs: {EnforceInheritedACLs}\n" +
                 $"InterPacketGap: {InterPacketGap}\n" +
-                $"MaxCpuUsage: {MaxCpuUsage}%\n" +
-                $"MinAvailableMemory: {MinAvailableMemory}\n";
+                "";
+
+            var blacklist = "";
             foreach (var processName in BlacklistedProcesses) {
-                msg += $"BlacklistedProcess: {processName}\n";
+                blacklist += $"    ProcessName: {processName}\n";
             }
+            if (!string.IsNullOrWhiteSpace(blacklist))
+                msg += $"BlacklistedProcesses:\n{blacklist}";
+
+
+            var space = "";
             foreach (var drive in SpaceMonitoring) {
-                msg += $"Drive to check free space: {drive.DriveName} " +
+                space += $"    DriveName: {drive.DriveName} " +
                        $"(threshold: {Conv.MegabytesToString(drive.SpaceThreshold)})\n";
             }
-            if (string.IsNullOrEmpty(SmtpHost)) {
+            if (!string.IsNullOrWhiteSpace(space))
+                msg += $"SpaceMonitoring:\n{space}";
+
+            if (string.IsNullOrWhiteSpace(SmtpHost)) {
                 msg += "SmtpHost: ====== Not configured, disabling email! ======" + "\n";
             } else {
                 msg +=
                     $"SmtpHost: {SmtpHost}\n" +
+                    $"SmtpPort: {SmtpPort}\n" +
                     $"SmtpUserCredential: {SmtpUserCredential}\n" +
-                    $"EmailPrefix: {EmailPrefix}\n" +
+                    $"SmtpPasswortCredential: --- not showing ---\n" +
                     $"EmailFrom: {EmailFrom}\n" +
+                    $"EmailPrefix: {EmailPrefix}\n" +
                     $"AdminEmailAdress: {AdminEmailAdress}\n" +
                     $"AdminDebugEmailAdress: {AdminDebugEmailAdress}\n" +
-                    $"StorageNotificationDelta: {StorageNotificationDelta} (" +
-                    TimeUtils.MinutesToHuman(StorageNotificationDelta, false) + ")\n" +
-                    $"AdminNotificationDelta: {AdminNotificationDelta} (" +
+                    $"SendTransferNotification: {SendTransferNotification}\n" +
+                    $"SendAdminNotification: {SendAdminNotification}\n" +
+                    $"AdminNotificationDelta: {AdminNotificationDelta} min (" +
                     TimeUtils.MinutesToHuman(AdminNotificationDelta, false) + ")\n" +
-                    $"GraceNotificationDelta: {GraceNotificationDelta} (" +
-                    TimeUtils.MinutesToHuman(GraceNotificationDelta, false) + ")\n";
+                    $"GraceNotificationDelta: {GraceNotificationDelta} min (" +
+                    TimeUtils.MinutesToHuman(GraceNotificationDelta, false) + ")\n" +
+                    $"StorageNotificationDelta: {StorageNotificationDelta} min (" +
+                    TimeUtils.MinutesToHuman(StorageNotificationDelta, false) + ")\n" +
+                    "";
             }
             return msg;
         }
diff --git a/ATxCommon/Serializables/ServiceStatus.cs b/ATxCommon/Serializables/ServiceStatus.cs
index e7321a7fbd6c0564276d6b9b27c619b936cc186a..a7082c0aa9345ac4adc92c254b162478b43767c2 100644
--- a/ATxCommon/Serializables/ServiceStatus.cs
+++ b/ATxCommon/Serializables/ServiceStatus.cs
@@ -40,7 +40,7 @@ namespace ATxCommon.Serializables
         /// <summary>
         /// The constructor, setting default values.
         /// </summary>
-        public ServiceStatus() {
+        private ServiceStatus() {
             _currentTransferSrc = "";
             _currentTargetTmp = "";
             _transferInProgress = false;
diff --git a/ATxConfigTest/AutoTxConfigTest.cs b/ATxConfigTest/AutoTxConfigTest.cs
index 679659e9ad476f889ca3d26b60c647a3dabf35e8..b4cbd9c2502c56961f749cb1c4f20c1da68a7dea 100644
--- a/ATxConfigTest/AutoTxConfigTest.cs
+++ b/ATxConfigTest/AutoTxConfigTest.cs
@@ -1,5 +1,4 @@
 using System;
-using System.IO;
 using ATxCommon.Serializables;
 using NLog;
 using NLog.Config;
@@ -9,40 +8,45 @@ namespace ATxConfigTest
 {
     internal class AutoTxConfigTest
     {
-        private static readonly Logger Log = LogManager.GetCurrentClassLogger();
-
         private static ServiceConfig _config;
-        private static ServiceStatus _status;
 
         private static void Main(string[] args) {
+            var logLevel = LogLevel.Info;
+            var logPrefix = "";
+            
+            var baseDir = AppDomain.CurrentDomain.BaseDirectory;
+            if (args.Length > 0)
+                baseDir = args[0];
+
+            if (args.Length > 1) {
+                if (args[1] == "debug") {
+                    logLevel = LogLevel.Debug;
+                    logPrefix = @"${date:format=yyyy-MM-dd HH\:mm\:ss} ";
+                }
+                if (args[1] == "trace") {
+                    logLevel = LogLevel.Trace;
+                    logPrefix = @"${date:format=yyyy-MM-dd HH\:mm\:ss} (${logger}) ";
+                }
+            }
+
             var logConfig = new LoggingConfiguration();
             var consoleTarget = new ConsoleTarget {
                 Name = "console",
-                Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} [${level}] (${logger}) ${message}",
+                Layout = logPrefix + @"[${level}] ${message}",
             };
             logConfig.AddTarget("console", consoleTarget);
-            var logRuleConsole = new LoggingRule("*", LogLevel.Debug, consoleTarget);
+            var logRuleConsole = new LoggingRule("*", logLevel, consoleTarget);
             logConfig.LoggingRules.Add(logRuleConsole);
             LogManager.Configuration = logConfig;
 
-            var baseDir = AppDomain.CurrentDomain.BaseDirectory;
-            if (args.Length > 0)
-                baseDir = args[0];
-
-            var configPath = Path.Combine(baseDir, "configuration.xml");
-            var statusPath = Path.Combine(baseDir, "status.xml");
+            const string mark = "----------------------------";
 
             try {
-                string msg;
-                Console.WriteLine($"\nTrying to parse configuration file [{configPath}]...\n");
-                _config = ServiceConfig.Deserialize(configPath);
-                msg = "------------------ configuration settings ------------------";
-                Console.WriteLine($"{msg}\n{_config.Summary()}{msg}\n");
-
-                Console.WriteLine($"\nTrying to parse status file [{statusPath}]...\n");
-                _status = ServiceStatus.Deserialize(statusPath, _config);
-                msg = "------------------ status parameters ------------------";
-                Console.WriteLine($"{msg}\n{_status.Summary()}{msg}\n");
+                Console.WriteLine($"\nTrying to parse configuration files from [{baseDir}]...\n");
+                _config = ServiceConfig.Deserialize(baseDir);
+                Console.WriteLine($"\n{mark} configuration settings {mark}");
+                Console.Write(_config.Summary());
+                Console.WriteLine($"{mark} configuration settings {mark}\n");
             }
             catch (Exception ex) {
                 Console.WriteLine(ex);
diff --git a/ATxService/AutoTx.cs b/ATxService/AutoTx.cs
index 5aebbb0749921f44ca93a129a749a51ef9547390..2df178a3f40c0e628334d54a362f8f945a19e872 100644
--- a/ATxService/AutoTx.cs
+++ b/ATxService/AutoTx.cs
@@ -187,8 +187,8 @@ namespace ATxService
         /// </summary>
         private void LoadSettings() {
             try {
-                LoadConfigXml();
-                LoadStatusXml();
+                LoadConfig();
+                LoadStatus();
             }
             catch (Exception ex) {
                 Log.Error("LoadSettings() failed: {0}\n{1}", ex.Message, ex.StackTrace);
@@ -200,31 +200,28 @@ namespace ATxService
         }
 
         /// <summary>
-        /// Load the configuration xml file.
+        /// Load the configuration.
         /// </summary>
-        private void LoadConfigXml() {
-	        var confPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
-		        "configuration.xml");
+        private void LoadConfig() {
             try {
-                _config = ServiceConfig.Deserialize(confPath);
-                Log.Debug("Loaded config from [{0}]", confPath);
+                _config = ServiceConfig.Deserialize(
+                    Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "conf"));
             }
             catch (ConfigurationErrorsException ex) {
-                Log.Error("ERROR validating configuration file [{0}]: {1}",
-	                confPath, ex.Message);
+                Log.Error("Validating configuration failed: {0}", ex.Message);
                 throw new Exception("Error validating configuration.");
             }
             catch (Exception ex) {
-                Log.Error("loading configuration XML failed: {0}", ex.Message);
+                Log.Error("Loading configuration failed: {0}", ex.Message);
                 // this should terminate the service process:
-                throw new Exception("Error loading config.");
+                throw new Exception("Error loading configuration.");
             }
         }
 
         /// <summary>
-        /// Load the status xml file.
+        /// Load the status.
         /// </summary>
-        private void LoadStatusXml() {
+        private void LoadStatus() {
 	        var statusPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
 		        "status.xml");
 			try {
diff --git a/ATxTray/AutoTxTray.cs b/ATxTray/AutoTxTray.cs
index 6fbc684f18cdfe64f783a9a3cfe5ce8c4749d2a0..fe0b01fa528d7fc9172711f1c9440ef51cfe1e82 100644
--- a/ATxTray/AutoTxTray.cs
+++ b/ATxTray/AutoTxTray.cs
@@ -91,7 +91,7 @@ namespace ATxTray
 
             Log.Trace("Trying to read service config and status files...");
             try {
-                _config = ServiceConfig.Deserialize(Path.Combine(baseDir, "configuration.xml"));
+                _config = ServiceConfig.Deserialize(Path.Combine(baseDir, "conf"));
                 UpdateStatusInformation();
                 SetupContextMenu();
             }
diff --git a/Resources/conf-minimal/config.common.xml b/Resources/conf-minimal/config.common.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6f2bc1b1b0cc329ab360a9d88b6a818b78fdf74a
--- /dev/null
+++ b/Resources/conf-minimal/config.common.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ServiceConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+    <!-- IncomingDirectory: directory on SourceDrive to watch for new files -->
+    <IncomingDirectory>ProgramData\AUTOTRANSFER\INCOMING</IncomingDirectory>
+
+    <!-- ManagedDirectory: directory on SourceDrive where files and folders are
+         moved while queueing for their transfer (sub-directory "PROCESSING")
+         and to store them for deferred delection after a grace period after
+         the transfer (sub-directory "DONE"). -->
+    <ManagedDirectory>ProgramData\AUTOTRANSFER</ManagedDirectory>
+
+    <!-- DestinationAlias: friendly name for the target to be used in mails -->
+    <DestinationAlias>Core Facility Storage</DestinationAlias>
+
+    <!-- DestinationDirectory: where files should be transferred to -->
+    <DestinationDirectory>\\fileserver.mydomain.xy\share\</DestinationDirectory>
+
+    <!-- TmpTransferDir: temporary directory relative to DestinationDirectory
+         to be used for running transfers -->
+    <TmpTransferDir>AUTOTRANSFER-TMP</TmpTransferDir>
+
+    <!-- MaxCpuUsage: pause transfer if CPU usage is above this value (in %)-->
+    <MaxCpuUsage>25</MaxCpuUsage>
+
+    <!-- MinAvailableMemory: pause transfer if free RAM is below (in MB) -->
+    <MinAvailableMemory>512</MinAvailableMemory>
+
+
+    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
+
+    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
+
+</ServiceConfig>
\ No newline at end of file
diff --git a/Resources/conf-minimal/host-specific.template.xml b/Resources/conf-minimal/host-specific.template.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cf9f260d5cc4deaac1bc0401b4d0d699fd6e5360
--- /dev/null
+++ b/Resources/conf-minimal/host-specific.template.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ServiceConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+    <!-- HostAlias: friendly name to be used for this machine in mails -->
+    <HostAlias>Confocal Microscope (Room 123)</HostAlias>
+
+    <!-- SourceDrive: local drive to operate on (include the backslash!) -->
+    <SourceDrive>D:\</SourceDrive>
+
+
+    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
+
+    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
+
+  </ServiceConfig>
\ No newline at end of file
diff --git a/Resources/conf/config.common.xml b/Resources/conf/config.common.xml
index fe28063b5f015800a0cd4cf944611e8b31c1befc..81094939de77e47ea5cb7588a55ff6ba0f5638df 100644
--- a/Resources/conf/config.common.xml
+++ b/Resources/conf/config.common.xml
@@ -3,30 +3,23 @@
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 
     <!-- IncomingDirectory: directory on SourceDrive to watch for new files -->
-    <IncomingDirectory>ATX\INCOMING</IncomingDirectory>
+    <IncomingDirectory>ProgramData\AUTOTRANSFER\INCOMING</IncomingDirectory>
 
     <!-- ManagedDirectory: directory on SourceDrive where files and folders are
          moved while queueing for their transfer (sub-directory "PROCESSING")
          and to store them for deferred delection after a grace period after
          the transfer (sub-directory "DONE"). -->
-    <ManagedDirectory>ATX\MANAGED</ManagedDirectory>
-
-    <!-- GracePeriod: number of days after data in the "DONE" location expires,
-         which will trigger a summary email to the admin address. -->
-    <GracePeriod>30</GracePeriod>
+    <ManagedDirectory>ProgramData\AUTOTRANSFER</ManagedDirectory>
 
     <!-- DestinationAlias: friendly name for the target to be used in mails -->
     <DestinationAlias>Core Facility Storage</DestinationAlias>
 
     <!-- DestinationDirectory: where files should be transferred to -->
-    <DestinationDirectory>C:\ATX\TARGET</DestinationDirectory>
+    <DestinationDirectory>\\fileserver.mydomain.xy\share\</DestinationDirectory>
 
     <!-- TmpTransferDir: temporary directory relative to DestinationDirectory
          to be used for running transfers -->
-    <TmpTransferDir>d-vamp-dw</TmpTransferDir>
-
-    <!-- ServiceTimer: interval (in ms) for checking files and parameters -->
-    <ServiceTimer>1000</ServiceTimer>
+    <TmpTransferDir>AUTOTRANSFER-TMP</TmpTransferDir>
 
     <!-- MaxCpuUsage: pause transfer if CPU usage is above this value (in %)-->
     <MaxCpuUsage>25</MaxCpuUsage>
@@ -40,10 +33,36 @@
     <!-- Debug: enable or disable debug log messages -->
     <Debug>true</Debug>
 
+    <!-- ServiceTimer: interval (in ms) for checking files and parameters -->
+    <ServiceTimer>1000</ServiceTimer>
+
     <!-- MarkerFile: a file to place in each user's incoming directory, the
          file itself will be ignored for the transfers -->
     <MarkerFile>_DO_NOT_ACQUIRE_HERE_.txt</MarkerFile>
 
+    <!-- GracePeriod: number of days after data in the "DONE" location expires,
+         which will trigger a summary email to the admin address. -->
+    <GracePeriod>30</GracePeriod>
+
+    <!-- EnforceInheritedACLs: whether to enforce ACL inheritance when moving
+         files and directories, see this page for details (DEFAULT: true)
+         https://support.microsoft.com/en-us/help/320246 -->
+    <EnforceInheritedACLs>false</EnforceInheritedACLs>
+
+    <!-- BlacklistedProcesses: a list of "ProcessName" entries denoting
+         programs that will cause a transfer to be suspended immediately if the
+         name is found in the list of running processes -->
+    <BlacklistedProcesses>
+        <ProcessName>calc</ProcessName>
+        <ProcessName>notepad</ProcessName>
+        <ProcessName>wordpad</ProcessName>
+    </BlacklistedProcesses>
+
+    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
+
+
+    <!--  OPTIONAL NOTIFICATION / EMAIL SETTINGS  -->
+
     <!-- SmtpHost: SMTP server hostname -->
     <SmtpHost />
     <!-- SmtpPort: SMTP server port, defaults to 25 if omitted -->
@@ -85,20 +104,6 @@
          in case one of the drives is below the threshold (in minutes) -->
     <StorageNotificationDelta>720</StorageNotificationDelta>
 
-    <!-- BlacklistedProcesses: a list of "ProcessName" entries denoting
-         programs that will cause a transfer to be suspended immediately if the
-         name is found in the list of running processes -->
-    <BlacklistedProcesses>
-        <ProcessName>calc</ProcessName>
-        <ProcessName>notepad</ProcessName>
-        <ProcessName>wordpad</ProcessName>
-    </BlacklistedProcesses>
-
-    <!-- EnforceInheritedACLs: whether to enforce ACL inheritance when moving
-         files and directories, see this page for details (DEFAULT: true)
-         https://support.microsoft.com/en-us/help/320246 -->
-    <EnforceInheritedACLs>false</EnforceInheritedACLs>
-
-    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
+    <!--  OPTIONAL NOTIFICATION / EMAIL SETTINGS  -->
 
 </ServiceConfig>
\ No newline at end of file
diff --git a/Resources/conf/host-specific.template.xml b/Resources/conf/host-specific.template.xml
index 10613b46c055cb94bd4f771e1c66c47c63c8d4b8..0feb144ea34072fa6b91ef471f62cd04204b59f9 100644
--- a/Resources/conf/host-specific.template.xml
+++ b/Resources/conf/host-specific.template.xml
@@ -6,11 +6,14 @@
     <HostAlias>Confocal Microscope (Room 123)</HostAlias>
 
     <!-- SourceDrive: local drive to operate on (include the backslash!) -->
-    <SourceDrive>C:\</SourceDrive>
+    <SourceDrive>D:\</SourceDrive>
 
 
     <!--  OPTIONAL CONFIGURATION SETTINGS  -->
 
+    <!-- InterPacketGap: RoboCopy parameter to limit the bandwidth -->
+    <InterPacketGap>0</InterPacketGap>
+
     <!-- A list of drive names and space thresholds to be used for monitoring
          the free space and send notifications if below. -->
     <SpaceMonitoring>
@@ -26,9 +29,6 @@
         </DriveToCheck><-->
     </SpaceMonitoring>
 
-    <!-- InterPacketGap: RoboCopy parameter to limit the bandwidth -->
-    <InterPacketGap />
-
     <!--  OPTIONAL CONFIGURATION SETTINGS  -->
 
   </ServiceConfig>
\ No newline at end of file
diff --git a/Resources/configuration-example.xml b/Resources/configuration-example.xml
deleted file mode 100644
index ec6bc1400363489de81452289fb7ef0cfee55619..0000000000000000000000000000000000000000
--- a/Resources/configuration-example.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ServiceConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-    <!-- HostAlias: friendly name to be used for this machine in mails -->
-    <HostAlias>Confocal Microscope 2</HostAlias>
-    <!-- DestinationAlias: friendly name for the target to be used in mails -->
-    <DestinationAlias>Core Facility Storage</DestinationAlias>
-
-    <!-- enable or disable debug log messages -->
-    <Debug>true</Debug>
-
-    <!-- SourceDrive: base drive including backslash, e.g. "D:\" -->
-    <SourceDrive>XXX</SourceDrive>
-    <!-- IncomingDirectory: directory on SourceDrive to watch for new files -->
-    <IncomingDirectory>AUTOTRANSFER</IncomingDirectory>
-    <!-- [OPTIONAL] MarkerFile: a file to place in each user's incoming
-         directory, the file itself will be ignored for the transfers -->
-    <MarkerFile>_DO_NOT_ACQUIRE_HERE_.txt</MarkerFile>
-    <!-- ManagedDirectory: directory on SourceDrive where files and folders are
-         moved while queueing for their transfer (sub-directory "PROCESSING")
-         and to store them for deferred delection after a grace period after
-         the transfer (sub-directory "DONE"). -->
-    <ManagedDirectory>ProgramData\AUTOTRANSFER</ManagedDirectory>
-    <!-- GracePeriod: number of days after data in the "DONE" location expires,
-         which will trigger a summary email to the admin address. -->
-    <GracePeriod>5</GracePeriod>
-    <!-- DestinationDirectory: where files should be transferred to -->
-    <DestinationDirectory>\\fileserver.mydomain.xy\share\</DestinationDirectory>
-    <!-- TmpTransferDir: temporary directory relative to DestinationDirectory
-         to be used for running transfers -->
-    <TmpTransferDir>AUTOTRANSFER-TMP</TmpTransferDir>
-
-    <!-- ServiceTimer: interval (in ms) for checking files and parameters -->
-    <ServiceTimer>1000</ServiceTimer>
-
-    <!-- MaxCpuUsage: pause transfer if CPU usage is above this value (in %) -->
-    <MaxCpuUsage>25</MaxCpuUsage>
-    <!-- MinAvailableMemory: pause transfer if free RAM is below (in MB) -->
-    <MinAvailableMemory>512</MinAvailableMemory>
-    
-    <SpaceMonitoring>
-        <DriveToCheck>
-            <DriveName>C:</DriveName>
-            <SpaceThreshold>20000</SpaceThreshold>
-        </DriveToCheck>
-        <DriveToCheck>
-            <DriveName>D:</DriveName>
-            <SpaceThreshold>120000</SpaceThreshold>
-        </DriveToCheck>
-    </SpaceMonitoring>
-    <!-- StorageNotificationDelta: how often to send storage notification mails
-         in case one of the drives is below the threshold (in minutes) -->
-    <StorageNotificationDelta>5</StorageNotificationDelta>
-
-    <BlacklistedProcesses>
-        <ProcessName>calc</ProcessName>
-        <ProcessName>notepad</ProcessName>
-        <ProcessName>wordpad</ProcessName>
-    </BlacklistedProcesses>
-
-    <!-- SendAdminNotification: send email to user on finished transfers -->
-    <SendTransferNotification>true</SendTransferNotification>
-    <EmailFrom>admin@mydomain.xy</EmailFrom>
-
-    <!-- SendAdminNotification: notify admins via email of certain events -->
-    <SendAdminNotification>true</SendAdminNotification>
-    <!-- AdminNotificationDelta: how long to wait (in minutes) after sending an
-         admin notification before sending the next one -->
-    <AdminNotificationDelta>60</AdminNotificationDelta>
-
-
-    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
-
-    <!-- SmtpHost: SMTP server hostname -->
-    <SmtpHost>smtp.mydomain.xy</SmtpHost>
-    <!-- SmtpPort: SMTP server port, defaults to 25 if omitted -->
-    <SmtpPort>25</SmtpPort>
-    <!-- SmtpUserCredential: SMTP user name if authentication required -->
-    <SmtpUserCredential />
-    <!-- SmtpPasswortCredential: SMTP password if authentication required -->
-    <SmtpPasswortCredential />
-
-    <!-- EmailPrefix: prefix label for email subjects -->
-    <EmailPrefix>[Core Facility] </EmailPrefix>
-    <AdminEmailAdress>admin@mydomain.xy</AdminEmailAdress>
-    <!-- AdminDebugEmailAdress: an email address where to send certain debug
-         messages to, e.g. on completed transfers. Can be empty. -->
-    <AdminDebugEmailAdress>admin@mydomain.xy</AdminDebugEmailAdress>
-
-    <!-- GraceNotificationDelta: minimum time (in minutes) between two emails
-         about expired folders in the grace location (default: 720 (12h)) -->
-    <GraceNotificationDelta>720</GraceNotificationDelta>
-
-    <!-- InterPacketGap: RoboCopy parameter to limit the bandwidth -->
-    <InterPacketGap>0</InterPacketGap>
-
-    <!-- EnforceInheritedACLs: whether to enforce ACL inheritance when moving
-         files and directories, see this page for details (DEFAULT: true)
-         https://support.microsoft.com/en-us/help/320246 -->
-    <EnforceInheritedACLs>false</EnforceInheritedACLs>
-
-    <!--  OPTIONAL CONFIGURATION SETTINGS  -->
-</ServiceConfig>
\ No newline at end of file
diff --git a/Resources/status-example.xml b/Resources/status-example.xml
deleted file mode 100644
index 51c6b289db46f2b06e885c153fd157ea819a5f1f..0000000000000000000000000000000000000000
--- a/Resources/status-example.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ServiceStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-  <LastStatusUpdate>0001-01-01T00:00:00</LastStatusUpdate>
-  <LastStorageNotification>0001-01-01T00:00:00</LastStorageNotification>
-  <LastAdminNotification>0001-01-01T00:00:00</LastAdminNotification>
-  <LimitReason />
-  <CurrentTransferSrc />
-  <CurrentTargetTmp />
-  <ServiceSuspended>false</ServiceSuspended>
-  <TransferInProgress>false</TransferInProgress>
-  <CleanShutdown>true</CleanShutdown>
-  <CurrentTransferSize>0</CurrentTransferSize>
-</ServiceStatus>
\ No newline at end of file
diff --git a/Scripts/Make-Package.ps1 b/Scripts/Make-Package.ps1
index 6ffddf7fe31c5c625c1481642103fce22597c8df..e0d96c24d85aec47c404b12c77af4e297e7c8dda 100644
--- a/Scripts/Make-Package.ps1
+++ b/Scripts/Make-Package.ps1
@@ -2,10 +2,20 @@ $ResourceDir = "..\ATxService\Resources"
 $RsrcDirCommon = "..\Resources"
 
 
-function Highlight([string]$Message, [string]$Color = "Cyan") {
+function Highlight([string]$Message, [string]$Color = "Cyan", $Indent = $False) {
+    if ($Indent) {
+        Write-Host -NoNewline "    "
+    }
     Write-Host -NoNewline "["
     Write-Host -NoNewline -F $Color $Message
     Write-Host -NoNewline "]"
+    if ($Indent) {
+        Write-Host
+    }
+}
+
+function RelToAbs([string]$RelPath) {
+    Join-Path -Resolve $(Get-Location) $RelPath
 }
 
 
@@ -27,20 +37,20 @@ catch {
 
 $PkgDir = $BuildDate -replace ':','-' -replace ' ','_'
 $PkgDir = "build_" + $PkgDir
-$BinariesDirService = "..\ATxService\bin\$($BuildConfiguration)"
-$BinariesDirTrayApp = "..\ATxTray\bin\$($BuildConfiguration)"
-$BinariesDirCfgTest = "..\ATxConfigTest\bin\$($BuildConfiguration)"
+$BinariesDirService = RelToAbs "..\ATxService\bin\$($BuildConfiguration)"
+$BinariesDirTrayApp = RelToAbs "..\ATxTray\bin\$($BuildConfiguration)"
+$BinariesDirCfgTest = RelToAbs "..\ATxConfigTest\bin\$($BuildConfiguration)"
 
 Write-Host -NoNewline "Creating package "
 Highlight $PkgDir "Red"
 Write-Host " using binaries from:"
-Write-Host $(Highlight $BinariesDirService "Green")
-Write-Host $(Highlight $BinariesDirTrayApp "Green")
-Write-Host $(Highlight $BinariesDirCfgTest "Green")
-Write-Host ""
+Highlight $BinariesDirService "Green" $True
+Highlight $BinariesDirTrayApp "Green" $True
+Highlight $BinariesDirCfgTest "Green" $True
+Write-Host
 
 if (Test-Path $PkgDir) {
-    Write-Host "Removing existing package dir [$($PkgDir)]..."
+    Write-Host "Removing existing package dir [$($PkgDir)]...`n"
     Remove-Item -Recurse -Force $PkgDir
 }
 
@@ -52,10 +62,10 @@ Copy-Item -Exclude *.pdb -Recurse "$($BinariesDirService)\*" $tgt
 Copy-Item -Exclude *.pdb -Recurse "$($BinariesDirTrayApp)\*" $tgt -EA Ignore
 Copy-Item -Exclude *.pdb -Recurse "$($BinariesDirCfgTest)\*" $tgt -EA Ignore
 # provide an up-to-date version of the example config file:
-Copy-Item "$($RsrcDirCommon)\configuration-example.xml" $tgt
+$example = New-Item -ItemType Container -Path $PkgDir -Name "conf-example"
+Copy-Item "$($RsrcDirCommon)\conf\config.common.xml" $example
+Copy-Item "$($RsrcDirCommon)\conf\host-specific.template.xml" $example
 
-Copy-Item "$($RsrcDirCommon)\configuration-example.xml" "$($PkgDir)\configuration.xml"
-Copy-Item "$($RsrcDirCommon)\status-example.xml" "$($PkgDir)\status.xml"
 Copy-Item "$($ResourceDir)\BuildDate.txt" "$($PkgDir)\AutoTx.log"
 Copy-Item "$($ResourceDir)\BuildConfiguration.txt" $($PkgDir)
 try {
@@ -74,8 +84,11 @@ Copy-Item "Install-Service.ps1" $PkgDir
 
 Write-Host -NoNewline "Done creating package "
 Highlight $PkgDir
-Write-Host -NoNewline " using config "
-Highlight $BuildConfiguration
-Write-Host -NoNewline " based on commit "
-Highlight $BuildCommit
+Write-Host
+Highlight "configuration: $($BuildConfiguration)" -Indent $True
+Highlight "commit: $($BuildCommit)" -Indent $True
+Write-Host
+
+Write-Host -NoNewline "Location: "
+Highlight "$(RelToAbs $PkgDir)"
 Write-Host
\ No newline at end of file
diff --git a/Updater/Update-Service.ps1 b/Updater/Update-Service.ps1
index 7e3333b9eca94049b33fa821f2b7792f32ccc767..f12c233122a539362d94b4740420ca09adf5df10 100644
--- a/Updater/Update-Service.ps1
+++ b/Updater/Update-Service.ps1
@@ -52,6 +52,7 @@ function ServiceIsBusy {
     }
 }
 
+
 function Stop-TrayApp() {
     try {
         Stop-Process -Name "ATxTray" -Force -ErrorAction Stop
@@ -137,7 +138,12 @@ function Start-MyService {
 
 function Get-WriteTime([string]$FileName) {
     try {
-        $TimeStamp = (Get-Item "$FileName").LastWriteTime
+        $TimeStamp = (Get-Item "$FileName" -EA Stop).LastWriteTime
+    }
+    catch [System.Management.Automation.ItemNotFoundException] {
+        Write-Verbose "File [$($FileName)] can't be found!"
+        throw [System.Management.Automation.ItemNotFoundException] `
+            "File not found: $($FileName)."
     }
     catch {
         $ex = $_.Exception.Message
@@ -157,11 +163,16 @@ function File-IsUpToDate([string]$ExistingFile, [string]$UpdateCandidate) {
         Log-Debug "File [$($ExistingFile)] is up-to-date."
         Return $True
     }
+    Write-Verbose "File [$($UpdateCandidate)] is newer than [$($ExistingFile)]."
     Return $False
 }
 
 
 function Create-Backup {
+    # Rename a file using a time-stamp suffix like "2017-12-04T16.41.35" while
+    # preserving its original suffix / extension.
+    #
+    # Return $True if the backup was created successfully, $False otherwise.
     Param (
         [Parameter(Mandatory=$True)]
         [ValidateScript({Test-Path -PathType Leaf $_})]
@@ -171,31 +182,28 @@ function Create-Backup {
     $FileWithoutSuffix = [io.path]::GetFileNameWithoutExtension($FileName)
     $FileSuffix = [io.path]::GetExtension($FileName)
     $BaseDir = Split-Path -Parent $FileName
-    
+
     # assemble a timestamp string like "2017-12-04T16.41.35"
     $BakTimeStamp = Get-Date -Format s | foreach {$_ -replace ":", "."}
     $BakName = "$($FileWithoutSuffix)_pre-$($BakTimeStamp)$($FileSuffix)"
     Log-Info "Creating backup of [$($FileName)] as [$($BaseDir)\$($BakName)]."
+
     try {
-        Rename-Item "$FileName" "$BaseDir\$BakName" -ErrorAction Stop
+        Rename-Item "$FileName" "$BaseDir\$BakName"
     }
     catch {
-        $ex = $_.Exception.Message
-        Log-Error "Backing up [$($FileName)] as [$($BakName)] FAILED!`n$($ex)"
-        Exit
+        Log-Error "Backing up [$($DstFile)] FAILED:`n> $($_.Exception.Message)"
+        Return $False
     }
+    Return $True
 }
 
 
 function Update-File {
-    # Check the given $SrcFile if a file with the same name is existing in
-    # $DstPath. If $SrcFile is newer, stop the service, create a backup of the
-    # file in $DstPath and finally copy the file from $SrcFile to $DstPath.
+    # Use the given $SrcFile to update the file with the same name in $DstPath,
+    # creating a backup of the original file before replacing it.
     #
     # Return $True if the file was updated, $False otherwise.
-    #
-    # WARNING: the function TERMINATES the script on any error!
-    #
     Param (
         [Parameter(Mandatory=$True)]
         [ValidateScript({[IO.Path]::IsPathRooted($_)})]
@@ -207,106 +215,204 @@ function Update-File {
     )
 
     $DstFile = "$($DstPath)\$(Split-Path -Leaf $SrcFile)"
-    if (-Not (Test-Path "$DstFile")) {
-        Log-Info "File not existing in destination, NOT UPDATING: [$($DstFile)]"
-        Return $False
-    }
+    Write-Verbose "Trying to update [$($DstFile)] with [$($SrcFile)]..."
 
-    if (File-IsUpToDate -ExistingFile $DstFile -UpdateCandidate $SrcFile) {
+    if (-Not (Create-Backup -FileName $DstFile)) {
         Return $False
     }
 
-    Stop-MyService "Found newer file at $($SrcFile), updating..."
-
     try {
-        Create-Backup -FileName $DstFile
+        Copy-Item -Path $SrcFile -Destination $DstPath
+        Log-Info "Updated config file [$($DstFile)]."
     }
     catch {
-        Log-Error "Backing up $($DstFile) FAILED!`n$($_.Exception.Message)"
-        Exit
+        Log-Error "Copying [$($SrcFile)] FAILED:`n> $($_.Exception.Message)"
+        Return $False
+    }
+    Return $True
+}
+
+
+function Update-Configuration {
+    # Update the common and host-specific configuration files with their new
+    # versions, stopping the service if necessary.
+    # The function DOES NOT do any checks, it simply runs the necessary update
+    # commands - meaning everything else (do the files exist, is an update
+    # required) has to be checked beforehand!!
+    #
+    # Return $True if all files were updated successfully.
+    $NewComm = Join-Path $UpdPathConfig "config.common.xml"
+    $NewHost = Join-Path $UpdPathConfig "$($env:COMPUTERNAME).xml"
+    Write-Verbose "Updating configuration files:`n> $($NewComm)`n> $($NewHost)"
+
+    Stop-MyService "Updating configuration using files at [$($UpdPathConfig)]."
+    
+    $Ret = Update-File $NewComm $ConfigPath
+    # only continue if the first update worked:
+    if ($Ret) {
+        $Ret = Update-File $NewHost $ConfigPath
     }
+    Return $Ret
+}
+
 
+function NewConfig-Available {
+    # Check the configuration update path and the given $DstPath for both
+    # configuration files (common and host-specific) and compare their
+    # respective file write-time.
+    #
+    # Return $True if the update path contains any newer file, $False otherwise.
+    Param (
+        [Parameter(Mandatory=$True)]
+        [ValidateScript({(Get-Item $_).PSIsContainer})]
+        [String]$DstPath
+    )
+
+    # old and new common configuration
+    $OComm = Join-Path $DstPath "config.common.xml"
+    $NComm = Join-Path $UpdPathConfig "config.common.xml"
+
+    # old and new host-specific configuration
+    $OHost = Join-Path $DstPath "$($env:COMPUTERNAME).xml"
+    $NHost = Join-Path $UpdPathConfig "$($env:COMPUTERNAME).xml"
+
+    $Ret = $True
     try {
-        Copy-Item -Path $SrcFile -Destination $DstPath -ErrorAction Stop
-        Log-Info "Updated config file '$($DstFile)'."
+        $Ret = (
+            $(File-IsUpToDate -ExistingFile $OHost -UpdateCandidate $NHost) -And
+            $(File-IsUpToDate -ExistingFile $OComm -UpdateCandidate $NComm)
+        )
     }
     catch {
-        Log-Error "Copying $($SrcFile) FAILED!`n$($_.Exception.Message)"
-        Exit
+        Log-Error $("Checking for new configuration files failed:"
+            "$($_.Exception.Message)")
+        Return $False
     }
+
+    if ($Ret) {
+        Write-Verbose "Configuration is up to date, no new files available."
+        Return $False
+    }
+    Log-Info "New configuration files found!"
     Return $True
 }
 
 
-function Update-Configuration {
-    $RetOr = $False
-    # common config files first:
-    ForEach ($NewConfig in Get-ChildItem $UpdPathConfigCommon) {
-        $ret = Update-File $NewConfig.FullName $ConfigPath
-        $RetOr = $RetOr -Or $ret
-    }
-    # then host specific config files:
-    ForEach ($NewConfig in Get-ChildItem $UpdPathConfig) {
-        $ret = Update-File $NewConfig.FullName $ConfigPath
-        $RetOr = $RetOr -Or $ret
-    }
-    if (-Not ($RetOr)) {
-        Log-Debug "No (new) configuration file(s) found."
+function Config-IsValid {
+    # Check if the new configuration provided at $UpdPathConfig validates with
+    # the appropriate "AutoTxConfigTest" binary (either the existing one in the
+    # service installation directory (if the service binaries won't be updated)
+    # or the new one at the $UpdPathBinaries location in case the service itself
+    # will be updated as well).
+    #
+    # Returns an array with two elements, the first one being $True in case the
+    # configuration was successfully validated ($False otherwise) and the second
+    # one containing the output of the configuration test tool as a string.
+    Param (
+        [Parameter(Mandatory=$True)]
+        [ValidateScript({(Test-Path $_ -PathType Leaf)})]
+        [String]$ConfigTest,
+
+        [Parameter(Mandatory=$True)]
+        [ValidateScript({(Test-Path $_ -PathType Container)})]
+        [String]$ConfigPath
+    )
+    Write-Verbose "Running [$($ConfigTest) $($ConfigPath)]..."
+    $Summary = & $ConfigTest $ConfigPath
+    $Ret = $?
+    # pipe through Out-String to preserve line breaks:
+    $Summary = "$("=" * 80)`n$($Summary | Out-String)`n$("=" * 80)"
+
+    if ($Ret) {
+        Log-Debug "Validated config files at [$($ConfigPath)]:`n$($Summary)"
+        Return $Ret, $Summary
+    }
+    Log-Error "Config at [$($ConfigPath)] FAILED VALIDATION:`n$($Summary)"
+    Return $Ret, $Summary
+}
+
+
+function Find-InstallationPackage {
+    # Try to locate the latest installation package using the pattern defined
+    # in the updater configuration.
+    Write-Verbose "Looking for installation package using pattern: $($Pattern)"
+    $PkgDir = Get-ChildItem -Path $UpdPathBinaries -Directory -Name |
+        Where-Object {$_ -match $Pattern} |
+        Sort-Object |
+        Select-Object -Last 1
+    
+    if ([string]::IsNullOrEmpty($PkgDir)) {
+        Log-Error "couldn't find installation package matching '$($Pattern)'!"
+        Exit
     }
-    Return $RetOr
+    $PkgDir = "$($UpdPathBinaries)\$($PkgDir)"
+    Write-Verbose "Found update installation package: [$($PkgDir)]"
+    Return $PkgDir
 }
 
 
 function Copy-ServiceFiles {
+    # Copy the files from an update package to the service installation
+    # directory, overwriting existing ones.
+    #
+    # Returns $True for success, $False otherwise.
+    Write-Verbose "Copying service binaries from [$($UpdPackage)]..."
     try {
-        Write-Verbose "Looking for source package using pattern: $($Pattern)"
-        $PkgDir = Get-ChildItem -Path $UpdPathBinaries -Directory -Name |
-            Where-Object {$_ -match $Pattern} |
-            Sort-Object |
-            Select-Object -Last 1
-        
-        if ([string]::IsNullOrEmpty($PkgDir)) {
-            Write-Host "ERROR: couldn't find package matching '$($Pattern)'!"
-            Exit
-        }
-        Write-Verbose "Found update source package: [$($PkgDir)]"
-
-        Stop-MyService "Trying to update service using package [$($PkgDir)]."
-        Copy-Item -Recurse -Force -ErrorAction Stop `
-            -Path "$($UpdPathBinaries)\$($PkgDir)\$($ServiceName)\*" `
+        Copy-Item -Recurse -Force `
+            -Path "$($UpdPackage)\$($ServiceName)\*" `
             -Destination "$InstallationPath"
     }
     catch {
-        Log-Error "Updating service binaries FAILED!`n$($_.Exception.Message)"
-        Exit
+        Log-Error "Updating service binaries FAILED:`n> $($_.Exception.Message)"
+        Return $False
     }
-    Log-Info "Updated service binaries with [$($PkgDir)]."
+    Log-Info "Updated service binaries with [$($UpdPackage)]."
+    Return $True
 }
 
 
 function Update-ServiceBinaries {
-    $MarkerFile = "$($UpdPathMarkerFiles)\$($env:COMPUTERNAME)"
-    if (Test-Path "$MarkerFile" -Type Leaf) {
-        Log-Debug "Found marker [$($MarkerFile)], not updating service."
+    # Stop the tray application and the service, update the service binaries and
+    # create a marker file indicating the service on this host has been updated.
+    #
+    # Returns $True if binaries were updated successfully and the marker file
+    # has been created, $False otherwise.
+    Stop-TrayApp
+    Stop-MyService "Trying to update service using package [$($UpdPackage)]."
+    $Ret = Copy-ServiceFiles
+    if (-Not $Ret) {
         Return $False
     }
-    Stop-TrayApp
-    Copy-ServiceFiles
+
+    $MarkerFile = "$($UpdPathMarkerFiles)\$($env:COMPUTERNAME)"
     try {
-        New-Item -Type File "$MarkerFile" -ErrorAction Stop | Out-Null
+        New-Item -Type File "$MarkerFile" | Out-Null
         Log-Debug "Created marker file [$($MarkerFile)]."
     }
     catch {
-        Log-Error "Creating [$($MarkerFile)] FAILED!`n$($_.Exception.Message)"
-        Exit
+        Log-Error "Creating [$($MarkerFile)] FAILED:`n> $($_.Exception.Message)"
+        Return $False
+    }
+    Return $True
+}
+
+
+function ServiceUpdate-Requested {
+    # Check for a host-specific marker file indicating whether the service
+    # binaries on this host should be updated.
+    $MarkerFile = "$($UpdPathMarkerFiles)\$($env:COMPUTERNAME)"
+    if (Test-Path "$MarkerFile" -Type Leaf) {
+        Log-Debug "Found marker [$($MarkerFile)], not updating service."
+        Return $False
     }
+    Write-Verbose "Marker [$($MarkerFile)] missing, service should be updated!"
     Return $True
 }
 
 
 function Upload-LogFiles {
     $Dest = "$($UploadPathLogs)\$($env:COMPUTERNAME)"
-    New-Item -Force -Type Directory $Dest
+    New-Item -Force -Type Directory $Dest | Out-Null
     try {
         Copy-Item -Force -ErrorAction Stop `
             -Path "$($LogPath)\AutoTx.log" `
@@ -321,7 +427,7 @@ function Upload-LogFiles {
 
 function Get-HostDescription() {
     $Desc = $env:COMPUTERNAME
-    $ConfigXml = "$($InstallationPath)\configuration.xml"
+    $ConfigXml = "$($ConfigPath)\$($Desc).xml"
     try {
         [xml]$XML = Get-Content $ConfigXml -ErrorAction Stop
         # careful, we need a string comparison here:
@@ -413,6 +519,7 @@ function Log-Debug([string]$Message) {
 ################################################################################
 
 
+$ErrorActionPreference = "Stop"
 
 try {
     . $UpdaterSettings
@@ -442,8 +549,7 @@ Log-Debug "$($Me) started..."
 # first check if the service is installed and running at all
 $ServiceRunningBefore = ServiceIsRunning $ServiceName
 
-$UpdPathConfig = "$($UpdateSourcePath)\Configs\$($env:COMPUTERNAME)"
-$UpdPathConfigCommon = "$($UpdateSourcePath)\Configs\_COMMON_"
+$UpdPathConfig = "$($UpdateSourcePath)\Configs"
 $UpdPathMarkerFiles = "$($UpdateSourcePath)\Service\UpdateMarkers"
 $UpdPathBinaries = "$($UpdateSourcePath)\Service\Binaries"
 $UploadPathLogs = "$($UpdateSourcePath)\Logs"
@@ -453,7 +559,6 @@ Exit-IfDirMissing $LogPath "log files"
 Exit-IfDirMissing $ConfigPath "configuration files"
 Exit-IfDirMissing $UpdateSourcePath "update source"
 Exit-IfDirMissing $UpdPathConfig "configuration update"
-Exit-IfDirMissing $UpdPathConfigCommon "common configuration update"
 Exit-IfDirMissing $UpdPathMarkerFiles "update marker"
 Exit-IfDirMissing $UpdPathBinaries "service binaries update"
 Exit-IfDirMissing $UploadPathLogs "log file target"
@@ -463,29 +568,99 @@ Exit-IfDirMissing $UploadPathLogs "log file target"
 #       the logfiles are uploaded no matter if one of the other tasks fails and
 #       terminates the entire script:
 Upload-LogFiles
-$ConfigUpdated = Update-Configuration
-$ServiceUpdated = Update-ServiceBinaries
 
-$msg = ""
-if ($ConfigUpdated) {
-    $msg += "The configuration files were updated.`n"
-}
-if ($ServiceUpdated) {
-    $msg += "The service binaries were updated.`n"
-}
+try {
+    $UpdItems = @()
+    $ConfigShouldBeUpdated = NewConfig-Available $ConfigPath
+    $ServiceShouldBeUpdated = ServiceUpdate-Requested
+    if (-Not ($ConfigShouldBeUpdated -Or $ServiceShouldBeUpdated)) {
+        Log-Debug "No update action found to be necessary."
+        Exit
+    }
+
+    # define where the configuration is located that should be tested:
+    $ConfigToTest = $ConfigPath
+    if ($ConfigShouldBeUpdated) {
+        $ConfigToTest = $UpdPathConfig
+        $UpdItems += "configuration files"
+    }
+
+    # define which configuration checker executable to use for testing:
+    $ConftestExe = "$($InstallationPath)\AutoTxConfigTest.exe"
+    if ($ServiceShouldBeUpdated) {
+        $UpdPackage = Find-InstallationPackage
+        $ConftestExe = "$($UpdPackage)\$($ServiceName)\AutoTxConfigTest.exe"
+        $UpdItems += "service binaries"
+    }
+
+    # now we're all set and can run the config test:
+    $ConfigValid, $ConfigSummary = Config-IsValid $ConftestExe $ConfigToTest
+
+
+    # if we don't have a valid configuration we complain and terminate:
+    if (-Not ($ConfigValid)) {
+        Log-Error "Configuration not valid for service, $($Me) terminating!"
+        Send-MailReport -Subject "Update failed, configuration invalid!" `
+            -Body $("An update action was found to be necessary, however the"
+                "configuration didn't`npass the validator.`n`nThe following"
+                "summary was generated by the configuration checker:"
+                "`n`n$($ConfigSummary)")
+        Exit
+    }
+
+
+    # reaching this point means
+    #    (1) something needs to be updated (config, service or both)
+    #  AND
+    #    (2) the config validates with the corresponding service version
+    Write-Verbose "Required update items:`n> - $($UpdItems -join "`n> - ")`n"
+
+    if ($ConfigShouldBeUpdated) {
+        $ConfigUpdated = Update-Configuration
+        if (-Not $ConfigUpdated) {
+            $msg = "Updating the configuration failed, $($Me) terminating!"
+            Log-Error $msg
+            Send-MailReport -Subject "updated failed!" -Body $msg
+            Exit
+        }
+    }
+
+    if ($ServiceShouldBeUpdated) {
+        $ServiceUpdated = Update-ServiceBinaries
+        if (-Not $ServiceUpdated) {
+            $msg = "Updating the service binaries failed, $($Me) terminating!"
+            Log-Error $msg
+            Send-MailReport -Subject "updated failed!" -Body $msg
+            Exit        
+        }
+    }
+
+    $UpdSummary = "Updated $($UpdItems -join " and ")."
+
+
 
-if ($msg -ne "") {
     if ($ServiceRunningBefore) {
-        Log-Debug "Update action occurred, finishing up..."
+        Log-Debug "$($UpdSummary) Trying to start the service again..."
         Start-MyService
     } else {
-        Log-Debug "Not starting the service as it was not running before."
+        Log-Debug "$($UpdSummary) Leaving the service stopped, as before."
+    }
+
+    $UpdDetails = $("An $($Me) run completed successfully. Updated items:"
+        "`n> - $($UpdItems -join "`n> - ")")
+    if ($ConfigUpdated) {
+        $UpdDetails += "`n`nConfig validation summary:`n$($ConfigSummary)"
     }
-    Send-MailReport -Subject "Config and / or service has been updated!" `
-        -Body $msg
-} else {
-    Log-Debug "No update action found to be necessary."
 }
+catch {
+    $UpdDetails = $("Unexpected problem, check logs! $($Me) terminating."
+        "`n`n$($_.Exception.Message)")
+    $UpdSummary = "ERROR, unhandled problem occurered!"
+    Log-Error $UpdDetails
+}
+
+
+Send-MailReport -Subject "$UpdSummary" -Body "$UpdDetails"
 
 Upload-LogFiles
 
diff --git a/Updater/UpdaterConfig-Example.inc.ps1 b/Updater/UpdaterConfig-Example.inc.ps1
index 900387003490db11ccdf29775e55b8d8079992f0..0417c19620b660cd718e220c23060f8f63ee2c94 100644
--- a/Updater/UpdaterConfig-Example.inc.ps1
+++ b/Updater/UpdaterConfig-Example.inc.ps1
@@ -2,7 +2,7 @@
 
 $ServiceName = "AutoTx"
 $InstallationPath = "C:\Tools\$($ServiceName)"
-$ConfigPath = "$($InstallationPath)"
+$ConfigPath = "$($InstallationPath)\conf"
 $LogPath = "$($InstallationPath)"
 
 $UpdateSourcePath = "\\fileserver.mydomain.xy\share\_AUTOTX_"