Click here to Skip to main content
15,892,643 members
Articles / Programming Languages / C#

CleanDesk - A Windows Service that keeps your desktop clean

Rate me:
Please Sign up or sign in to vote.
4.43/5 (7 votes)
10 May 2010CPOL13 min read 50.4K   924   29   8
Automates the Desktop cleanup job.

CleanDeskInstaller

Introduction

CleanDesk is a utility to keep your desktop clean. When you startup your PC, what you see first is your desktop, and most people like it to be clean. Seeing the desktop clogged with junk files which you might have downloaded from the mailbox, all those text files in which you wrote your notes, a plethora of shortcuts to new programs you have installed, and so on might not be a good start to your work.

Some people organize it as folders, some people dump it to a different folder, some people leave it as it is, some people re-install Windows (:) just an exaggeration). So basically, everybody likes their desktop clean. I would prefer an empty desktop, at the most two or three files. But it can get cumbersome at times organizing it because the number of objects might be too many. So I thought, let me try and automate this.

Yes, I know there is a Windows desktop cleanup wizard. But I prefer to have my own service, purely because of some additional tasks that I needed to perform like organize the files logically into folders instead of simply dumping into one folder, logging the activities, making my PC to do this automatically when I shutdown, and so on. What came out of these requirements is a Windows service, that moves the contents of your desktop to a pre-specified location, with additional sorting. This is performed when your system shuts down (or starts up, depending on your preference). I have a clean desktop now, and my files are organised, while I can work temporarily on the desktop and don't bother to organize it.

Although the story is presented here in the background of CleanDesk, I believe it is a job which includes creating a Windows service, making it do some file related work, and storing the settings in a file. The code can also act as a base for similar tasks.

Primer

Please note that the program is developed using Visual Studio 2010 which will be required to open the accompanying source code.

Before I get into the technical details of CleanDesk, let me tell you that it doesn't have a face (as of now), it just has a good soul - meaning, it is just a Windows service and I have not put an icon and options menu in the system tray. This will be done in the next stage, but for now, I have done it as a .NET based Windows Service. For now, you will have to use the services console to see the service and interact with it.

Two technical aspects worth mentioning here:

1. Windows Service

A Windows service is a daemon that runs in the background, performing your work. It usually doesn't have a front end.

When it comes to developing a Windows service using .NET, you have a template, which simplifies the task. The three functions that you should be familiar with at the very least when dealing with this job are:

  • OnStart - This is a function, which will be called by the framework when the service starts up. Usually, this is where you might want to invoke your worker thread as per the business logic you are putting in.
  • OnStop - Will be called when the service is stopped from the services console panel. You might need to do your cleanup jobs here, like freeing up memory used and so on.
  • OnShutdown - Will be called when the OS is shut down, while the service is in started state. Code written here will be executed when your Operating System is shutdown.

For CleanDesk, I have used only these three functions, and in fact I only needed two. I needed to check if my folders are in place and start the organization (if the setting specifies it to do at startup) in the OnStart, or do the organization when the system is shutdown if the settings says so, at OnShutdown. I used OnStop only to write logs.

Installing a Windows service involves executing the command installutil. One important point worth mentioning here is that we have to use a user account when installing the service and not local system or the admin account. The way, CleanDesk finds the desktop is based on your login credentials. If you want to use one of the other possible approaches, like installing using the local system account, there might be slight tweaking required. Anyways, I used the approach of using the user account, by specifying the username and password and letting the service automatically find out the desktop. Installutil comes with Visual Studio, and you have to use the Visual Studio console to run it. If you are using Express Edition, you may have to install the .NET Framework SDK for this. If you simply want to install the service without bothering to change the code or anything, I have provided a Windows setup project for CleanDesk which does just this. Just run the setup and give your username and password to install the service. The process should look like this:

Step 1: Run the setup. Give the username of the account for which you want to install CleanDesk. Note that the desktop of the given account's owner will be organized by CleanDesk.

Installation.jpg

Step 2: Verify the service at the services console. You can go to "run" and type services.msc to access the services console. Or you can use the computer management snap-in to access this console. The first time after you install the service, you will have to start the service explicitly. The next time onwards, it will start with Windows.

CleanDeskService.jpg

