ObjBin Cleaner Service

For a long time I have wanted to do a program that on a regular basis would check the folder  with the projects  and delete from there autogenic folders of  type obj and bin, extra everything, generate ReSharper and folders TestResults. For some, this may not be a problem, but if there is enough in the development of several large projects it becomes a problem, all this may take up to 2 gigs, for example, as I have. Can you imagine even imagine a situation where on one machine running a lot of people, each with its own folder with the project and after the session there is no clean up these folders. Once again, here are cleaning working stations, we found that in these locations was 60+ gigs! Thus, the usefulness of utility, which would all have daily been removed – is obvious.

For this purpose, I decided to use the Windows Service. Some people say that’s easier to write .bat file, but I do not own this magic and did not want to mess about, Windows Service seemed more attractive prospect in terms of new knowledge.

Idea

The basic idea was that I would service itself and the program settings of the service. I decided to store the settings in the normal XML file, so you can edit everything by hand if necessary.

The service itself will know which folders to look for other folders matching the specified search parameters and delete everything found. The search itself will take place on schedule. Schedule should be simple, without any tricks like “to run every second Monday in the decade.”

Prepare

Create a new solution and add to the project the first thing Windows Service. While you can leave everything as is. It is so easy only if you have Visual Studio Ultimate or Enterprise. Otherwise, it will be necessary to connect the link to System. ServiceProcess, create a class and inherit from ServiceBase.

The next step is to add the project to specify settings for the GUI. It can be as WinForms, and WPF. I stopped for a second option for most practices. Although in this case no major advantage it has not given.

Since we have a common configuration file, and then service and GUI should get access to it. A little thought, you can guess that the code is the same for both projects, and a direct route to the selection of this functional in a separate library. No sooner said than done. Create another project for a dynamic link library. In a graphical environment for projects and for the service add a reference to this project.

The initial data

It would be good to write basic code, based on already known to the initial data, so as not to hardcode them and understand that we will be in the process, but that will have to compute on the go. Therefore, I propose that we start with a common library.
Since the settings are stored in an XML file and from the nature of the file, I decided to start with the class of the unit for all settings, which will act as the root of the file. Yes, and it will be easier.

[Serializable]
public class CommonInformationStructure {     }

Mark the attribute Serializable, so do not mess around with the serialization manually.
Next, we need:

  • Lists of directories that need to track
  • Lists of patterns to find
  • Schedule
public List<RootFolder> Folders;
public List<SearchPattern> Patterns;
public Schedule Schedule;
public CommonInformationStructure() {
  Folders = new List<RootFolder>();
  Patterns = new List<SearchPattern>();
  Schedule = new Schedule();
}

Classes RootFolder, SearchPattern and Schedule are very simple. But it’s still better to make them into classes as it’s more convenient way to expand the functionality of the program, if we add something else later.

[Serializable]
public class RootFolder {
  public string Path;
}

[Serializable]
public class SearchPattern {
  public string Pattern;
}

[Serializable]
public class Schedule {
  public int Hour;
  public List<DayOfWeek> DaysOfWeek;

  public Schedule() {
    Hour = 9;
    DaysOfWeek = new List<DayOfWeek>();
  }
}

Do not forget to mark each class as serializable.
It seems that now we have the necessary data structure upon which we can develop the basic functionality.

Creating a basic functional

In my view, writing code directly into the service class is not a good idea, because the process of debug and research would be unreasonably prolonged, and the procedure of installation / removal service will stretch. All that makes the service, you can first write in the code for an interface, and then move back to service. I’ve been doing it that way.

Service:

settings.Folders.Select(s => s.Path)
    .ForEach(folder => settings.Patterns.ForEach(p => {
        var directories = Directory.GetDirectories(folder, p.Pattern, SearchOption.AllDirectories);
        foreach (var dir in directories) {
          try {
             Directory.Delete(dir, true);
          }
          catch(Exception e) {
             eventLog.WriteEntry(string.Format("Can't delete folder '{0}' \r\nException rised {1}", dir,e.Message));
          }
        }
    });
);

Actually this will be the main source. Of course not in this form, it is better to divide into smaller functions, add logging everywhere in EventLog, so that  the results will look like there

GetMonitoringFolders(eventLog)
      .ForEach(folder => DeleteAutogeneratedFilesInFolder(eventLog, folder));

I think this is no longer a problem

Saving and reading settings

For this purpose it is best to create a class with static methods, so you can refer to them from any area of ​​application. In our case, this is normal. For such a small application should not be used ServiceLocator or something like that. OOP is not stands for Over Object-oriented Programing.
Since we have all the settings in a single class, it will be access input to maintain and use the output for reading.

public class SettingsService {
  public static void SaveSettings(CommonInformationStructure info) {
    var xmlWriter = XmlWriter.Create(GetPath());
    new XmlSerializer(typeof (CommonInformationStructure)).Serialize(xmlWriter, info);
    xmlWriter.Close();
  }

  public static CommonInformationStructure LoadSettings(string path) {
    if (!File.Exists(path)) {
      SaveSettings(GenerateDefaultValues());
    }

    var xmlReader = File.OpenText(path);
    var settings = ((CommonInformationStructure) new XmlSerializer(typeof (CommonInformationStructure)).Deserialize(xmlReader));
    xmlReader.Close();

    return settings;
  }

  private static CommonInformationStructure GenerateDefaultValues() {
    var info = new CommonInformationStructure();

    info.Patterns.Add(new SearchPattern {Pattern = "obj"});
    info.Patterns.Add(new SearchPattern {Pattern = "bin"});
    info.Patterns.Add(new SearchPattern {Pattern = "_Resharper.*"});

    return info;
  }

