Search
Recent Tweets

Entries in diagnostics (3)

Friday
Oct112013

A Remotely Managed Bing Image Search Wallpaper App - Part 2

Part 1 gave an overview introduction to the app and covered job scheduling, searching for images on Bing, and downloading images. Part 2 covers setting the wallpaper, remote app settings, remote logging and more.

Setting the Wallpaper

The switch wallpaper job is scheduled to start running later than the download job to give time for images to download. Plus it makes for a better prank to wait for the wallpaper change to set in later. The job starts by checking the app's enabled status and bailing if disabled. It then deletes old images according to app settings and the create date of the local images. Next if AppSettings.Instance.WallpaperOverrideUrl is set that image is downloaded and used as the background. Otherwise the job randomly picks one of the images downloaded from the Bing image search results.

The actual code that sets the wallpaper isn't that interesting; it's a modified version from this StackOverflow post. Finally the job uses the metadata manager to get the image URL where the local filename came from; that's used in the remote logging so we can see what image we set the wallpaper to.
internal class SwitchWallpaperJob : IJob
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();

	public const string OutputPathKey = "OutputPath";

	public async void Execute(IJobExecutionContext context)
	{
		try
		{
			if (!AppSettings.Instance.CheckStatus()) return;

			var path = AppSettings.ImagePath.FullName;
			Ensure.That(path, "path").IsNotNullOrWhiteSpace();

			ImageCleanup.Execute();

			var imageFile = await GetWallpaperImageFile(path);
			if (null == imageFile) return;

			//TODO: setting for wallpaper style or smart set via desktop & image size
			Logger.Troll("Changing wallpaper to {0}", imageFile.Name);
			Wallpaper.Set(imageFile.FullName, Wallpaper.Style.Centered);

			var meta = new MetadataManager().Get(imageFile.FullName);
			var changedTo = (null != meta) ? meta.RemoteLocation + " via term " 
				+ meta.Term : imageFile.Name;

			Logger.Troll("Changed wallpaper to {0}", changedTo);
		}
		catch (Exception ex)
		{
			Logger.Error(ex.ToString());
			throw new JobExecutionException(ex);
		}
	}

	private static async Task<FileInfo> GetWallpaperImageFile(string path)
	{
		if (!string.IsNullOrWhiteSpace(AppSettings.Instance.WallpaperOverrideUrl))
		{
			Logger.Troll("Using image override url {0}", 
				AppSettings.Instance.WallpaperOverrideUrl);
			var uri = new Uri(AppSettings.Instance.WallpaperOverrideUrl);
			var file = uri.Segments[uri.Segments.Length - 1];
			var ext = file.Substring(file.LastIndexOf(".") + 1);
			var outFilename = new FileInfo(Path.Combine(
				AppSettings.ImagePath.FullName, "Override." + ext));
			await ImageDownloader.DownloadImage(AppSettings.Instance.WallpaperOverrideUrl, 
				outFilename.FullName);
			return outFilename;
		}

		Logger.Troll("Enumerating files in {0}", path);
		var files = Directory.GetFiles(path, "*.jpg").ToList();

		if (!files.Any())
		{
			Logger.Troll("No images yet; will try again later");
			return null;
		}

		Logger.Troll("Found {0} wallpaper images in {1}", files.Count, path);
		var r = new Random();
		var randFile = new FileInfo(files[r.Next(0, files.Count)]);
		return randFile;
	}
}

Remote App Settings

The only app.config setting the app uses is ConfigSource which is expected to be a URL to retrieve the settings from. In this way we can change the wallpaper app behavior remotely which is useful when you're pranking someone or if you're using yourself to synchronize wallpaper settings between multiple computers. DropBox, SkyDrive, Google Drive and the like are simple solutions for hosting the file though that might trace the app back to you.

The config data is in JSON format and a sample follows. For pranking purposes I might use "Justin Bieber", "Justin Bieber Wallpaper", "Justin Bieber 2013" etc. in search terms but for testing or personal use, not so much. For debugging I set the all the job intervals to short amounts but use much longer durations for deployment.


Trying It Out

Images are downloaded to AppData\Local\WIO; WIO is the app name and stands for Windows Image Optimization :) and keeps the process name short and esoteric.



Also in this directory is the metadata index file, mapping image filenames to the source URL downloaded from, in BSON format with some base64 encoding

