Category Archives: Geek

HTTPAuth Logout in Chrome, Firefox and IE with jQuery

I’ve recently setup a site which has multiple users and uses HTTP Auth. I wanted to implement a Logout button to allow switching users. Turns out, there’s no easy programmatic way to stop a browser from remembering your username and password.

Being a client feature it must be done in JavaScript, and since I’m using jQuery on the page I’ve utilised that. It should be easy enough to translate this into vanilla JavaScript, feel free to send me another version and I’ll post it alongside.

After much reading I came across this post: http://tom-mcgee.com/blog/archives/4435 which works well in Firefox and Internet Explorer, below is my modified version which also works in Chrome. (I’ve only tested this on the latest versions of these browsers).

I’ve basically added a fallback which should work everywhere, all be it in a not entirely beautiful fashion (it doesn’t work in the background, and forces an authentication popup where you might prefer to redirect to another page).This refreshes the page to request another login, but you could easily adapt it to send the user to a different page.

$(function(){
    $('#user_logout').on('click', function(e){
        // HTTPAuth Logout code based on: http://tom-mcgee.com/blog/archives/4435
        e.preventDefault();
        try {
            // This is for Firefox
            $.ajax({
                // This can be any path on your same domain which requires HTTPAuth
                url: "/any/path",
                username: 'reset',
                password: 'reset',
                // If the return is 401, refresh the page to request new details.
                statusCode: { 401: function() {
                    document.location = document.location;
                    }
                }
            });
        } catch (exception) {
            // Firefox throws an exception since we didn't handle anything but a 401 above
            // This line works only in IE
            if (!document.execCommand("ClearAuthenticationCache")) {
                // exeCommand returns false if it didn't work (which happens in Chrome) so as a last
                // resort refresh the page providing new, invalid details.
                document.location = "http://reset:reset@" + document.location.hostname + document.location.pathname;
            }
        }
    });
});

Creating a Microsoft Office 365 Offline Installer

Update: This post is getting quite old now, and the process may well have changed. Please be sure to check the comments where some helpful people have posted updates, and please also share any tips you come across for future visitors too.

Recently I had to install Office 365 on a dozen machines at a location with 512mb Internet. After the nostalgia faded I wrestled through the directions to obtain an Offline installer. In the hope this will save somebody some head scratching, here are the directions.

This will install Office 365 Pro ready for you to activate it with your Office 365 Organisational Account. It’s been tested with an Office 365 Education A3 license.

1. Download the Office Deployment Tool

Download and run this tool, then give it a folder where you’d like it to extract the files we’re going to need. If you want both 32 and 64 bit versions of Office, you’re going to have to repeat this whole process again. I pointed this tool at a folder called office_installer_32 and then at office_installer_64

You should see a file called setup.exe and one called configuration.xml

Download Here:

2. Edit the XML configuration file

This file tells the tool which versions of the software you’d like to download and install.

In the same directory where you extracted the installer tool, edit the file called “configuration.xml” and fill it with the following, change the OfficeClientEdition value to 32 for a 32 bit  version.

<Configuration>
  <Add OfficeClientEdition="64" >
    <Product ID="O365ProPlusRetail">
      <Language ID="en-us" />
    </Product>
  </Add>
  <Updates Enabled="TRUE" />
  <Display Level="None" AcceptEULA="TRUE" />
  <Property Name="AUTOACTIVATE" Value="1" />
</Configuration>

This will install Office 365 Pro, automatically activate it (though it’ll still ask for your Office account), not show you the EULA (because it’s quicker to get it installed that way) and enable updates. If you have a file server which you wish to use, consider using the SourcePath parameter under both <add> and <updates>. See the Configuration Reference for more details.

See the XML Configuration Reference for more details: http://technet.microsoft.com/EN-US/library/jj219426.aspx

3. Do the Download

Now open a command prompt in the folder which contains setup.exe and run:

setup.exe /download configuration.xml

This will download all the files required to install that which is specified in the configuration.xml. All in all, it’s about 1.1GB (0.9GB for 32 bit). All the files get put into a folder called ‘Office’ unless you specified ‘sourcePath’.

4. Install It

You install Office running:

setup.exe /configure configuration.xml

in the same was as you downloaded it. To speed this up if you’re running on multiple machines, create a file called setup.bat and fill it with the command above. Now, double clicking this file will install Office. The setup should require no interaction until it’s done and ready to activate.

