/// <summary>
        /// Parses a list of drive data objects from a string
        /// </summary>
        /// <param name="inpList">Input string containing drive information</param>
        /// <returns>List of drives with associated data</returns>
        public static IEnumerable<clsDriveData> GetDriveList(string inpList)
        {
            if (string.IsNullOrWhiteSpace(inpList))
            {
                // There were no drives in string
                LogError("Drive list provided to GetDriveList is empty");
                return null;
            }

            // Data for drives is separated by semi-colon.
            var driveArray = inpList.Split(';');

            var driveList = new List<clsDriveData>();

            // Data for an individual drive is separated by comma
            foreach (var driveSpec in driveArray)
            {
                if (string.IsNullOrWhiteSpace(driveSpec))
                {
                    LogError("Unable to get drive space threshold from string, should be something like G:,600 and not " + driveSpec);
                    return null;
                }

                var driveInfo = driveSpec.Split(',');

                if (driveInfo.Length != 2)
                {
                    LogError("Invalid parameter count for drive data string " + driveSpec + ", should be something like G:,600");;
                    return null;
                }

                // Add the data for this drive to the return list
                // Note that driveInfo[0] can be either just a drive letter or a drive letter and a colon; either is supported
                var newDrive = new clsDriveData(driveInfo[0], double.Parse(driveInfo[1]));
                driveList.Add(newDrive);
            }

            return driveList;
        }
        /// <summary>
        /// For remote drives, uses WMI to determine if free space on disk is above minimum threshold
        /// For local drives, uses DriveInfo
        /// </summary>
        /// <param name="machine">Name of server to check</param>
        /// <param name="driveData">Data for drive to be checked</param>
        /// <param name="perspective">Client/Server setting for manager.  "Client" means checking a remote drive; "Server" means running on a Proto-x server </param>
        /// <param name="driveFreeSpaceGB">Actual drive free space in GB</param>
        /// <returns>Enum indicating space status</returns>
        public static SpaceCheckResults IsPurgeRequired(string machine, string perspective, clsDriveData driveData, out double driveFreeSpaceGB)
        {
            SpaceCheckResults testResult;

            driveFreeSpaceGB = -1;

            if (perspective.StartsWith("client", StringComparison.InvariantCultureIgnoreCase))
            {
                // Checking a remote drive
                // Get WMI object representing drive
                var requestStr = @"\\" + machine + @"\root\cimv2:win32_logicaldisk.deviceid=""" + driveData.DriveLetter + "\"";

                try
                {
                    var disk = new ManagementObject(requestStr);
                    disk.Get();

                    var oFreeSpace = disk["FreeSpace"];
                    if (oFreeSpace == null)
                    {
                        LogError("Drive " + driveData.DriveLetter + " not found via WMI; likely is Not Ready", true);
                        return SpaceCheckResults.Error;
                    }

                    var availableSpace = Convert.ToDouble(oFreeSpace);
                    var totalSpace = Convert.ToDouble(disk["Size"]);

                    if (totalSpace <= 0)
                    {
                        LogError("Drive " + driveData.DriveLetter + " reports a total size of 0 bytes via WMI; likely is Not Ready", true);
                        return SpaceCheckResults.Error;
                    }

                    driveFreeSpaceGB = BytesToGB((long)availableSpace);
                }
                catch (Exception ex)
                {
                    var msg = "Exception getting free disk space using WMI, drive " + driveData.DriveLetter + ": " + ex.Message;

                    var postToDB = !Environment.MachineName.StartsWith("monroe", StringComparison.InvariantCultureIgnoreCase);
                    LogError(msg, postToDB);

                    if (driveFreeSpaceGB > 0)
                        driveFreeSpaceGB = -driveFreeSpaceGB;

                    if (Math.Abs(driveFreeSpaceGB) < float.Epsilon)
                        driveFreeSpaceGB = -1;
                }
            }
            else
            {
                // Analyzing a drive local to this manager

                try
                {
                    // Note: WMI string would be: "win32_logicaldisk.deviceid=\"" + driveData.DriveLetter + "\"";
                    // Instantiate a new drive info object
                    var diDrive = new DriveInfo(driveData.DriveLetter);

                    if (!diDrive.IsReady)
                    {
                        LogError("Drive " + driveData.DriveLetter + " reports Not Ready via DriveInfo object; drive is offline or drive letter is invalid", true);
                        return SpaceCheckResults.Error;
                    }

                    if (diDrive.TotalSize <= 0)
                    {
                        LogError("Drive " + driveData.DriveLetter + " reports a total size of 0 bytes via DriveInfo object; likely is Not Ready", true);
                        return SpaceCheckResults.Error;
                    }

                    driveFreeSpaceGB = BytesToGB(diDrive.TotalFreeSpace);
                }
                catch (Exception ex)
                {
                    LogError("Exception getting free disk space via .NET DriveInfo object, drive " + driveData.DriveLetter + ": " + ex.Message, true);

                    if (driveFreeSpaceGB > 0)
                        driveFreeSpaceGB = -driveFreeSpaceGB;
                    if (Math.Abs(driveFreeSpaceGB) < float.Epsilon)
                        driveFreeSpaceGB = -1;
                }

            }

            if (driveFreeSpaceGB < 0)
            {
                testResult = SpaceCheckResults.Error;

                // Log space requirement if debug logging enabled
                ReportStatus( "Drive " + driveData.DriveLetter + " Space Threshold: " + driveData.MinDriveSpace + ", Drive not found", true);
            }
            else
            {
                if (driveFreeSpaceGB > driveData.MinDriveSpace)
                    testResult = SpaceCheckResults.Above_Threshold;
                else
                    testResult = SpaceCheckResults.Below_Threshold;

                // Log space requirement if debug logging enabled
                ReportStatus("Drive " + driveData.DriveLetter +
                    " Space Threshold: " + driveData.MinDriveSpace +
                    ", Avail space: " + driveFreeSpaceGB.ToString("####0.0"), true);

            }

            return testResult;
        }
        private DriveOpStatus ProcessDrive(int maxReps, clsDriveData testDrive)
        {
            const int MAX_MISSING_FOLDERS = 50;

            var opStatus = DriveOpStatus.KeepRunning;
            var repCounter = 0;
            var folderMissingCount = 0;

            try
            {

                // Start a purge loop for the current drive
                var bDriveInfoLogged = false;
                while (true)
                {
                    // Check for configuration changes
                    if (m_ConfigChanged)
                    {
                        // Local config has changed, so exit loop and reload settings
                        ReportStatus("Local config changed. Reloading configuration");
                        opStatus = DriveOpStatus.Exit_Restart_OK;
                        break;
                    }

                    // Check to see if iteration limit has been exceeded
                    if (repCounter >= maxReps)
                    {
                        // Exceeded max number of repetitions for this run, so exit
                        ReportStatus("Reached maximum repetition count of " + maxReps + "; Program exiting");
                        opStatus = DriveOpStatus.Exit_No_Restart;
                        break;
                    }

                    if (folderMissingCount >= MAX_MISSING_FOLDERS)
                    {
                        // Too many missing folders; MyEMSL or the archive could be offline
                        LogError("Too many missing folders: MyEMSL or the archive could be offline; Program exiting");
                        opStatus = DriveOpStatus.Exit_No_Restart;
                        break;
                    }

                    // Check error count
                    if (!TestErrorCount())
                    {
                        // Excessive errors. Program exit required. Logging handled by TestErrorCount
                        opStatus = DriveOpStatus.Exit_No_Restart;
                        break;
                    }

                    // Check available space on server drive and compare it with min allowed space
                    double driveFreeSpaceGB;
                    var serverName = m_MgrSettings.GetParam("machname");
                    var perspective = m_MgrSettings.GetParam("perspective");
                    var checkResult = clsUtilityMethods.IsPurgeRequired(serverName,
                                                                        perspective,
                                                                        testDrive,
                                                                        out driveFreeSpaceGB);

                    if (checkResult == SpaceCheckResults.Above_Threshold)
                    {
                        // Drive doesn't need purging, so continue to next drive
                        ReportStatus("No purge required, drive " + testDrive.DriveLetter + " " + Math.Round(driveFreeSpaceGB, 0) + " GB free vs. " + Math.Round(testDrive.MinDriveSpace, 0) + " GB threshold");
                        break;
                    }

                    string pendingWindowsUpdateMessage;
                    if (PRISM.clsWindowsUpdateStatus.ServerUpdatesArePending(DateTime.Now, out pendingWindowsUpdateMessage))
                    {
                        ReportStatus("Exiting: " + pendingWindowsUpdateMessage);
                        break;
                    }

                    if (checkResult == SpaceCheckResults.Error)
                    {
                        // There was an error getting the free space for this drive. Logging handled by IsPurgeRequired
                        m_ErrorCount++;
                        break;
                    }

                    if (!bDriveInfoLogged)
                    {
                        bDriveInfoLogged = true;
                        // Note: there are extra spaces after "required" so the log message lines up with the "No purge required" message
                        ReportStatus("Purge required   , drive " + testDrive.DriveLetter + " " + Math.Round(driveFreeSpaceGB, 0) + " GB free vs. " + Math.Round(testDrive.MinDriveSpace, 0) + " GB threshold");
                    }

                    // Request a purge task
                    var requestResult = m_Task.RequestTask(testDrive.DriveLetter);

                    // Check for an error
                    if (requestResult == EnumRequestTaskResult.ResultError)
                    {
                        // Error requesting task. Error logging handled by RequestTask, so just continue to next purge candidate
                        m_ErrorCount++;
                        repCounter++;
                        continue;
                    }

                    // Check for MC database config change
                    if (requestResult == EnumRequestTaskResult.ConfigChanged)
                    {
                        // Manager control db has changed. Set flag and allow config test at beginning of loop to control restart
                        m_ConfigChanged = true;
                        continue;
                    }

                    // Check for task not assigned
                    if (requestResult == EnumRequestTaskResult.NoTaskFound)
                    {
                        // No purge task assigned. This is a problem because the drive is low on space
                        LogWarning("Drive purge required, but no purge task assigned");
                        break;
                    }

                    // If we got to here, the drive needs purging and a purge task was assigned. So, perform the purge
                    var purgeResult = m_StorageOps.PurgeDataset(m_Task);

                    // Evaluate purge result
                    switch (purgeResult)
                    {
                        case EnumCloseOutType.CLOSEOUT_SUCCESS:
                        case EnumCloseOutType.CLOSEOUT_PURGE_AUTO:
                        case EnumCloseOutType.CLOSEOUT_PURGE_ALL_EXCEPT_QC:
                            repCounter++;
                            m_ErrorCount = 0;
                            break;
                        case EnumCloseOutType.CLOSEOUT_UPDATE_REQUIRED:
                            repCounter++;
                            m_ErrorCount = 0;
                            break;
                        case EnumCloseOutType.CLOSEOUT_FAILED:
                            m_ErrorCount++;
                            repCounter++;
                            break;
                        // Obsolete:
                        //case EnumCloseOutType.CLOSEOUT_WAITING_HASH_FILE:
                        //	repCounter++;
                        //	m_ErrorCount = 0;
                        //	break;
                        case EnumCloseOutType.CLOSEOUT_DRIVE_MISSING:
                        case EnumCloseOutType.CLOSEOUT_DATASET_FOLDER_MISSING_IN_ARCHIVE:
                            repCounter++;
                            folderMissingCount++;
                            break;
                    }

                    // Close the purge task
                    m_Task.CloseTask(purgeResult);

                    if (purgeResult == EnumCloseOutType.CLOSEOUT_DRIVE_MISSING)
                    {
                        LogWarning("Drive not found; moving on to next drive");
                        break;
                    }

                    if (purgeResult == EnumCloseOutType.CLOSEOUT_ARCHIVE_OFFLINE)
                    {
                        LogWarning("Archive is offline; closing the manager");
                        opStatus = DriveOpStatus.Exit_No_Restart;
                        break;
                    }

                }

            }
            catch (Exception ex)
            {
                LogError("Exception in ProcessDrive", ex);
            }

            return opStatus;
        }