Introduction
A while ago, I was looking for an open source disk defragmenter for Windows. None of them had the feature I desired, namely a native executable that would run at boot time. However, I was able to get the author of UltraDefrag to implement this feature in exchange for writing the manual. Soon afterwards, I decided to contribute a utility that would configure UltraDefrag to run as a scheduled task. This was a very simple WinForms app that is required to perform two unique tasks. The first was to add a service to the task scheduler service. The second was to get a list of available drives.
How the Code Works
From a disk drive point of view, I had two concerns: the first was getting a list of drive letters on the machine, and the second was determining which of those drive letters represented hard drives. These were both accomplished via calls to the .NET API as illustrated below:
DriveInfo[] driveInfo = DriveInfo.GetDrives();
List<string> drives = new List<string>();
foreach (DriveInfo drive in driveInfo) {
DriveType driveType = Native.GetDriveType(drive.Name);
if(
driveType != DriveType.Fixed
&&
driveType != DriveType.Removable
&&
driveType != DriveType.RAMDisk
) continue;
if(!is_virtual(drive.Name[0]))
drives.Add(drive.Name);
}
cmbDrives.DataSource = drives;
After getting a list of drive letters, one has to make the API call to add the scheduled task. The API call for this is NetScheduleJobAdd. This API call uses a complex data type that is pretty straight forward. I have documented how to use this on the PInvoke.NET page in the preceding link. The application call to NetScheduleJobAdd
is illustrated below. Note that the function below is a delegate of the ok button's click event.
private void cmdOk_Click(object sender, EventArgs e)
{
IntPtr ptr;
AT_INFO atInfo = new AT_INFO();
atInfo.Command = string.Format("udefrag {0}", cmbDrives.SelectedValue);
atInfo.DaysOfMonth = 0;
atInfo.DaysOfWeek = 0;
atInfo.Flags = AT_JobFlags.JOB_RUN_PERIODICALLY | AT_JobFlags.JOB_NONINTERACTIVE;
atInfo.JobTime = (uint) timePicker.Value.TimeOfDay.TotalMilliseconds;
int jobId;
if (radDaily.Checked)
{
atInfo.DaysOfWeek = 0x7F;
}
else if (radWeekly.Checked)
{
if (chkMonday.Checked) atInfo.DaysOfWeek |= 0x1;
if (chkTuesday.Checked) atInfo.DaysOfWeek |= 0x2;
if (chkWednesday.Checked) atInfo.DaysOfWeek |= 0x4;
if (chkThursday.Checked) atInfo.DaysOfWeek |= 0x8;
if (chkFriday.Checked) atInfo.DaysOfWeek |= 0x10;
if (chkSaturday.Checked) atInfo.DaysOfWeek |= 0x20;
if (chkSunday.Checked) atInfo.DaysOfWeek |= 0x40;
}
ptr = Marshal.AllocHGlobal(Marshal.SizeOf(atInfo));
Marshal.StructureToPtr(atInfo, ptr, true);
Native.NetScheduleJobAdd(null, ptr, out jobId);
}
Points of Interest
Originally, I found the PInvoke code for NetScheduleJobAdd()
at http://www.dotnet247.com/247reference/msgs/47/239675.aspx. However, that URL no longer works and the internet wayback machine has no reference to it. I documented the PInvoke calls on PInvoke.NET.
Further Development
The program is currently hard coded to work with ultra defrag. However, it would be easy to make it configurable to work with any application that operates on hard drives. It is simply a matter of replacing one hard coded string
with a string
loaded from a config file to achieve this functionality. Some other configuration options would have to be added to appropriately rebrand the configuration utility for other applications. For example, the title bar would have to change.
Unfortunately, I don't have the time to make such changes. However, if you think this code would work well with something other than UltraDefrag, I'd be happy to include your changes in the version included with UltraDefrag, pending the approval of Dmitri, UltraDefrag's author.
Links
History
- 7th January, 2009: First version of this article