Also, I have not tried to change the laws of the society here. You need to have access to your whereabouts to use it. The service acts as your surrogate when doing its job of moving the files to the necessary folders and cleaning up the desktop. So the user account under which the service is installed will sort that user's desktop, and he/she is assumed to have rights both on the desktop and the destination folder specified. Dealing with rights can be frustrating at times, but yes, the whole point is - your security.

Windows event viewer is a good help to use when you face issues with installing and running the service, like service not able to move the file, etc.

2. Using Settings in .NET Projects

To add settings to your project, go to the properties of your project and select the Settings tab. In the Settings tab, select Add Settings. Once you have added the settings, you will get a grid, where you will be able to add your contents. The settings grid consists of the columns: Name, Type, Scope, and Value.

Once you have added the values here, what you get is an XML file with the .config file prepended with the application name. This is a very easy way to store your application settings. You can specify the values at design time or at run time. If you decide to do it at design time, these are the steps that you can follow (as per Microsoft):

In Solution Explorer, expand the Properties node of your project.

In Solution Explorer, double-click the .settings file in which you want to add a new setting. The default name for this file is Settings.settings. In the Settings designer, set the Name, Type, Scope, and Value for your setting. Each row represents a single setting. The diagram here should better explain this.

Image 4

Once you want to change the settings, follow this:

  • In Solution Explorer, expand the Properties node of your project.
  • In Solution Explorer, double-click the .settings file in which you want to add a new setting. The default name for this file is Settings.settings.
  • In the Settings Designer, find the setting you want to change and type the new value in the Value column.

Once CleanDesk is installed and you want to change the values, do this:

  • Using Microsoft Notepad or some other text or XML editor, open the <AssemblyName>.exe.config file associated with your application.
  • Locate the entry for the setting you want to change.

For instance, if you want to change the log path, locate the value Log and change its value as required:

XML
<setting name="Log" serializeAs="String">
   <value>d:\log.txt</value>
</setting>

Type a new value for your setting and save the file.

In CleanDesk, I have used StringCollection as well, to store the extension->folder map. Yes, the setting does allow to store collections as well, which I have made use of.

Dive into the Code

Before getting into the specifics of the code, let me explain a couple of CleanDesk terms here. A Runis a running sequence number which I will be appending to a file/folder name, in the event it already exists in the destination location. A DropBox is a location where the files with an unspecified extension will be dropped into. The user will have to manually sort it from here. One can also think of a rules based sorting here, but as of now, let's keep it simple.

Let's get deeper into the code now. We have two classes which are the powerhouses of CleanDesk. Shown below is the class diagram.

Classes.jpg

It's pretty mundane. No exciting oops here. Most of them are static functions. Oh yeah, the job in hand is something like a script which performs the task of moving the file, which you might be scheduling it to Tivoli (a job scheduler from IBM). As the name explains, I have grouped all the file related functions in FileRelatedFunctions and the organization related functions in OrganizerCode.

The core of the program is the function StartOrganizing(). This function does the job of moving the files in the desktop to its organized folder. To simplify the explanation of business logic, let's take the help of a flowchart. The one below does just that.

FlowChart.jpg

Here's the corresponding code for the flowchart:

C#
/// <summary>
/// Function which does the actual sort and move functinality
/// </summary>
/// <param name="objectsOnDesktop">A list of files
///         and folders on the Desktop</param>
public static void StartOrganizing(String[] objectsOnDesktop)
{
    String moveFileTo = String.Empty;
    String newFileName = string.Empty;
    String newFolderName = string.Empty;
    int attempt = 0;

    foreach (String objectOnDesktop in objectsOnDesktop)
    {
        try
        {
            FileInfo fi = new FileInfo(objectOnDesktop);

            if (fi.Attributes != FileAttributes.Directory)
            {
                // check to see of the file or folder is there in the exclusion list
                // if so, bypass it
                if (IgnorableFiles.Contains(fi.Name.ToLower()))
                    continue;

                // Determine the moveto folder.
                moveFileTo = FolderExtensionMapping.ContainsKey(
                    fi.Extension.Remove(0, 1)) ?
                    FolderExtensionMapping[fi.Extension.Remove(0, 1).ToLower()] : 
                    "DropBox";

                // To check if a directory exists
                // as destination for the given extension
                DirectoryInfo di = 
                  new DirectoryInfo(OrganizedFolder + "\\" + moveFileTo);

                // If there is no directory let's create it
                if (!di.Exists)
                    di.Create();

                // Log the attempt to move the file
                FileRelatedFunctions.WriteLog("Attempting to move " + 
                                     fi.FullName + " to " + di.FullName);

                newFileName = fi.Name;
                // If the file already exists append
                // a running sequence number and copy.
                FileInfo checkDest = new FileInfo(OrganizedFolder + "\\" + 
                                     moveFileTo + "\\" + newFileName);
                if (checkDest.Exists)
                {
                    FileRelatedFunctions.WriteLog("Destination file " + 
                        "already exists. Attempting with new extension");
                    newFileName = GetUniqueFileName(newFileName);

                    attempt = 1;
                    bool fileExists = true;

                    // While a folder exists and my tries
                    // are less than the cap as per mention in setup
                    while (fileExists && (attempt <= Cap))
                    {
                        FileInfo checkDestAgain = new FileInfo(OrganizedFolder + 
                                 "\\" + moveFileTo + "\\" + newFileName);
                        if (checkDestAgain.Exists)
                            fileExists = true;
                        else
                            fileExists = false;

                        if (fileExists)
                        {
                            newFileName = GetUniqueFileName(newFileName);
                            attempt++;
                        }
                    }
                }

                if (attempt != Cap)
                {
                    FileRelatedFunctions.WriteLog("Attempt = " + 
                       attempt.ToString() + " : Cap = " + Cap.ToString());

                    // Don't try to overwrite. We will play safe here
                    fi.CopyTo(OrganizedFolder + "\\" + moveFileTo + 
                              "\\" + newFileName, false);

                    FileInfo newFi = new FileInfo(OrganizedFolder + "\\" + 
                                         moveFileTo + "\\" + newFileName);
                    if (newFi.Length == fi.Length)
                    {
                        FileRelatedFunctions.WriteLog("File Size Verification Sucess");
                        // We have copied the file. We have verified
                        // the copied original files to be of same size
                        // Now we will delete the originale file
                        fi.Delete();
                    }

                    // No exception raised. High probanility that the proces is successfull
                    FileRelatedFunctions.WriteLog("Move File Complete. Source = [" + 
                                         fi.FullName + "] Dest = [" + OrganizedFolder + 
                                         "\\" + moveFileTo + "\\" + 
                                         newFileName + "]");
                }
                else
                {
                    FileRelatedFunctions.WriteLog("Same file exists more than " + 
                         Cap.ToString() + " times. Aborting move file operation.");
                }
            }
            else
            {
                DirectoryInfo di = new DirectoryInfo(fi.FullName);
                newFolderName = fi.Name;

                FileRelatedFunctions.WriteLog("Attempting to move folder " + 
                    fi.FullName + " to " + OrganizedFolder + 
                    "\\Folders\\");

                // Check if folder already exists. Is so assign
                // a running sequence number before proceeding
                DirectoryInfo newDi = new DirectoryInfo(OrganizedFolder + 
                                      "\\Folders\\" + newFolderName);
                if (newDi.Exists)
                {
                    FileRelatedFunctions.WriteLog("Destination folder " + 
                       "already exisis. Renaming using running sequence");
                    newFolderName = GetUniqueFileName(newFolderName);

                    attempt = 1;
                    bool folderExists = true;
                    while (folderExists && (attempt <= Cap))
                    {
                        newDi = new DirectoryInfo(OrganizedFolder + 
                                    "\\Folders\\" + newFolderName);

                        if (newDi.Exists)
                        {
                            folderExists = true;
                            newFolderName = GetUniqueFileName(newFolderName);
                        }
                        else
                            folderExists = false;

                        if(folderExists)
                            attempt++;
                    }
                }

                // Do only if we have got a sequence number less than cap
                if (attempt != Cap)
                {
                    if (Directory.GetDirectoryRoot(fi.FullName) == 
                        Directory.GetDirectoryRoot(OrganizedFolder + 
                        "\\Folders\\"))
                    {
                        //Attempt to copy the directory
                        Directory.Move(fi.FullName, OrganizedFolder + 
                            "\\Folders\\" + newFolderName);
                    }
                    else
                    {
                        FileRelatedFunctions.CopyDirectory(di, 
                          new DirectoryInfo(OrganizedFolder + 
                          "\\Folders\\" + newFolderName));

                        // We will verify if all files are of equal size.
                        // If okay, delete the desktop folder
                        if (FileRelatedFunctions.CompareDirectories(di, 
                            new DirectoryInfo(OrganizedFolder + 
                            "\\Folders\\" + newFolderName)))
                        {
                            FileRelatedFunctions.WriteLog("File Sizes" + 
                               " inside Folder Verification Sucess");
                            Directory.Delete(di.FullName, true);
                        }
                    }

                    FileRelatedFunctions.WriteLog(
                      "Move File Complete. Source = [" + di.FullName + 
                      "] Dest = [" + OrganizedFolder + 
                      "\\Folders\\" + newFolderName + "]");
                }
                else
                {
                    FileRelatedFunctions.WriteLog("Same folder exists more than" + 
                                                  Cap.ToString() + " times.");
                }
            }

        }
        catch (Exception ex)
        {
            // Write the exception as well, to make deuggin easier
            FileRelatedFunctions.WriteLog(ex.ToString());
            FileRelatedFunctions.WriteLog("Fail");
        }
    }
}