Optional Extras

If you’re doing this for somebody else, you may want to also visit Office.com in their web browser and Always Enable the addon.

When you first use the Office products you will need to activate it by logging in with your username and password.

A note on licences

After you activate your suite with your login, if you delete that user the software will tell you to login to check things are OK. I have not found how you can re-activate with a different login.

Some of the official pages I used

Category:

Comparison of Cloud Storage Options

Over the past few months I’ve switched between several cloud-storage options. Here’s an overview of my thoughts on each. These are in the order which I tried them in with the newest (and so my current favourite) at the top.

Unless otherwise noted, all of these features are available in the free version also.

Cubby

Pros

  • Cheap (may go up after Beta period).
  • DirectSync means you can use the Cubby software to sync files between computers on your LAN without those files counting towards your quota (unless you are also syncing them to the cloud) Only for paid accounts
  • Very simple interface.
  • Easily lets you merge a Cloud folder with a Local folder.

Cons

  • You get told how many files and how many bytes still need to be synced, but you don’t get any indication as to the speed it’s transferring at.
  • You can share a Cubby with other users, but you can’t share only a subfolder.
  • Support are not very helpful. Syncing got stuck with files left for me on two machines, on one occasion a support guy phoned me to try and diagnose, but basically told me to look through the log file myself and find the problem. Email support then refused to help because I had mentioned I’d used a network drive on one machine, they left me with “we don’t support network drives” regardless of my other machine having the same problem without the network drive.

Wuala

Pros

  • The security concious will enjoy their pre-transfer encryption.
  • Sync files and folders in place, no need to move or symlink them.
  • Backup Option lets you have a single-directional syncing folder.
  • Small, cheap options for sizing.
  • Easy to contact support by email or through the forum.

Cons

  • Very limited view of the status of your files.
  • Requires Java for both

SparkleShare

Self-Hosted option, based on Git.

Pros

  • Self Hosting gives you flexibility and the option of cheap space.

Cons

  • Self-Hosting means you’re responsible for your own redundancy.
  • On large amounts of files (dozens of thousands) SparkleShare appeared to try and handle them in batches of a few hundred, but still froze and never finshed.

Google Drive

Pros

  • Get to your files quickly if you use Gmail.

Cons

  • Hogged resources when tracking many files (I added dozens of thousands, and often had to quit Drive to keep everything else running).
  • You have to keep your files in the Google Drive folder.
  • Doesn’t follow symlinks.
  • You can only sync into an empty folder, you cannot merge your Drive contents with some local files (an issuee after a reinstall) to save downloading files again.

Dropbox

Pros

  • Most of the people I’ve wanted to share files with, already have Dropbox.
  • Easily see the status of your files and you have the information you need to judge how long until everythnig is in sync.
  • Share individual folders with other users.
  • Follows Symlinks (Junctions/Hardlinks).

Cons

  • The smallest package is £10 for 100GB, if you only need 20GB it can feel nasty to pay for all the space you don’t need.
  • Based on Amazon s3 you’re supporting Amazon and their less than friendly business and environmental practices.
  • All your files must be in your Dropbox folder, however following Symlinks eases this trouble.
Categories:

Using xDebug’s Clickable Stack trace with Programmer’s Notepad (and MINGW32) in Windows

I’ve spent far more time trying to make this work than I care to admit. The mistake I’ve made in the past is to try and do this in a batch file, I’ve now done it in bash (which I have anyway since I installed Git and asked for unix-tools to be globally available).

xDebug lets you turn file names into click able links, which can in turn be used to launch your text editor in the right place.

Requirements:

  • bash & sed – These come when you install Git and ask for the Unix tools to be globally available
  • Programmers Notepad – naturally. Though this is easy enough to tweak to any editor.
  • Firefox and xDebug

Step 1 – Create the SH file

Put this text into a file called “EditWithPnotepad.sh” and save it somewhere sensible. You may need to tweak the last line, especially if you want to use this for an editor other than PNotepad.

#!/bin/bash
uo=$1
protocol=$(echo $uo | sed 's/\([a-z]*\):\/\/\(.*\):\([0-9]*\)\//\1/');
file=$(echo $uo | sed 's/\([a-z]*\):\/\/\(.*\):\([0-9]*\)\//\2/');
line=$(echo $uo | sed 's/\([A-z]*\):\/\/\(.*\):\([0-9]*\)\//\3/');
"C:\Program Files (x86)\Programmer's Notepad\pn.exe" --line $line "$file" &

