Welcome to NavWin!
  

Introduction

Keeping your disk in a non-fragmented state is important if you want to keep your Windows speed at optimal performance. In Windows Vista and above you can schedule this activity to occur if your PC is permanently switched on. However if your PC is not switched on most of the time, then your best option is to target a time shortly after you log in. The defrag.exe has to be run as a background process to keep any impact on your Windows session at a low level. The following C# code will run the standard windows Defrag as a low priority process in the background.

It works on Windows XP and above, and in 32 bit and 64 bit systems. Note that defrag.exe actually triggers a secondary process dfrgntfs.exe which also needs to be forced to run as a low priority process.

The program is configured through the registry. The top level key is:

HKEY_CURRENT_USER\Software\NavWin\Defrag

The screenshot below shows the different settings. LastRunDate is a read/write value.

How to Invoke the Defrag and lower its priority

The following code is used to start a process (Defrag.Exe), lower its priority to ‘BelowNormal’ then search for a spawned process (DfrgNtfs.Exe) and lower its priority to Idle. DfrgNtfs.Exe actually does most of the hard-work so we only want this running in the background.

    /// <summary>

    /// The function starts the Defrag.Exe and waits for if to finish

    /// It ensures the process is run with lower priority and the spawned process DfrgNtfs

    /// is given 'Idle' priority

    /// </summary>

    /// <param name="drive">Drive to defrag - format is "c:" for example</param>

    /// <param name="outputPath">The location to write the results of the defrag</param>

    /// <param name="appendOutput">A value which indicates if the results should be appended or not. This is relevant if more than one drive is being defragged</param>

    private static void Defrag(string drive, string outputPath, bool appendOutput)

    {

        ProcessStartInfo info = new ProcessStartInfo();

        info.FileName = "defrag";

        info.Arguments = drive + " -f";

        info.UseShellExecute = false;

        info.CreateNoWindow = true;

        info.RedirectStandardOutput = true;

 

        Process defrag = Process.Start(info);

        // lower the priority of the DEFRAG EXE to be BelowNormal (this is how Vista sets it in any case)

        // However this is not enough as we also need to lower the priority of DFRGNTFS.EXE

        // which is spawned by DEFRAG

        defrag.PriorityClass = ProcessPriorityClass.BelowNormal;

 

        while (!defrag.HasExited)

        {

            System.Threading.Thread.Sleep(1000);

            // Wait for dfrgntfs.exe to appear as this process needs

            // a lower priority also

            Process[] procs = Process.GetProcessesByName("dfrgntfs");

            if (procs != null && procs.Length > 0)

            {

                procs[0].PriorityClass = ProcessPriorityClass.Idle;

                // Once we have lowered the priority of the dfrgntfs process we can now wait

                // for the main defrag process to finsih normally

                defrag.WaitForExit();

            }

        }

        string result = "Defrag results for drive " + drive + "\r\n\r\n" + defrag.StandardOutput.ReadToEnd();

        if (appendOutput)

            File.AppendAllText(outputPath, "\r\n" + result);

        else

            File.WriteAllText(outputPath, result);

    }

Reading and writing to the registry

The following two help functions are used to access the registry

    private static string ReadSetting(string key, string defaultValue)

    {

        object value = Registry.GetValue(REG_PATH, key, defaultValue);

        if (value == null)

            return defaultValue;

        else

            return value.ToString();

    }

    private static void WriteSetting(string key, string value)

    {

        Registry.SetValue(REG_PATH, key, value);

    }

 

The main control function

This function reads the config settings and runs the defrag process. The defrag has a small impact on your current Windows session. To reduce this, we use a control setting LastRunDate, to ensure we only run this once a day.

In addition, to ensure this program has minimal impact on startup, it will only begin the defrag 5 minutes after Windows has logged in.

    private const string REG_PATH = @"HKEY_CURRENT_USER\Software\NavWin\Defrag";

    static void Main(string[] args)

    {

 

        // Only defrag once a day, use the registry to store this

        string lastRunDate = ReadSetting("LastRunDate", "");

        string today = DateTime.Now.ToString("dd-MM-yyyy");

        if (lastRunDate.ToString() == today)

        {

            // the defrag already ran once today, do not do it again

            return;

        }

        // The very first time the app runs, this reg key will return

        double maxWait = Double.Parse(ReadSetting("MaxWaitMinutes", "5"));

        System.Threading.Thread.Sleep(Convert.ToInt32(1000.0 * 60.0 * maxWait));

 

        string writeResultPath = System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);

        writeResultPath = Path.Combine(writeResultPath, "NavWin");

        if (!Directory.Exists(writeResultPath))

            Directory.CreateDirectory(writeResultPath);

 

        writeResultPath = Path.Combine(writeResultPath, "Defrag");

        if (!Directory.Exists(writeResultPath))

            Directory.CreateDirectory(writeResultPath);

 

        writeResultPath = Path.Combine(writeResultPath, "navwin_defrag_output.txt");

        writeResultPath = ReadSetting("ResultsPath", writeResultPath);

 

 

        // Expected format is space delimited c: d:

        string defragDrives = ReadSetting("DefragDrives", "c:");

 

        char[] sep = {' '};

        string[] drives = defragDrives.Split(sep);

        bool appendOutput = false;

        foreach (string drive in drives)

        {

            if (drives.Length > 0)

            {

                Defrag(drive, writeResultPath, appendOutput);

                appendOutput = true;

            }

        }

 

        Registry.SetValue(REG_PATH, "LastRunDate", today);

        // write the registry for max wait as a convenience

        Registry.SetValue(REG_PATH, "MaxWaitMinutes", maxWait.ToString());

        Registry.SetValue(REG_PATH, "ResultsPath", writeResultPath);

        Registry.SetValue(REG_PATH, "DefragDrives", defragDrives);

    }