It is pretty long, but mostly dominated by conditional statements as I am doing a lot of checks. Moving the folder to the destination has a slightly additional functionality than the flowchart. It checks for the extension (if it is a file) and moves the file to the destination folder + into the folder name for the particular extension as mentioned in the setting file. I will explain this setting in a short while.

To log, I've used a simple text writer that writes to a log file. Obviously, the log should be in a location where the service (the user account) has read/write access. As you can see, the code heavily depends on FileInfo and FolderInfo to do its job. These are functions provided by the .NET Framework to perform file related operations. Cool, we have the core part explained. Let me round it off with other parts which complete the program.

Well, as I have mentioned before, the logical beginning of the program is the OnStart function. The program checks for the settings to be performed on startup. If any, do the organizing, else leave it for the shut down. If the job is not to be performed at Windows startup, then it has to be when the OS shuts down, and hence the function call in OnShutdown. Here are the three functions I have been mentioning.

The running sequence number is derived using a fairly simple logic. The program tries to find a non-existing file name with a running sequence number till a cap value mentioned in the settings. The default value is 99 which, of course, you can change. It means that there can be file names abc.txt, abx(01).txt, abc(02).txt, till abc(98).txt. So at a time, 99 copies of the files will be there. You can set this value till int.Maximum to get an unlimited run. Anyways, I preferred to keep the run to a manageable limit - 99, but you may change it as per your requirement.

C#
/// <summary>
/// Function called when the service is started up
/// </summary>
/// <param name="args">Arguments from the services console</param>
protected override void OnStart(string[] args)
{
    FileRelatedFunctions.WriteLog("-------STARTED----------");
    if (OrganizerCore.Startup)
        DoOrganize();
}

/// <summary>
/// Called when the service is explicitly stopped
/// </summary>
protected override void OnStop()
{
    FileRelatedFunctions.WriteLog("-------STOPPED---------");
}

/// <summary>
/// Called when the OS is shutdown
/// </summary>
protected override void OnShutdown()
{
    if (!OrganizerCore.Startup)
        DoOrganize();

    FileRelatedFunctions.WriteLog("-------SHUTDOWN---------");
}

The DoOrganize function completes the job.

C#
/// <summary>
/// This function does the job of organizing the files and folders
/// </summary>
private void DoOrganize()
{
    String[] filesAndFoldersOnDesktop = 
             FileRelatedFunctions.GetAllFilesAndFolders();
    OrganizerCore.StartOrganizing(filesAndFoldersOnDesktop);
}

CleanDesk Settings