Step 2 – Register the protocol

Open regedit and create the following keys – we’ll set the values after. Do this by right clicking on the parent (HKEY_CLASSES_ROOT, then pontepad etc) and clicking on New > Key.

[HKEY_CLASSES_ROOT\pnotepad]
[HKEY_CLASSES_ROOT\pnotepad\shell]
[HKEY_CLASSES_ROOT\pnotepad\shell\open]
[HKEY_CLASSES_ROOT\pnotepad\shell\open\command]

Click on pnotepad and the double click on (default) to change it. Set it to “URL: PNotepad Protocol”.

Under that same (default) key right click in the blank space and click on New > Key and name it “URL Protocol”. It needs no value.

Now click on Command and set its (default) to your own version of the following, be careful not to mess up the quotes or spaces.

"C:\Program Files (x86)\Git\bin\sh.exe" "D:\Path\To\Your\EditWithPnotepad.sh" "%1"

Step 3 – Tell xDebug how to format URLs

Open up your php.ini and add or edit this line, then restart PHP/Apache as required.

xdebug.file_link_format="pnotepad://%f:%l"

Step 4 – Test

That should be it, make some code code throw an error and try clicking the link.

Any touble, or updates let me know!

Categories:

Magical Image Resizing URL’s with Zend_Framework (and cached for Apache)

As a lazy programmer I don’t like resizing images all the time. So I made this bit of code to let me write a URL including the file size and have the work done for me.

  • Written for Zend Framework 1.11.12
  • It will resize the image and save it into the correct location so that Apache will find it next time and not call the PHP.
  • You can specify the required width, height, both or neither.
  • If a width or height is given as 0 then the original dimension will be used.

Add some settings to application.ini

cache.publicDir = '/cache'
cache.dataDir = APPLICATION_PATH "/../public/cache"
data.dataDir = APPLICATION_PATH "/../public/data

Add a route in the Bootstrap

The regex router must match a URL containing the required file sizes, the file name and as many sub directories as thrown at it. It matches the following and sends them to the ThumbAction on FilesController:

  • /cache/100/200/file.jpg
  • /cache/100/200/subdir/file.jpg
  • /cache/100/200/another-subdir/file.jpg
$router->addRoute('file_resized_thumbnail', new Zend_Controller_Router_Route_Regex(
	'cache/([0-9]*)/([0-9]*)(.*/?)/([^/]*?)',
		array(
			'controller'	=> 'files',
			'action'		=> 'thumb',
		),
		array(
			1	=> 'width',
			2	=> 'height',
			3	=> 'subdir',
			4	=> 'file'
		),
		'/cache/%d/%d/%s/%s'
));

FilesController.php::ThumbAction()

/**
*	Loads the file, gets a resized version and saves it in a location identical to 
*	the current URL so Apache can find it next time.
**/
public function thumbAction()
{
	$this->view->layout()->disableLayout();
	$options = $this->_helper->container->get('options');
	
	$fileName = $this->getRequest()->getParam('file');
	$subdir = $this->getRequest()->getParam('subdir');
	$width = $this->getRequest()->getParam('width');
	$height = $this->getRequest()->getParam('height');
	
	$cacheUrlPrefix = $options['cache']['publicDir'];
	$cachePathPrefix = $options['cache']['dataDir'];
	$dataPathPrefix = $options['data']['dataDir'];
	
	$fileName = urldecode($fileName);
	$subdir = ($subdir == "") ? "" : urldecode($subdir);
	
	$fullPath = $dataPathPrefix.'/'.$subdir.$fileName;
	
	if(realpath($fullPath) == false){
		throw new Zend_Controller_Action_Exception('File not Found: '.$fullPath, 404);
	}
	$file = new Pata_File(array(
		'path'			=>	$fullPath
	));
	
	if ($file == false) {
		throw new Zend_Controller_Dispatcher_Exception('That file was not found in that subdir');
	}
	$cacheDir = $cachePathPrefix.'/'.$width.'/'.$height.$subdir;
	$cacheFile = $cacheDir.'/'.$fileName;
	if (file_exists($cacheDir) == false) {
		mkdir($cacheDir, 0777, true);
	}
	imagepng($file->getThumbnailPng($width, $height), $cacheFile);
	
	header('content-type: image/png');
	readfile($cacheFile);
}