Fetching App Settings

When retrieving the settings, a simple check is done on the data returned from the HTTP call to see if it is JSON. If not, the code assumes it is encrypted. When using the app in a prank fashion, encrypting the data helps mask app activity if the target discovers the URL in the app config or notices the HTTP traffic. Of course encrypting the config makes changing settings more difficult, so the app also contains a FileEncrypt command line app to easily encrypt the data. Adding a Send To shortcut means only having to right-click a plain text JSON file to generate an encrypted version in the same directory.
	public enum AppStatus { Enabled, Paused, Disabled }

    public sealed class AppSettings
    {
        private static volatile AppSettings _instance;
        private static readonly object SyncRoot = new Object();

        private AppSettings()
        {
            this.Search = new SearchSettings();
            this.Job = new JobSettings();
            this.Log = new LogSettings();
        }

        public string ImageDeleteAfterTimespan { get; set; }
        public string WallpaperOverrideUrl { get; set; }

        [JsonConverter(typeof(StringEnumConverter))]
        public AppStatus Status { get; set; }

        public static AppSettings Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        _instance = new AppSettings();
                    }
                }
                return _instance;
            }
        }

        public static async Task<AppSettings> Load()
        {
            string configData;
            using (var client = new HttpClient())
            {
                configData = await client.GetStringAsync(
					ConfigurationManager.AppSettings["ConfigSource"]);

                var isJsonPlain = (configData.TrimStart().StartsWith("{"));
                if (!isJsonPlain)
                    configData = CryptoManager.Decrypt3DES(configData);
            }

            lock (SyncRoot)
                _instance = JsonConvert.DeserializeObject<AppSettings>(configData);

            return _instance;
        }

        public bool CheckStatus()
        {
            if (Status == AppStatus.Disabled)
            {
                Application.Exit();
                return false;
            }

            return Status == AppStatus.Enabled;
        }

        [JsonIgnore]
        public static DirectoryInfo ImagePath
        {
            get
            {
                var path = Path.Combine(Environment.GetFolderPath(
					Environment.SpecialFolder.LocalApplicationData), "WIO");
                var di = new DirectoryInfo(path);
                if (!di.Exists) di.Create();
                return di;
            }
        }

        public SearchSettings Search { get; set; }
        public JobSettings Job { get; set; }
        public LogSettings Log { get; set; }
    }

Obfuscating the Code

With punking a fellow IT coworker, I wanted to obfuscate the code so it'd be more difficult to figure out the app's logic should it be discovered and viewed in a disassembler. For that I used eazfuscator.

It was free to use for 30 days which was all I needed and it's available via NuGet. It slowed the build down some but it's only done in Release mode and usually that's only done at the very end.