The initialization is done in the constructor of the service class. It reads the services. The various settings possible are:

  • ExtenstionMap: This is the string which tells CleanDesk which extension goes where. The format is extension->FolderName. When CleanDesk tries to move the file/folder, it checks if the folder exists, else creates one and moves the file/folder. This is where you should be adding more extensions as per your requirement. For example, if you need to move all your zip files to a folder by the name "compressed files", then the syntax would be zip, compressed files.

  • ExclusionList: Some of the files on the desktop, like a reference Excel, should remain on the desktop, which the user wants to see on the desktop. Well, exclude these. Ideally, desktop.ini should also be excluded, which I have already added in the settings file.
  • Log: Where the service should write the log. Please take care of the log very well; it is very, very important to track down what happens to the files/folders processed by CleanDesk.
  • OrganizedFolder: The destination folder to which your files will be placed to. The new folders will be inside the destination. It will also have a folder by the name "folders" where all your folders will be.
  • Schedule: The string "shutdown" says the organization should be performed when the OS is shutdown, which I have kept as default. Any other text means, do the job at Windows startup.
  • Cap: is the upper limit of the running sequence number. For example, if Cap is 2, the running sequence numbers generated are (01),(02) ; if Cap is 3, the numbers would be (01),(02),(03).

I am also providing installation for the service. This is again done by the Visual Studio Setup and Deployment Wizard. Please refer here for a detailed explanation on doing this. No point repeating it here. Do note that the settings reside in the same folder as the executable and by the name of CleanDesk.exe.config.By default, I have pointed the log to d:\log.txt. Please change it as per your convenience.

Words of Caution

CleanDesk moves your files. You can see that I have done it in three steps as an additional precaution, instead of a one step move. Copy to destination->Verify destination->Delete source. I have been using CleanDesk for sometime now, and I have not lost any file. Still, I would suggest monitor for a few days when you install it first on your system just to add more precaution.

Why shouldn't you be installing a similar program (other than CleanDesk) if it exists and even if it is free from the internet? Simple, your are allowing access to your files and it's very, very risky. CleanDesk provides you full source code and I would suggest verifying the code once, compiling it yourself, and installing the compiled copy. The safest way to a clean desktop.

Conclusion

The code is very well commented, so there shouldn't be a problem understanding it. Any suggestions, enhancements regarding the program or the article are welcome. Thanks for your time.

Points of Interest

I have not used Test Driven Development here. I followed Test Assisted Development (if such a term exists) for one of my functions. But adding additional test cases for the file related functions should make it more affable.

I will try to do this, but if not, please consider trying it yourself: Sorting based on the file/folder name to a relevant folder, automatically. For instance, a folder containing a file by the extension .csproj should go into your programming->CSharp->ProjectName folder. Another example, a file by the name MyAccounts.xlsx should go to folder named "Finance", and so on. Well, it is AI. But not very complex, I guess. Basically, it is about identifying the context of the word and naming the folder accordingly.

A settings dialog and a tray bar icon is on the way.

History

  • 09/May/2010 - Initial post.
  • 11/May/2010 - Changes to fix issues with regard to the running sequence number generation logic. The value ahs been made configurable, and explanation added in the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Singapore Singapore
He is a Microsoft technology enthusiast, who wish to create applications which others find useful.He loves making small tools and getting involved in architecting bigger systems.

He is currently working as a professional developer in a software development firm in .Net technologies.

He likes reading technical blogs, contributing to opensource and most importantly, enjoying life.

His ambition is to be an impressive software maker.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey20-Feb-12 19:34
professionalManoj Kumar Choubey20-Feb-12 19:34 
QuestionThis service on a server Pin
DDD_KKK13-Feb-12 23:23
DDD_KKK13-Feb-12 23:23 
QuestionWhile instalation where should we specify, Username and pasword Pin
DDD_KKK13-Feb-12 3:23
DDD_KKK13-Feb-12 3:23 
Questionwhat happends after 99 tries? Pin
c0ax_lx10-May-10 4:35
c0ax_lx10-May-10 4:35 
AnswerRe: what happends after 99 tries? Pin
Ajay Britto10-May-10 18:09
Ajay Britto10-May-10 18:09 
Generallazzines Pin
radioman.lt9-May-10 6:41
radioman.lt9-May-10 6:41 
GeneralRe: lazzines Pin
Ajay Britto9-May-10 13:07
Ajay Britto9-May-10 13:07 
GeneralRe: lazzines Pin
radioman.lt10-May-10 2:02
radioman.lt10-May-10 2:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.