library/Pata/File.php

/*
*	This is a model I use to make working with files a bit easier. Several of the functions are used by thumbAction but the code could be moved to the controller if you so desired.
*/
<?php
class Pata_File
{
	protected $path;
	protected $relativePath;
	protected $url;
	protected $image;
	protected $thumbUrl;
	protected $thumbnailDimensions = array();

	protected $thumbnailPng = array();
	
	protected $description;
	
	public function __construct($options)
	{
		if (isset($options['path'])) {
			$this->path = $options['path'];
		}
		if (isset($options['relativePath'])) {
			$this->relativePath = $options['relativePath'];
		}
		if (isset($options['url'])) {
			$this->url = $options['url'];
		}
		if (isset($options['thumbUrl'])) {
			$this->thumbUrl = $options['thumbUrl'];
		}
	}

	public function getName()
	{
		return basename($this->getPath());
	}
	
	public function getPath()
	{
		if ($this->path == null) {
			throw new Exception('No path has been set');
		}
		return $this->path;
	}
	
	public function getRelativePath()
	{
		return $this->relativePath;
	}
	
	public function getDir()
	{
		return substr($this->getPath(), 0, - strlen($this->getName()));
	}
	
	public function setDescription($description)
	{
		$this->description = $description;
	}
	
	public function getDescription()
	{
		return $this->description;
	}
	
	/**
	 * The URL to the actual file itself - not a page displaying it, see getPageUrl()
	 * @return type 
	 */
	public function getUrl($width = 800, $height = null)
	{
		if ($this->url == null) {
			throw new Exception('No url has been set');
		}
		$height = $height ?: round($width * 0.75, 0);
		return str_replace(array(':width:', ':height:'), array($width, $height), $this->url);
	}
	
	public function getThumbUrl($width = 120, $height = null)
	{
		if ($this->thumbUrl == null) {
			throw new Exception('No thumbUrl has been set');
		}
		$height = $height ?: round($width * 0.75, 0);
		return str_replace(array(':width:', ':height:'), array($width, $height), $this->thumbUrl);
	}

	public function setPath($new)
	{
		$this->path = $new;
	}
	
	public function setUrl($new)
	{
		$this->url = $new;
	}
	
	public function setThumbUrl($new)
	{
		$this->thumbUrl = $new;
	}
	
	public function setPageUrl($new)
	{
		$this->pageUrl = $new;
	}
	
	/**
	 *	Page Url is the URL to the page on which this image can be viewed
	 */
	public function getPageUrl()
	{
		if ($this->pageUrl == null) {
			throw new Exception('No pageUrl has been set');
		}
		return $this->pageUrl;
	}
	
	public function getExtention()
	{
		if (preg_match('/\.[A-z0-9]+$/', $this->getPath(), $ext)) {
			$ext = $ext[0];
			$ext = substr($ext, 1);
		} else {
			$finfo = new finfo(FILEINFO_MIME);
			$mime = $finfo->file($this->getPath());
			$mime = explode(";", $mime);
			$mime = $mime[0];
			$mimeToExt = array(
				"text/plain" => "txt",
			);
			if (isset($mimeToExt[$mime])) {
				$ext = $mimeToExt[$mime];
			} else {
				$ext = "";
			}
		}
		return $ext;
	}
	
	public function freeMemory()
	{
		imagedestroy($this->getImage());
		$this->img = null;
	}
	
	public function getImage()
	{
		if ($this->image == null) {
			switch(strtolower($this->getExtention())){
				case "jpg":
				case "jpeg":
					$this->img = imagecreatefromjpeg($this->getPath());
					break;
				case "png":
					$this->img = imagecreatefrompng($this->getPath());
					break;
				case "gif";
					$this->img = imagecreatefromgif($this->getPath());
					break;
				case "bmp";
					$this->img = imagecreatefrombmp($this->getPath());
					break;
				case ".txt";
				case "doc";
					$this->img = imagecreatefrompng(realpath(dirname(__FILE__).'/File/Images/thumb_doc.png'));
					break;
				case "xls";
					$this->img = imagecreatefrompng(realpath(dirname(__FILE__).'/File/Images/thumb_excel.png'));
					break;
				default;
					$this->img = imagecreatefrompng(realpath(dirname(__FILE__).'/File/Images/thumb_unknown.png'));
					break;
			}
		}
		return $this->img;
	}
	