Unfortunately when running in Release mode with the obfuscated code I received the below exception. Removing the obfuscation build step and running in Release mode removed the exception so that means the process changed the behavior of the code.
Quartz.SchedulerException was unhandled by user code
  HResult=-2146233088
  Message=Repeat Interval cannot be zero.
  Source=Quartz
  StackTrace:
       at Quartz.Impl.Triggers.SimpleTriggerImpl.Validate() in c:\Work\OpenSource\quartznet\src\Quartz\Impl\Triggers\SimpleTriggerImpl.cs:line 727
       at Quartz.Core.QuartzScheduler.ScheduleJob(IJobDetail jobDetail, ITrigger trigger) in c:\Work\OpenSource\quartznet\src\Quartz\Core\QuartzScheduler.cs:line 720
       at Quartz.Impl.StdScheduler.ScheduleJob(IJobDetail jobDetail, ITrigger trigger) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\StdScheduler.cs:line 262
       at   .       ()
       at   . (Type  )
       at System.Collections.Generic.List`1.ForEach(Action`1 action)
       at   . ()
       at  . (Task`1  )
       at System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
  InnerException:
This turned out to be an issue with the obfuscator not handling JSON serialization correctly. It was fixed in a later version of the tool but the latest version of the NuGet package was an older version. Any use of reflection can also be an issue so lesson learned - always fully regression test when using obfuscation. This tool now appears to be more commercialized but I applaud the quality and the after hours support I received when reporting an issue.

Quick Installation

To reduce the chance of getting caught, I created a simple install script that can be quickly run via a flash drive or from a network share. Sooner or later I knew my target would forget a workstation lock and this way even a quick bathroom or coffee trip was plenty of time.

First a post build event to copy the files needed for deployment to an Install\bin folder:

if not exist "$(SolutionDir)Install\bin" mkdir "$(SolutionDir)Install\bin"
del /f /q $(SolutionDir)Install\bin\*.*
xcopy /r /d /i /s /y /exclude:$(SolutionDir)Install\InstallStageExclude.txt
  $(TargetDir)*.* $(SolutionDir)Install\bin

Next the script would copy the contents to the appropriate location. Normally I'd use PowerShell but batch files are just faster with being able to double-click and run worry-free.



Remote Logging

Logging was needed to monitor the app to see what wallpaper was set on the victim's computer or to view problems if things weren't working correctly. Local computer logging didn't do any good as I wouldn't have access to the data later, so I evaluated a couple of cloud based logging solutions.

Loggr.net

Loggr.net is what I started with. It was intuitive and easy to use. Where it broke down for my needs was the API limits of the free account - 100 log records per day. So I ended up changing the code to only send important log events there based on log type (error, warning, custom). The code to send logging data wasn't bad, though it ended up being more than typical for more control over the logged events.


Loggly

Next I tried Loggly which allowed up to 200 MB/day for free. I found it's website UI to be a bit counter-intuitive though they were upgrading to a GEN 2 platform near the end of my usage. I liked the JSON logging and the command line style website. It offered a lot of searching functionality and from an API perspective it was easy to work with the log data programmatically. The code to send logging data was dead simple, though I was doing more with the Loggr.net API.



In Conclusion

Was it worth it?

Yes:
  • It's a fun prank that keeps on giving for you.
  • I have an automated, randomized source of wallpaper for myself, synced between computers.
  • Most importantly I got to play with different tech, learn new things and do some coding for fun.

Disclaimers

This was a fun educational experiment and one-time prank. I only offer the source code here; no binaries, setups, support, or API keys.

Should you use some or all of this or do something similar, keep in mind:
  • Depending on your use and legalese interpretation, you may be violating Bing's terms of use.
  • Web search is like a box of chocolates; you never know what you're gonna get.
  • Used in prank fashion, you may run the risk of offending someone with a random image, on top of already annoying him/her.
  • Used at work you may slow down someone's machine or the work network with downloading hundreds of high resolution images in a short time span.
Wednesday
Jan252012

Quick NLog Tip: Getting the Log Filename

I have found NLog to be very easy to use but one thing that was not clear was how to retrieve a log's filename. Our unhandled exception process packages up different files into a zip file for support and I wanted to resolve the log filename to include it in the package.

The below code is one way to retrieve the log filename:

using NLog;
using NLog.Layouts;
using NLog.Targets;

namespace MyApp.Diagnostics
{
    public static class LogManagement
    {
        private const string DefaultTargetName = "allTarget";

        public static string GetTargetFilename(string targetName)
        {
            var target = GetTarget<FileTarget>(targetName);
            if (null == target) return null;

            var layout = target.FileName as SimpleLayout;
            if (null == layout) return null;
            
            // layout.Text provides the filename "template"
            // LogEventInfo is required; might make sense for a log line template but not really filename
            var filename = layout.Render(new LogEventInfo()).Replace(@"/", @"\");
            return filename;
        }

        public static string GetTargetFilename()
        {
            return GetTargetFilename(DefaultTargetName);
        }

        private static T GetTarget<T>(string targetName) 
            where T: Target
        {
            if (null == LogManager.Configuration) return null;
            var target = LogManager.Configuration.FindTargetByName(targetName) as T;
            return target;
        }
    }
}
First the code resolves a given target by name. My first thought was that Target.Filename would be a string of the filename but it is a generic Layout object. In my case I'm using a SimpleLayout and I cast that just for inspection purposes but it is not strictly needed in this sample. From there the code calls the Render method of the layout to transform the "template filename" renderers (variables such as ${processid}) into the final result with the rendered values.

Unfortunately Render requires a LogEventInfo object which might make more sense if we were rendering the template for a given line of a log file but not so much for the filename. Passing null generates an object reference not set exception but passing in a new LogEventInfo object worked and still allowed proper variable substitution; at least it did in my case but it might depend on what renderers you use in your filename configuration. Finally the code replaces any forward slashes in the filename with backslashes.

If you use a different type of target or layout the code would need adjusting. NLog's LogManager is sealed so you cannot inherit from it but you can wrap it or change the code to be use extension methods though that might increase the direct dependency on NLog.

So given the below NLog.config, layout.Text is "${environment:LOCALAPPDATA}/MyApp/logs/MyApp-${processid}.log.txt" and that renders to something along the lines of "C:\Users\MyUsername\AppData\Local\MyApp\logs\MyApp-9176.log.txt"

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <!-- make sure to set 'Copy To Output Directory' option for this file -->
  <!-- go to http://nlog-project.org/wiki/Configuration_file for more information -->

  <targets async="false">
    <target name="allTarget" xsi:type="File" deleteOldFileOnStartup="true" archiveEvery="Day" maxArchiveFiles="1"
            fileName="${environment:LOCALAPPDATA}/MyApp/logs/MyApp-${processid}.log.txt"
            layout="${time}|${threadid}|${level:uppercase=true}|${logger}|${message}"
            autoFlush="true" />
    <target name="debugTarget" xsi:type="Debugger" layout="${time}|${level:uppercase=true}|${logger}|${message}"   />
  </targets>

  <rules>
    <!-- levels: Debug, Error, Fatal, Info, Off, Trace, Warn -->
    <logger name="*" minLevel="Debug" writeTo="debugTarget,allTarget" />
  </rules>
</nlog>
Tuesday
Nov152011

Ninject and NLog with Multiple Loggers

Today I wanted to add some simple file logging to my ASP.NET MVC application and I thought it would be a good time to try out NLog. I used log4net in the past but it has become stale and NLog appeared more modern and capable. I liked what I saw with NLog but I wanted to abstract it away behind DI / IOC so:
  • it would not get in the way of testing
  • it would not clutter my code with a dependency on a specific logging framework everywhere
  • the interface could be changed if needed
I wanted to still maintain some of the power and flexibility of NLog though, namely the use of multiple loggers determined by context and configuration.

Setup

NLog

I installed NLog via their Nuget package though I also ended up installing the MSI from their download page for the extras such as the item templates, XSD schema, etc. though I believe there are separate NuGet packages for some of that as well.

Ninject

I am a big fan of Ninject's DI/IOC framework. There are two primary ways of setting up Ninject for ASP.NET MVC3. Originally I went with NuGet but I had issues with that approach and went with the "older" NinjectHttpApplication route. In hindsight I think another issue caused the problems I had there and I may revert back to that approach.

Logging interface

First a simple logging interface. For the time being I just pulled out what I considered the most common signatures from NLog's Logger class. It is easy enough to add other members later if/when needed. I did not see any reason to change these signatures currently and mirroring Logger makes implementation easier.
namespace App.Core.Infrastructure.Diagnostics
{
    public interface ILog
    {
        void Debug(string format, params object[] args);
        void Error(string format, params object[] args);
        void Fatal(string format, params object[] args);
        void Info(string format, params object[] args);
        void Trace(string format, params object[] args);
        void Warn(string format, params object[] args);
		
		bool IsDebugEnabled { get; }
        bool IsErrorEnabled { get; }
        bool IsFatalEnabled { get; }
        bool IsInfoEnabled { get; }
        bool IsTraceEnabled { get; }
        bool IsWarnEnabled { get; }
    }
}

Implementing ILog

By nature of inheriting from NLog's Logger the ILog interface is satisfied since it is mirrored. At one point I started some custom functionality but ended up not needing it though I am likely to add something here before long.
using NLog;

namespace App.Core.Infrastructure.Diagnostics
{
    public class Log : Logger, ILog
    {
        // something useful to come here I'm sure :)
    }
}

DI / IOC Container Registration

Next up the ILog binding is created in the appropriate module. The binding is to a method which gets the full type name of the type being injected into. That class name is then passed into NLog's LogManager.GetLogger method to get the appropriate logger based on the context.
using NLog;
using Ninject.Modules;
using App.Core.Data.Diagnostics;

namespace App.Core.Infrastructure.DI
{
	internal class WebAppModule : NinjectModule
	{
		public override void Load()
		{
			Bind<ILog>().ToMethod(x =>
			{
				var scope = x.Request.ParentRequest.Service.FullName;
				var log = (ILog)LogManager.GetLogger(scope, typeof(Log));
				return log;
			});
			/* other code omitted for brevity */
		}
	}
}

Sample Usage (Data Access)

In a DatabaseMonitor class an ILog instance will be constructor-injected for later logging of Entity Framework database activity to a log file. I removed implementation of IDatabaseMonitor and DevArt's OracleMonitor use for brevity.
using App.Core.Data.Interfaces;
using App.Core.Infrastructure.Diagnostics;
using Devart.Common;
using Devart.Data.Oracle;
using JetBrains.Annotations;

namespace App.Core.Data.Diagnostics
{
	[UsedImplicitly]
    internal class DatabaseMonitor : IDatabaseMonitor
    {
        private readonly ILog _log;

        public DatabaseMonitor(ILog log)
        {
            _log = log;
        }

        private void OracleMon_TraceEvent(object sender, MonitorEventArgs e)
        {
            _log.Debug(e.Description);
        }

        /* other code omitted for brevity */
    }
}

Sample Usage (Controller)

Besides the data access classes the controllers are the other major logical area for logging. This is a trivial example as logging the results of controller action processing is the real value but again I cut that for brevity.
using App.Core.Data.Interfaces;
using App.Core.Infrastructure;
using App.Core.Infrastructure.DI;
using MvcContrib.Filters;

namespace App.Core.UI.Controllers
{
    [PassParametersDuringRedirect]
    public partial class ContractSearchController : ControllerBase
    {
        private readonly IRepository _repository;
        private readonly ILog _log;

        [UsedImplicitly]
        [DepInject(Comment = "Required due to T4MVC partial ctor")]
        public ContractSearchController(IRepository repository, ILog log) 
        {
            EnsureThat.ArgumentIsNotNull(repository, "repository");
            EnsureThat.ArgumentIsNotNull(log, "log");
            _repository = repository;
            _log = log;
            _log.Debug("Contract search controller instantiated");
        }
		
		/* other code omitted for brevity */
	}
}

NLog.config

NLog can be configured in different ways including web.config but I like the default approach of the separate NLog.config. Here I define sources for a database log file, UI log file, a combined (all) log file and Debug output. Then loggers are configured with a name pattern that will match the namespaces used above to associate logging to the appropriate logger which is associated to one or more targets.
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <!-- make sure to set 'Copy To Output Directory' option for this file -->
  <!-- go to http://nlog-project.org/wiki/Configuration_file for more information -->

  <targets async="true">
    <target name="dbFileTarget" xsi:type="File" 
		fileName="${basedir}/logs/log-database.txt" deleteOldFileOnStartup="true" 
        layout="${time}|${level:uppercase=true}|${message}" 
		archiveEvery="Day" maxArchiveFiles="2"  />
    <target name="uiFileTarget" xsi:type="File" 
		fileName="${basedir}/logs/log-ui.txt" deleteOldFileOnStartup="true"
        layout="${time}|${level:uppercase=true}|${message}" 
		archiveEvery="Day" maxArchiveFiles="2"  />
    <target name="allTarget" xsi:type="File" 
		fileName="${basedir}/logs/log-all.txt" deleteOldFileOnStartup="true" 
		layout="${time}|${level:uppercase=true}|${logger}|${message}" 
		archiveEvery="Day" maxArchiveFiles="2"/>
    <target name="debugTarget" xsi:type="Debugger" 
		layout="${time}|${level:uppercase=true}|${logger}|${message}"   />
  </targets>

  <rules>
    <logger name="*" minlevel="Debug" writeTo="debugTarget,allTarget" />
    <logger name="App.Core.Data.*" minlevel="Debug" writeTo="dbFileTarget"/>
    <logger name="App.Core.UI.*" minlevel="Debug" writeTo="uiFileTarget"/>
  </rules>
</nlog>

Output

That is pretty much it; it just works. In the below example the all file is slightly larger than the sum of the individual log files since the combined log file's layout includes the name of the logger whereas that is implied and therefore omitted in the more specific log files.


Final Thoughts

This is the result of my first day evaluating NLog so I expect to change it as things progress. So far though I am impressed with the flexibility and ease of use with NLog and I am content with it now that it is playing well with Ninject. After doing this first pass I seemed to remember seeing some Ninject logging extensions in the past and sure enough there is a Ninject.Extensions.Logging.nLog NuGet package and that may be worth looking at as well.