  public static string GetPath() {
    var location = Assembly.GetExecutingAssembly().Location;
    var directoryName = Path.GetDirectoryName(location);
    return Path.Combine(directoryName, "Settings.xml");
  }
}

As seen from the listing, we used the standard XmlSerializer class to get a file XML. We do fit perfectly. Read and write file in that case is equally easy.

The path to the file is determined by the path of executable code, which is also in this case, excellent work for Windows Service. You can certainly search in the reestre on behalf of the service and from there to get the path to the installed services and work with a string. But this is too much code.

Now, in order to get the settings it’s enough to write

var settings = SettingsService.LoadSettings();

Creating a service

By default, the service override 2 methods

protected override void OnStart(string[] args) {}

protected override void OnStop() {}

We still will override the method OnContinue, in order to implement support for the suspension of service. Although it is not necessary.

You need to select all the code in the main method  (CleanProjectFolders), in order to be able to use it several times, and put this in its methods OnStart & OnContinue.

protected override void OnStart(string[] args) {
   if (!EventLog.SourceExists("WBR OB Cleaner")) {
      EventLog.CreateEventSource("WBR OB Cleaner", "WBR OB Cleaner");
   }

   CleanProjectFolders();
}

In the OnStart method will still be useful to create EventLog for our service to record there when, how and how successfully service removes the specified folder.

With EventLog work is easy, just create it once

if (!EventLog.SourceExists("WBR OB Cleaner")) {
   EventLog.CreateEventSource("WBR OB Cleaner", "WBR OB Cleaner");
}

So that you can use it everywhere.

var eventLog = new EventLog {
   Log = "WBR OB Cleaner",
   Source = "WBR OB Cleaner"
};

And do not forget to flush it out, and then when you reach a certain limit, the service will fall so that he could not write to the log.

Actually with all the functionality of the service! Simple isn’t it? Mm ..programming all at once easy and difficult.

However, not everything is done in order to make it a service. The simplest, in my opinion, is to generate service installer for this service

Creating an installer for the service

We turn to the class of service, open the properties and see the bottom link «Add Installer». After that, there is another class. Opening it we see the following picture:

Choose to start the second point, and look again at the properties:

Change Account  property for LocalService – this is probably the most important thing, everything else can be left by default.

Now click on serviceInstaller and there change the properties on your own. You can change the service name and a description of it. The rest can be left as is

Save proctitis, collect all should get together without errors.

To install the service manually, execute the command «C: \ WINDOWS \ Microsoft.NET \ Framework \ <framework version> \ InstallUtil.exe C: \ MyService.exe», to remove, use the key «/ u».

Interface

The interface is simple, as the program hello world. In this case I did not even bother to design patterns and just wrote all this in code behind. And I think that’s right, if you do not agree, then you’re stuck in the past, when paying for a line of code;)

The whole interface is one window and three tabs. I even doubt whether it is worth mentioning here about how it’s written. The only thing that I want to say, it is necessary to run an application with administrative privileges, otherwise not be able to create new files ina folder with a program if it is in Program Files.

This is done to add to a project file app.manifest (Add> New item)and modify the lines

<requestedExecutionLevel  level="asInvoker" uiAccess="false" />

На

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

It’s all in the beginning of the file

Oh! It would be nice to know if our service is generally in the systemand how it is able. To find a service, run the following code

var service = ServiceController.GetServices()
    .Where(s => s.ServiceName == serviceName).SingleOrDefault();

Where serviceName is the service name given to a previous images above. ServiceController.GetServices () is in the namespace System.ServiceProcess. There is nothing else interesting on the client does not.

Installation Package

In order to create an installation package by the studio, do the following steps.

Open the solution that you want to create an installation package. Adding a new project.

After that, select the setup project, find the icon «Add custom actions» and click on it. Or you can right-click on the project View> Custom actions.

As a result, open the window with actions for the installation, repair, or uninstall the program.

Again, select the root element in a new window and click the right mouse button. Click on one menu item, and a new dialog box. In it,the drop-down list, select the Application Folder.

Then click on Add File or Add Assembly, depending on the situation.Select the files you want. Then you can watch something like the following picture.

The next step will be to change the name of that user will see when you install the program. You can set the program name, manufacturer, and stuff like that. To do this in the Project Explorer select the project installer and click on the F4 (Properties). There change everything in compliance with the own ideas of beauty =)

As an added convenience when using the software, you can add afolder in the Start menu. To do this, open a window with settings File System. Then find the item User’s Program Menu. Again, using the context menu create a new folder, name it in tune with the service oras fancy will helps, then select the new folder in the left panel againname context menu. There find the item Create New Shortcut

There is a new dialog box where you can select the item that will be a link. Open the Application Folder from the list above chosen and choose what we will have a label. Click OK, and then it will be possible to rename a link to something more digestible and pleasing to the eye.

To install the files, you can set a lot of other service directories. To them something to write act as follows:

Jump to the settings window File System, right click on the root element of the File System on Target Machine, in the context menu, select one item and see the variety of system folders. But these are not limited to, the bottom point visible Custom Folder – which means you can put anything anywhere.

So, compile the project and can install straight from the studio. To do this, open the Solution Explorer, find there a project installer, a shortcut menu and select the item Install.

That’s all. About work with the installer I will tell later

Source code

Hard’n’heavy!

Tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>