	public function getThumbnailDimensions($width, $height, $return = 'both')
	{
		$cacheName = $width.'x'.$height.$return;
		if (isset($this->thumbnailDimensions[$cacheName])) {
			return $this->thumbnailDimensions[$cacheName];
		}
		
		if ($this->getType() == 'images') {
			$imgSize = getimagesize($this->getPath());
			$imgWidth = $imgSize[0];
			$imgHeight = $imgSize[1];
		} else {
			$img = $this->getImage();
			$imgWidth = imagesx($img);
			$imgHeight = imagesy($img);
		}
		if ($width > 0) {
			$thumbWidth = $width;
		} else {
			$thumbWidth = ($imgWidth / $imgHeight) * $height;
		}
		if ($height > 0) {
			$thumbHeight = $height;
		} else {
			$thumbHeight = ($imgHeight / $imgWidth) * $width;
		}
		
		$thumbWidth = floor($thumbWidth);
		$thumbHeight = floor($thumbHeight);
		$returnValue = null;
		if ($return == 'both') {
			$returnValue = array('width' => $thumbWidth, 'height' => $thumbHeight);
		} elseif ($return == 'width') {
			$returnValue = $thumbWidth;
		} elseif ($return == 'height') { 
			$returnValue = $thumbHeight;
		} else {
			throw new Exception($return . ' is not a valid parameter for return type for getThumbnailDimensions in Pata_File');
		}
		
		$this->thumbnailDimensions[$cacheName] = $returnValue;
		return $returnValue;
	}
	
	public function getThumbnailPng($width = 0, $height = 0)
	{
		if (!isset($this->thumbnailPng[$width.'x'.$height])) {
			$img = $this->getImage();
			$imgWidth = imagesx($img);
			$imgHeight = imagesy($img);
			$thumbTop = 0;
			$thumbLeft = 0;
			// If we have specified widths and heights, use them. Otherwise calculate them
			if ($width == 0 && $height == 0) {
				$thumbWidth = $imgWidth;
				$thumbHeight = $imgHeight;
			} else {
				$dimensions = $this->getThumbnailDimensions($width, $height);
				$thumbWidth = $dimensions['width'];
				$thumbHeight = $dimensions['height'];
			}
			// These default to not being changed
			$scaleWidth = $thumbWidth;
			$scaleHeight = $thumbHeight;

			if ($imgWidth > $imgHeight) {
				$scaleHeight = ($imgHeight / $imgWidth) * $thumbWidth;
				$thumbTop = ($thumbHeight - $scaleHeight) / 2;
			} elseif($imgWidth < $imgHeight) {
				$scaleWidth = ($imgWidth / $imgHeight) * $thumbHeight;
				$thumbLeft = ($thumbWidth - $scaleWidth) / 2;
			}
			
			$thumb = ImageCreateTrueColor($thumbWidth, $thumbHeight);
			// Fill the image with transparency
			imagesavealpha($thumb, true);
			$trans_colour = imagecolorallocatealpha($thumb, 0, 0, 0, 127);
			imagefill($thumb, 0, 0, $trans_colour);
	
			imagecopyresampled($thumb, $img, $thumbLeft, $thumbTop, 0, 0, $scaleWidth, $scaleHeight, $imgWidth, $imgHeight);
			imageinterlace($thumb, true);
			$this->thumbnailPng[$width.'x'.$height] = $thumb;
		}
		return $this->thumbnailPng[$width.'x'.$height];
	}
	
	function getType()
	{
		$types = $this->getFileTypeExtentions();
		foreach ($types as $type => $exts) {
			if (in_array(strtolower($this->getExtention()), $exts)) {
				return $type;
			}
		}
		return 'unknown';
	}
	
	function getFileTypeExtentions($type = null)
	{
		$types = array(
			'images'		=> array('jpg','jpeg','gif','png','tiff','bmp'),
			'documents'		=> array('doc', 'docx', 'txt', 'rtf', 'odt', 'pdf'),
			'spreadsheets'	=> array('xls'),
		);
		if ($type != null) {
			if (!isset($types[$type])) {
				throw new Exception('Pata_Gallery->getFileTypeExtentions() does not have a list of extentions for the "'.$type.'" type');
			}
			return $types[$type];
		}
		return $types;
	}
}
Tags:
Category:

Using WYMEditor with Dojo (by including jQuery when needed)

I’ve decided to use Dojo for a project – to test the waters. However I’d like to still use WYMEditor. WYMEditor requires jQuery internally as well as for it’s handy jQuery plugin. Obviously I don’t want to always load jQuery in addition to Dojo for the few pages it’s required on, so here’s the solution I’ve come up with to dynamically load jQuery and any other required files using a DeferredList.

I have this as a function to which I pass a form name; in Dojo I then check whether there are any .richTextEditor elements and begin the initialisation if required.

I guess it would be good to wrap this into a dojo module of some sort.

Versions:

  • Dojo version: 1.7.2
  • jQuery: 1.7.1
  • WYMEditor: 1.0.0a5
require(['dojo/DeferredList', 'dojo/io/script'], function(DeferredList, script) {
	// We don't want to include things twice because it's bad and breaks WYMEditor.
	var requireds = [];
	if (typeof jQuery == "undefined") {
		requireds.push(script.get({ url: '/js/jquery/jquery-1.7.1.min.js', checkString: 'jQuery' }));
	}
	// If you wanted, it would not be a bad idea to check for each plugin individually.
	if (typeof WYMeditor == "undefined") {
		requireds.push(script.get({ url: '/js/jquery/wymeditor/jquery.wymeditor.js', checkString: 'WYMeditor' }));
		requireds.push(script.get({ url: '/js/jquery/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js', checkString: 'WYMeditor.editor.prototype.hovertools' }));
	};

	var dl = new DeferredList(requireds);
	dl.then(function(res){
		jQuery(function(){
			// Fill in your options here
			var editorOptions = {};
			jQuery('.richTextEditor').wymeditor(editorOptions);
			// You might also want to add the wymupdate class to your submit button here
		});
	});
});
Tags:
Categories:

Scan Text for Postcodes and Plot Them on a Map

I’ve often been frustrated when sites list addresses (such as store locations) without providing a map to help you visually see the closest one to you, or the best one to stop by on your way somewhere.

So I dreamed up a tool to parse a web page for postcodes and plot them, this afternoon my brother asked me for one so I finally got around to making one.

It is here: http://www.pata.cat/maps/postcode-picker

Comments, ideas and feedback are welcome. The source is also yours on request – it’s a part of a bigger Zend Framework project and a little finickety to share easily.

Category:

Good ‘n’ Proper Virus Removal

Last Updated: November 2020

People get viruses all the time, I’ve no idea how but they seem to manage it. I spent lots of my time cleaning people’s computers, if you don’t wish to pay someone else to do it you can usually do it yourself using these directions (or get in touch if you’d like me to do it for you). You may get some hard to clean viruses which need research and persistence to remove – in this blog post I can’t help you with those.

Some Tips:

  • You might need to do them one at a time if you find they’re going very slow.
  • Don’t forget to back up your files. Always: virus or no virus. Don’t forget to scan your backup for viruses too (or delete and rebuild it if it’s a read-only one).
  • Each scan here could take between 1 and 8 hours to finish, depending on how much stuff you have on your computer.
  • They are all free, don’t pay for anything! Look for the “Free Version” or “Free Edition”. If you find the programs useful, however, most of them have the option to buy or donate to support them.
  • Where you get the option, ask for a Full Scan. You may need to click an “Advanced” button first.
  • Some will not clean “low-threat” items such as Cookies. Don’t worry about them, but clean anything else they let you.
  • No Anti-Virus picks up everything, that’s why you need to run several to have a really good scrub.
  • If you have an anti virus installed, run a full scan with that first. Then disable it so it doesn’t scan files every time one of the other scans does.

Run these from your browser (or a download file which doesn’t install):

You need to download and install these ones. Remember to uninstall them after to free up your disk space:

Also make sure your current anti virus is up to date and not complaining about anything. If you don’t have one then install:

You may also want to:
  • Run CCleaner – to clean up your caches, registry, broken links and suchlike
  • Defrag with MyDefrag – to tidy up your files so they load faster. Skip this is you have a Solid State Drive (SSD)
  • Use GlaryUtilities to clean up and speed up

If you know any other free online anti viruses, which actually clean up for you at the end, post them in the comments!

Tags:
Categories: