Tuesday, May 8, 2012

Uh Oh - don't stop the Application Information service!

The alternative title for this post is How to successfully move the SoftwareDistribution folder.

Today I had an issue... I could no longer elevate applications to Administrator level. Let me explain how I got into this mess.

I run Windows 7 and my boot drive is only 40GB. I needed to install yet another SDK, and I only had ½ GB or so left on that drive, so the installer immediately barfed and reported a drive space issue. So I go in and delete stuff from the various temp folders, then I run WinDirStat to see what else is taking space. After it has done its thing, a likely space-hogging candidate shows up - the SoftwareDistribution folder under C:\Windows.

Now an easy way to gain space is to move space hogging directories onto another drive, delete the original, and create a symlink in its place (as detailed in this and this blog post by Scott Hanselmann). Of course you need to move files/folders that Windows does not have open or is not constantly using. SoftwareDistribution fits that category - almost. There was one file I couldn't delete because it was held open by the Application Information service. The Application Information service looks innocuous, but you need to be very careful how you deal with it. It cannot be shut down cleanly. You can try to shut it down, but it errors every time you try, until it appears to end up in some kind of twilight state. So because I couldn't shut it down (in order to release its lock on the ReportingEvents.log file), I thought I would do another clever thing: set it's startup mode to disabled, and then reboot.

Of course this works, not a problem. But then I quickly discovered the flaw in my plan. In order to delete the remains of the SoftwareDistribution folder, I need to provide administrative permission, i.e. I have to agree to the UAC prompt. Therein lies the problem - the UAC uses the Application Information service to perform the elevation, but I've shut the service down and prevented it from being started. In fact I have a Catch-22 because I cannot do anything as Administrator, which means I also cannot restart the service or change its startup mode back to what it should be.

Big mistake. Here is where I am going to save you some time if you have the same problem - don't bother Googling the answer, because 100% of the answers I looked at were wrong. They either require you to run an elevated command prompt, or they require you to roll back to the last System Restore point. Remember that we can't elevate, and because I rebooted the last System Restore point is useless to me (I know because I tried it).

So how did I fix it? Quite simply I took advantage of a idiosyncracy in Windows that I didn't know about until now. I rebooted into safe mode, and then changed the Application Information service details from there. This works because UAC is not invoked in safe mode, if your user account is in the local Administrators group then anything you run is running as admin, unlike regular useage where the apps need to be individually elevated. While I was in safe mode I finished deleting the SoftwareDistribution folder and created the symlink, then I rebooted back into normal mode.

So the two critical things to remember if you are going to mess with important services or try and move the SoftwareDistribution folder:

  • make sure your user account is in the local Administrators group 
  • do the work in safe mode, or reboot into safe mode to fix issues 

Of course I could just buy a shiny new drive and reinstall Windows, but do you know how many hours is involved in repaving a development machine? Not to mention that you have way less fun if you do things the boring way!



keywords: application information service, safe mode, softwaredistribution, uac, appinfo

Saturday, July 9, 2011

Taking data binding, validation and MVVM to the next level - part 2

In part 1 of this series, you saw how to:
  • create a Validation rule
  • add that rule to the databinding of your TextBox
  • show a negative validation result in the tooltip of the TextBox

In this session, we are going to extend the validation rule to more completely check the file path entered by the user. If converters are the most useful and awesome additions to databinding, then validation rules have to be the second most useful and awesome. A couple of reasons why they are so awesome are:
  • you can use them declaratively in XAML
  • you can pass extra parameters to the validation rule
  • validation rules encapsulate logic and separate that logic out from your model or view model
  • validation rules are highly testable, unlike validation done via the IDataErrorInfo interface
  • you can specify multiple different validation rules on a binding
  • you have some control over when they are invoked

Okay, on to business. We are going to use two handy static methods on the Path class, GetInvalidPathChars and GetInvalidFileNameChars. We have to use these in the correct order - there are some characters that are legal in a path, but not in a file name, so there is no point testing the file name before the path. Here is some code:

using System;
using System.IO;
using System.Linq;
using System.Windows.Controls;

namespace FilePathValidation1
{
public class FilePathValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null && value.GetType() != typeof(string))
return new ValidationResult(false, "Input value was of the wrong type, expected a string");

var filePath = value as string;

if (string.IsNullOrWhiteSpace(filePath))
return new ValidationResult(false, "The file path cannot be empty or whitespace.");

//check the path:
if (Path.GetInvalidPathChars().Any(x => filePath.Contains(x)))
return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file path.", GetPrinatbleInvalidChars(Path.GetInvalidPathChars())));


return new ValidationResult(true, null);
}

/// <summary>
/// Gets the printable characters from the passed char array.
/// </summary>
/// <param name="chars">The array of characters to check.</param>
/// <returns>Returns a string containing the printable characters.</returns>
private string GetPrinatbleInvalidChars(char[] chars)
{
string invalidChars = string.Join("", chars.Where(x => !Char.IsWhiteSpace(x)));
return invalidChars;
}

}
}


So it is quite simple, we grab the list of invalid characters, then using LINQ check each one until we find the first failure, at which point we return a negative validation result, and include the printable invalid characters in the error message.

Checking the file name itself is quite similar:

      public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null && value.GetType() != typeof(string))
return new ValidationResult(false, "Input value was of the wrong type, expected a string");

var filePath = value as string;

if (string.IsNullOrWhiteSpace(filePath))
return new ValidationResult(false, "The file path cannot be empty or whitespace.");

//check the path:
if (Path.GetInvalidPathChars().Any(x => filePath.Contains(x)))
return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file path.", GetPrinatbleInvalidChars(Path.GetInvalidPathChars())));

//check the filename (if one can be isolated out):
string fileName = Path.GetFileName(filePath);
if (Path.GetInvalidFileNameChars().Any(x => fileName.Contains(x)))
return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file name.", GetPrinatbleInvalidChars(Path.GetInvalidFileNameChars())));


return new ValidationResult(true, null);
}


Because we have already dealt with a possibly null filePath value earlier on in the function, we don't need to worry about the GetFileName() function returning a null, it will return either the file name, or string.Empty.
However.... we have a catch-22 situation here - we need to get the file name so we can check it for invalid characters, but GetFileName() will itself throw an ArgumentException if it encounters an invalid character. So the answer is to wrap that statement in a try...catch:

          //check the filename (if one can be isolated out):
try
{
string fileName = Path.GetFileName(filePath);
if (Path.GetInvalidFileNameChars().Any(x => fileName.Contains(x)))
throw new ArgumentException(string.Format("The characters {0} are not permitted in a file name.", GetPrinatbleInvalidChars(Path.GetInvalidFileNameChars())));
}
catch (ArgumentException e) { return new ValidationResult(false, e.Message); }


Rather than code up two different lines returning a ValidationResult, I have employed the cheap'n'nasty hack of returning it from the catch clause, and throwing my own ArgumentException if necessary to get to it. I wouldn't do this in real code, I'm only doing it here to keep things shorter, and I warned you in the last post that this is example code not coded for prettiness. By doing this I can piggyback upon the exception message returned by the call to GetFileName().

Now one final thing - let's tidy up that empty/null entry condition checking. We are going to add a boolean property to the FilePathValidationRule to indicate whether it is allowable to have a null or empty path, we will add a new check into the rule that uses the new property, and we will set that new property from XAML.

    public class FilePathValidationRule : ValidationRule
{

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null && value.GetType() != typeof(string))
return new ValidationResult(false, "Input value was of the wrong type, expected a string");

var filePath = value as string;

//check for empty/null file path:
if (string.IsNullOrEmpty(filePath))
{
if (!AllowEmptyPath)
return new ValidationResult(false, "The file path may not be empty.");
else
return new ValidationResult(true, null);
}

//null & empty has been handled above, now check for pure whitespace:
if (string.IsNullOrWhiteSpace(filePath))
return new ValidationResult(false, "The file path cannot consist only of whitespace.");

//check the path:
if (Path.GetInvalidPathChars().Any(x => filePath.Contains(x)))
return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file path.", GetPrinatbleInvalidChars(Path.GetInvalidPathChars())));

//check the filename (if one can be isolated out):
try
{
string fileName = Path.GetFileName(filePath);
if (Path.GetInvalidFileNameChars().Any(x => fileName.Contains(x)))
throw new ArgumentException(string.Format("The characters {0} are not permitted in a file name.", GetPrinatbleInvalidChars(Path.GetInvalidFileNameChars())));
}
catch (ArgumentException e) { return new ValidationResult(false, e.Message); }

return new ValidationResult(true, null);
}

/// <summary>
/// Gets and sets a flag indicating whether an empty path forms an error condition or not.
/// </summary>
public bool AllowEmptyPath { get; set; }


private string GetPrinatbleInvalidChars(char[] chars)
{
string invalidChars = string.Join("", chars.Where(x => !Char.IsWhiteSpace(x)));
return invalidChars;
}

}


<Window x:Class="FilePathValidation1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight"

xmlns:this="clr-namespace:FilePathValidation1"
>

<Window.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="20" >
<TextBlock Text="Enter the path to your file" VerticalAlignment="Bottom" />
<TextBox x:Name="FilePathTextBox" Width="350" Margin="5,0,0,0">
<TextBox.Text>
<Binding Path="FilePath" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<this:FilePathValidationRule AllowEmptyPath="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="FileBrowseButton"
Content="..."
Command="{Binding FileBrowseCommand}"
Width="20" Margin="5,0,0,0"
/>
</StackPanel>
</Grid>
</Window>


As you can see, the only change to the XAML was the use of the new AllowEmptyPath property (which you only have to set if you need a value different from its default of false). From the next three images, you'll see that our new rule conditions are working quite nicely:






But.... remember before when I said that there were a lot of edge cases, and the functions built into the .Net framework were not going to be able to do all the work for you? Check this nasty path, which according to our rules is valid:



Tune in for the next post, where I show you how to catch this, and also illustrate a nice edge case regarding path length (which is not always limited to 260 characters! A-ha!).

Thursday, July 7, 2011

Taking data binding, validation and MVVM to the next level - part 1

I've been having fun today, working on something that on the surface seems very simple, but once you delve into it there are a lot of complexities and edge cases hidden just below the surface.

Today boys and girls, let's talk about how to validate a file system path. We are going to do this in a nice MVVM compliant way.

First, let us set the scene; how many times have you coded up a window with a textbox and a simple button which opens the file or folder browse dialog:



The XAML code for this is very simple:
<Window x:Class="FilePathValidation1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="20" >
<TextBlock Text="Enter the path to your file" VerticalAlignment="Bottom" />
<TextBox x:Name="FilePathTextBox" Text="{Binding FilePath}" Width="350" Margin="5,0,0,0" />
<Button x:Name="FileBrowseButton"
Content="..."
Command="{Binding FileBrowseCommand}"
Width="20" Margin="5,0,0,0"
/>
</StackPanel>
</Grid>
</Window>


And the class behind:

using System;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using System.ComponentModel;

namespace FilePathValidation1
{

///
/// Interaction logic for MainWindow.xaml
///

public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindowLoaded);
}

private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
this.DataContext = this;
}

///
/// Gets the command used to browse for a file.
///

public ICommand FileBrowseCommand
{
get
{
if (_fileBrowseCommand == null)
_fileBrowseCommand = new RelayCommand(OpenFileBrowseDialog);
return _fileBrowseCommand;
}
}

///
/// Gets and sets the file path.
///

public string FilePath
{
get { return _filePath; }
set
{
if (!string.Equals(value, _filePath, StringComparison.InvariantCultureIgnoreCase))
{
_filePath = value;
OnPropertyChanged("FilePath");
}
}
}

private void OpenFileBrowseDialog(object context)
{
OpenFileDialog dlg = new OpenFileDialog();
var retVal = dlg.ShowDialog();
if (retVal.HasValue && retVal.Value)
{
FilePath = dlg.FileName;
}
}

///
/// Raises the event.
///

/// The name of the property that changed.
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

///
/// Occurs when a property value changes.
///

public event PropertyChangedEventHandler PropertyChanged;


private ICommand _fileBrowseCommand;
private string _filePath;
}
}


Just remember this code isn't designed to win any awards for being pretty.
So if you have a play with the code above, you'll find that you can either enter a file path directly in the textbox, or you can pop open the file browse dialog and select an existing file. All good, yeah?

But pretty quickly you'll also discover that you need to validate anything the user enters. To do this, we'll take advantage of the ValidationRule class that is already built into the framework, and the ValidationRules property that is built into the WPF binding mechanism.

Let's start with the validation rule:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;

namespace FilePathValidation1
{
public class FilePathValidationRule : ValidationRule
{

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null && value.GetType() != typeof(string))
return new ValidationResult(false, "Input value was of the wrong type, expected a string");

var filePath = value as string;

if (string.IsNullOrWhiteSpace(filePath))
return new ValidationResult(false, "The file path cannot be empty or whitespace.");


return new ValidationResult(true, null);
}
}
}


This validation rule simply extends the System.Windows.Controls.ValidationRule class that is found in the PresentationFramework assembly, we've got just a couple of simple checks in it for now.

Here is how we use it in the XAML:
<Window x:Class="FilePathValidation1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight"

xmlns:this="clr-namespace:FilePathValidation1"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="20" >
<TextBlock Text="Enter the path to your file" VerticalAlignment="Bottom" />
<TextBox x:Name="FilePathTextBox" Width="350" Margin="5,0,0,0">
<TextBox.Text>
<Binding Path="FilePath" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<this:FilePathValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>

</TextBox>
<Button x:Name="FileBrowseButton"
Content="..."
Command="{Binding FileBrowseCommand}"
Width="20" Margin="5,0,0,0"
/>
</StackPanel>
</Grid>
</Window>


I have highlighted the differences in yellow. Notice how we are now using the long form for specifying the binding on the textbox, and we can also specify any number of validation rules to run. These rules are run whenever the user changes what is in the textbox, this is controlled by the UpdateSourceTrigger property on the binding (for example, I could change it so the validation only runs when the user removes the focus from the textbox).

Soooo... all you have to do now to see this in action is run the project, enter some text in the textbox, then delete the text and whammo!! the validation rule will return a validation error, the border of the TextBox will turn red, and with the addition of a handy style on the TextBox the validation message will show in the tooltip:



Pretty nifty, yeah?
Okay, this post is long enough and it lays down the base of what we are going to continue and work with. You need to validate more than just a path consisting of whitespace, you need to also check for forbidden characters and stuff. Go and make yourself a coffee, then come back and read part 2, where I show you why validating a file path can be so darned tricky.

Thursday, December 9, 2010

File search: something I discovered in VS2010

Anyone who uses Visual Studio should be familiar with the Find in Files functionality (shortcut: Ctrl-Shift-F ).



Everyone has used it at some point, usually when you need to search all the files in your solution for a particular string. Those of you more adventurous may even have changed the file filter at the bottom, or searched using a regex. Pat yourself on the back, you are awesome.

Today i found a feature of it that has been there for a few years but i had never seen it before. This feature kicks ass. I used it to search specific folders on the file system that were outside of the solution. How many times have you thought to yourself, "hmmm.... where was that bit of code in that other project that did that certain thing...?", so you break out Windows File Explorer to search your base projects folder, only to be returned a bunch of crap? Well, you don't even have to leave your IDE to do it. Start up Find in Files, and on the Look in combo (that is usually set to Entire Solution or Current Document), click the ellipsis to the right of it:



and you will be presented with a dialog you never knew existed.



Just navigate your file system using the Available folders combo and its up-a-level button, and select whichever folders or drives you want to search from the listbox. Hit OK then Find All and Visual Studio will start searching for you.



Super user tips:
  • Don't forget that you can still use the file filter box, if searching large folder structures you may want to limit what files you are checking.
  • If you want to avoid that browse dialog, you can just enter a semi-colon delimited set of paths straight into the Look in combo
  • You can save a set of folders/paths for later use. In the browse dialog, select your paths, then enter a name for them in the Folder set combo and click Apply. You can then reselect that entry in later searches.

Friday, February 26, 2010

Streamlining property notifications in MVVM

Anyone doing Silverlight or WPF will know that the MVVM pattern is all the rage at the moment. So lots of people out there are now writing ViewModels to bind to their Views, which means they will be writing this sort of thing:
public string MyProperty
{
get { return _myProperty; }
set
{
bool changed = value != _myProperty;
if (changed)
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}


When you have a ViewModel that has anything more than just a few properties, you end up having a LOT of code that while not an exact duplicate, it is doing the exact same thing to different arguments, which may or may not be of the same type. Then you multiply this problem by the number of Views you have, and the process of writing a ViewModel becomes very tedious indeed.

When you have the same code duplicated, you look to see if you can refactor it into a function, and the lines of duplicated code become a call to that new function. And when you are doing the exact same thing to different types, you look to see if you can employ generics.

To solve this issue i came up with the following:
protected void SetProperty(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)
{
bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));
currentValue = newValue;
if (changed)
{
OnPropertyChanged(propertyName);
if (additionalProperties != null)
foreach (string additionalProperty in additionalProperties)
OnPropertyChanged(additionalProperty);
}
}


It's not rocket science, and others have probably come up with the same kind of function, but i love it because it now saves me so much time and eliminates so much repetition. I include this function in to a base class that all my ViewModels inherit from, here it is with a sample on how to use it:
public class ViewModelBase : INotifyPropertyChanged
{

protected void SetProperty(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)
{
bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));
currentValue = newValue;
if (changed)
{
OnPropertyChanged(propertyName);

if (additionalProperties != null)
foreach (string additionalProperty in additionalProperties)
OnPropertyChanged(additionalProperty);
}
}

protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public event PropertyChangedEventHandler PropertyChanged;
}

public class MyRealViewModel : ViewModelBase
{
public int NumberOfItems
{
get { return _numItems; }
set { SetProperty(ref value, ref _numItems, true, "NumberOfItems"); }
}

public bool SomeKindOfFlag
{
get { return _flag; }
set { SetProperty(ref value, ref _flag, false, ""); }
}

public LightSabre WeaponOfChoice
{
get { return _weapon; }
set { SetProperty(ref value, ref _weapon, true, "WeaponOfChoice", "SomeKindOfFlag", "NumberOfItems"); }
}

private bool _flag;
private int _numItems;
private LightSabre _weapon;
}

public class LightSabre
{
public string LightSabreName { get; set; }

public override bool Equals(object obj)
{
if (obj != null && obj as LightSabre != null)
return ((LightSabre)obj).LightSabreName == this.LightSabreName;

return false;
}
}

Monday, January 18, 2010

Wow, so many posts in so few days.... someone should give me a medal.

After a plague of errors when trying to deploy some IIS7 hosted WCF services to a machine, and then using VS2008 from my local machine to create a ServiceReference, i thought i should list some of the issues and what fixed them. Any issues worth mentioning will get their own blog posts, so they will be short and sharp.

The document at the url http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc was not recognized as a known document type.
The error message from each known type may help you fix the problem:
- Report from 'DISCO Document' is 'There was an error downloading 'http://server2008.workflow.local/ProjectServices/WebServices/MyWCFService.svc?disco'.'.
- The request failed with HTTP status 502: Proxy Error ( Host was not found ).
- Report from 'WSDL Document' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
- Report from 'http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
- Report from 'XML Schema' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.
Metadata contains a reference that cannot be resolved: 'http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc'.
Content Type application/soap+xml; charset=utf-8 was not supported by service http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc. The client and service bindings may be mismatched.
The remote server returned an error: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..
If the service is defined in the current solution, try building the solution and adding the service reference again.


It turns out i was missing a mex entry from my service endpoint addresses in my web.config. As soon as i added it:

<services>
  <service behaviorConfiguration="BaseServiceBehavior" name="MyProject.Web.Services.MyWCFService">
    <endpoint binding="basicHttpBinding" bindingConfiguration="MyWCFService" contract="MyProject.Web.Services.IMyWCFService" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
  </service>
</services>


the Add Service Reference started working. W00t.

*note for those who spotted the invalid url: as usual, identities have been changed to protect the innocent.

Tuesday, January 12, 2010

Better file searching in Windows 7

Wow, i just found another cool feature in Windows 7 - a nice way to do file searching.

I had tried to run a SourceSafe history report to find what files i had changed on a specific project, and of course SourceSafe was being its usual wanky horrendous self and the report was producing nothing. So i move to plan B - use a file search on my local hard drive, and order by date modified. Then i noticed that Windows 7 now allows you to search by a date range:





The syntax is

datemodified:<start date> .. <end date>

you can type this directly in to the search box, the date formats are in the UI locale you are running as. You can also use your mousey mouse to select the date range from the dropdown date selector, but personally i find it faster and more accurate to just type it in.

Quick example time: what did i add to my Zune playlist in the last few months of last year? (did you think i was gonna show a pic of all the code i wrote - hell no!!)


Monday, January 11, 2010

Dynamic ControlTemplate in Silverlight

I have a Silverlight 3 application that uses a grid from DevExpress. I needed to extend the standard AgDataGridColumn so that i could show images, those images being representations of an enumeration.

To achieve this is simple enough - just override the ControlTemplate that is assigned to the DisplayTemplate property of the column. Doing this declaratively (in XAML) is pretty damn simple. But i wasn't doing it that way - as i am using a factory pattern for creating the columns from some agnostic descriptors, i needed to locate, load and assign the ControlTemplate programmatically.

The extended grid column sits in a second assembly. As i had created several templated controls in this controls assembly, the project templates had already created a ResourceDictionary called generic.xaml in the Themes folder, so i intended to use this file to define the ControlTemplate and then just load it in code, using a line of code similar to


ControlTemplate ct = Application.Current.Resources["MyImageColumnTemplate"] as ControlTemplate;

Unfortunately, this didn't work, Application.Current.Resources had no idea about my ControlTemplate. Then i realised that because it was defined in a ResourceDictionary, i needed to load that ResourceDictionary and merge it with the resources associated with the current application. To do that is pretty simple:

ResourceDictionary dict = new ResourceDictionary();
dict.Source = new Uri("path to resource dictionary", UriKind.Absolute);

Application.Current.Resources.MergedDictionaries.Add(dict);


But of course nothing is that simple, right? I found half a dozen blog or forum postings that indicated all i would have to use for my URI was this:

Uri uri = new Uri("pack://application:,,,/MySilverlightControls;component/themes/generic.xaml", UriKind.Absolute);

The scheme is correct, the assembly name is correct, the path is correct, what could go wrong? Well, first i received an error about there being no port number specified:


UriFormatException was unhandled by user code

Invalid URI: A port was expected because of there is a colon (':') present but the port could not be parsed.

A little bit more googlebinging showed me that i probably needed to register the pack scheme – i thought it would already be registered (especially as the Loaded event for several Silverlight components had been fired) but it wasn't. OK, another half step forward:

if (!UriParser.IsKnownScheme("pack"))

UriParser.Register(newGenericUriParser(GenericUriParserOptions.GenericAuthority), "pack", -1);


Once i dropped that bit of code in an appropriate place, that error went away. But then i started to be plagued with vague COM errors depending on how i tried to declare the URIs.


dict.Source = new Uri("/themes/generic.xaml", UriKind.Relative);


dict.Source = new Uri("./../../themes/generic.xaml", UriKind.Relative);


dict.Source = new Uri("../../themes/generic.xaml", UriKind.Relative);

all produced the error


Error HRESULT E_FAIL has been returned from a call to a COM component.


at MS.Internal.XcpImports.CheckHResult(UInt32 hr)


at MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj, DependencyProperty property, String s)


at etc, etc...



dict.Source = new Uri("pack://application:,,,/themes/generic.xaml", UriKind.Absolute);


dict.Source = new Uri("pack://application:,,,/MySilverlightControls;component/themes/generic.xaml", UriKind.Absolute);

both produced the error


Exception from HRESULT: 0x80072EE5

which effectively means "Invalid URI".

So i did what any engineer or scientist should do – remove every variable from the equation until you are left with just the single problematic item, and then try and isolate the problem. I created a new ResourceDictionary
(called Dictionary1.xaml) under the Themes folder, put the MyImageColumnTemplate XAML in there, and went back through my seven different ways of specifying the URI for the ResourceDictionary. Suddenly i had success! This ended up being the code that worked:

XAML:


<ControlTemplate x:Name="MyImageColumnTemplate" >

<Grid MaxHeight="20" MaxWidth="20">

<Grid.Resources>

<localGrid:EnumColumnImageConverter x:Key="ImageContentConverter"/>

</Grid.Resources>

<Image Source="{Binding EditValue, Converter={StaticResource ImageContentConverter}}" />

</Grid>

</ControlTemplate>


C#:

Uri uri = new Uri("/MySilverlightControls;component/themes/Dictionary1.xaml", UriKind.Relative);

ResourceDictionary dict = new ResourceDictionary();

dict.Source = uri;

Application.Current.Resources.MergedDictionaries.Add(dict);

ControlTemplate ct = (ControlTemplate)Application.Current.Resources["MyImageColumnTemplate"];

this.DisplayTemplate = ct;

That URI also worked when i pointed it at generic.xaml. All the other attempted URIs look correct, but none of them would work. Now i had a custom image column that used my custom ControlTemplate, which in turn used my Converter to translate an enumeration into the appropriate image (that image was also extracted from a resource file.... but i had no issue with that!).


Wow, this post was a bit long but i tried my best to keep it simple, and i hope it helps someone out there.






Wednesday, September 23, 2009

Rogue msiexec processes after installing VS2010

I need to give big props to Mebyon Kernow for his blog post here with the answer to this problem.

After installing VS2010 on my HP Mini, i noticed the cpu usage constantly sitting at 50+%. Upon looking at thr process manager, i saw that there were two rogue msiexec processes working hard and chewing up cpu cycles. As the HP Mini is a netbook, constant work on the cpu chews up battery life. A quick google* turned up the aforementioned blog post. Ten minutes after adding the appropriate folder, the msiexec instances finished their business and disappeared.

The answer in Mebyon's post was quite simple, and finding it meant i didn't have to think too hard for myself.

* I actually used Bing, but saying "a quick bing" doesn't have the same zhoosh as saying "a quick google".




Keywords: VS2010, rogue process, msiexec

Monday, August 3, 2009

SourceSafe history report - from the command line

Sometimes it is really useful to be able to do a history report on your SourceSafe archive. For instance, what check-ins did the developer called X make between 1 June 2009 and 30 June 2009?

Surprisingly it can be tough to search out the exact info you need to make this work effectively. Having this sort of reporting is also really helpful when putting together release notes, especially if developers use the 'comment' feature when checking items in, and mention specific bug cases (you are using a bug tracking product, aren't you?).


Here is how you do it. Open up a command prompt. Then you need to set an environmental variable called SSDIR, this is so the following commands know what repository we will be working with. To do this type the path to the folder containing the srcsafe.ini file of the repository:

C:>set SSDIR=c:\Program Files\Sourcesafe\

Note the trailing slash, and note that the filename itself is not included. Then you need to navigate to the folder where sourcesafe is installed:

C:>cd C:\Program Files\Microsoft Visual SourceSafe

Then we use ss.exe to generate the history report. This particular command gives me all the files that were checked in between 0900 on the 1st July and 0900 on the 30th July.

C:\Program Files\Microsoft Visual SourceSafe>ss history "$/Projects/My Project" -Oc:\history.txt -R -vd30/07/09;09:00a~01/07/09;09:00a

Breaking down the command line arguments:


















Command optionWhat it means
history ss.exe can be used for many things - we are telling it to do a history report.
"$/Projects/My Project"Path to the project i want reported on in the SourceSafe repository pointed to by SSDIR.
-Oc:\blah.txt-O means output, and then i specify the file i want the data outputed to.
-RThis is the recursive flag, IOW it means do all projects (folders) under the project specified as the start point.
-vdThis is the bit that limits the date. In my case the dates are in real english format (dd/mm/yy), not US format. The later date is listed first. The tilde (~) indicates that it is a range. The time is included with the date by separating it with a semi-colon, and the AM/PM is indicated by using either 'a' or 'p'.


This gives me a nice little text file called history.txt that i can scan through (or programmatically parse), it looks a little like this:

**********************
Label: "1.0.0.635"
User: Builder Date: 31/07/09 Time: 5:02p
Labeled
Label comment: Automated Build of Version 1.0.0.635

***** AssemblyInfo.cs *****
Version 29
User: Builder Date: 31/07/09 Time: 5:02p
Checked in $/Projects/My Project/Properties
Comment: Automated Build of Version 1.0.0.635

***** GridView.cs *****
Version 41
User: Shane Date: 16/07/09 Time: 4:27p
Checked in $/Projects/My Project/Controls/GridView
Comment: case 12345, changed how a column was rendered







Keywords: sourcesafe, history report, command line

Saturday, July 4, 2009

Activating Office 2007 on Windows 7 RC

I was trying to activate Office 2007 running on Windows 7 RC, but i kept getting an error message saying that there was an error communicating with the server, please try again in a few minutes. Selecting the option to activate by phone instead caused the activation dialog to disappear, it would not give me a product code or allow me to enter in an activation code.

The solution to this was simple - i needed to run the Office product (whichever one i was using to do the activation, Outlook in my case) as admin. I am in the admin group, but that is not sufficient (because even when you are logged in as administrator, processes you spawn still run at a reduced privilege level) - you need to run the process as administrator. There is an extra step involved when trying to do this with an Office product, as the installed shortcuts don't give you the Run as administrator option when you right click on them.


  • determine where the Office product is installed to*, i.e. it will usually be C:\Program Files (x86)\Microsoft Office\Office12
  • find the exe of one of the products, so you want to find WINWORD.EXE, EXCEL.EXE, OUTLOOK.EXE or MSACCESS.EXE, right click on the file, select Run as administrator
  • click Yes on the UAC prompt, or select the Administrator user and enter the admin password if you get that prompt
  • if you are running Word/Excel/Access, click on the launch orb in the top left corner, select Word Options/Excel Options/Access Options, in the Options popup dialog select Resources, then select activate Microsoft Office
  • if you are running Outlook from the step above, select Help->Activate Product
  • in the activation dialog, select the activate via internet option, click Next
  • Office should now activate. If it doesn't then you may have a firewall, proxy, or general connection problem.


*note that because the Office products have a special sort of shortcut installed, you cannot just right click on the short cut and go Properties->General to find the path to the executable, as the path shown in that tab will be the path to the shortcut file itself, not the product executable



Keywords: Office 2007 activation, activation error, Windows 7

Saturday, April 11, 2009

Goodbye RSS Bandit

I've been using RSS Bandit as my RSS feed aggregator and reader for a couple of years now, but today was the day i finally got annoyed enough to ditch it and switch to a new feed aggregator. I chose FeedBurner.

What annoyed me the most about Bandit?
- it kept losing blog posts. Whenever the app started, the latest blog posts were from 10th Dec 2008 (i'm not sure what happened on that date or what is special about it). If i right clicked on the feed and selected Update, the missing posts from the last 4 months would appear again, along with any new posts. This only affected some feeds, there was no pattern to which ones (the feeds affected had anywhere from 10 to 1000+ posts in them), but it was the same feeds each time. This smells like a data store issue to me.

- the delete functionality was incredibly slow. If the deletes folder had maybe a couple of hundred posts in it, and i deleted one post from another feed, then the speed was ok. But if i select ten posts and delete them then it takes ages. And it gets worse the more posts you have sitting in the deletes folder. Taking 30sec to delete 10 posts is IMHO suboptimal.

- it wouldn't shut down correctly. If i started it up and then closed it down in the next hour or so, then it terminated properly. But if i left it running overnight and then shut it down the next morning, the main process would continue to run. Sometimes an app will do this when it is doing a bit of shutdown processing, like maybe tidying up its data store and rebuilding indexes, so i gave Bandit the benefit of the doubt at the start and just let that process run. In fact at one stage i let it go several days, and the process just kept on running in the background. This means it is buggy, and the trouble is that in cases like this if you terminate the process forceably, you risk corruption of its data store if it was in the middle of doing something when you terminated. This might actually be what caused problem #1 above.

-it was also an enormous memory hog. When running Bandit under XP or Vista i would get regular out of memory errors (a termination because of this could also cause problem #1). I should mention that i've got 4GB RAM, and i was running a 64bit version of Vista. I'm now running a 64 bit version of Windows7, and the memory consumption is a lot more stable. While some might blame the OS for the memory management problems, i blame Bandit, as it must have been doing something in a way that triggered the memory issue (when i shut down Bandit the memory got freed up again). They were probably loading the entire database into memory, which would not be particularly efficient on most desktop machines.

Now, i am a software engineer, so i could have just grabbed the source and debugged and fixed the problem instead of whining about it, but i really can't be bothered for a number of reasons. First, i am too busy already. And i hate debugging other people's shit, it can be incredibly tedious. Once i found the problem, it might be a quick fix, or it could be a major rewrite depending on how the app is written. And in any case, there is no guarantee that they would accept my fix, as is their right. It is just simpler to find and install a new aggregator.

So i installed FeedDemon, and in my first 5mins with it i had already started to like it more than RSS Bandit. My only complaint is that when i went to import my RSS Bandit feeds/posts, it threw an error saying i needed to reinstall Bandit, and didn't give me any more details than that. Once again it was probably an issue with Bandit, not FeedDemon. But apart from that little glitch i am happy - FeedDemon is massively faster, it deletes fast, uses considerably less memory, and it shuts down when i tell it to shut down :)

To anyone considering installing RSS Bandit: don't, give it another evolution or two before you try it. Bandit is not very scalable, and doesn't handle feeds with large numbers of posts very well (i have around 130 feeds containing approx 23100 posts). The data store (database) is slow and possibly inefficient. Some improvements need to be made; i need to be able to crunch the database and rebuild indexes, and i need to be able to specify where it should cache its feed data without having to alter the config file directly. Filtering functionality would also be super, so i could automatically delete unwanted posts on high volume feeds.

I might revisit Bandit it in a year and see what progress has been made. Until then, c'est la vie.




Keywords: rss bandit, losing posts, feeddemon, rss

Thursday, February 5, 2009

I have been developing a Silverlight application, and i deployed it to a demo server. When i went to test it, the demo gods immediately kicked into gear and i received a script error message instead of seeing my control:

Error: Sys.InvalidOperationException: InitializeError error #2104 in control '[insert my control id here]': Could not download the Silverlight application. Check web server settings

So of course i googled the error message. It turns out this one is very easy to solve, but there is a bit of random rubbish and partial answers floating around out there. It turns out that IIS would not serve the control to the browser because i had not set up the correct MIME types in IIS, so IIS had no idea what the browser was requesting.

The best solution i found was this blog post: http://web.iotap.com/Blogs/tabid/277/EntryId/65/Configuring-Silverlight-2-0-Application-in-IIS.aspx

You need to ensure that the website hosting the Silverlight control has the following MIME types registered:


ExtensionMIME type
.applicationapplication/x-ms-application
.deployapplication/octet-stream
.manifestapplication/manifest
.xamlapplication/xaml+xml
.xapapplication/x-silverlight-app
.xbapapplication/x-ms-xbap
.xpsapplication/vnd.ms-xpsdocument






Realistically you probably only need the xaml and xap entries, but i entered them all and the demo gods smiled once again. Once you have done that, Ctrl-F5 your web page (or just F5 for some other browsers), and you should see your Silverlight control appear.

As per that blog article, i also enabled the content expiration, but i'm not sure of the relevance of that in this particular case (maybe it stops the Silverlight control from being cached?).




keywords: silverlight 2, deploy, error 2104, silverlight mime types

Wednesday, September 10, 2008

Chrome and its processes

Wow, i gotta say "Thanks" to Scott Hanselman (who i must say is an excellent technical speaker, i met him briefly at TechEd NZ, not that he would remember me). Almost exactly a week ago i questioned all the processes being started by Google Chrome (here), and in an excellent post he has answered that exact question. You can find his post here

The problem with his answer is that it produces a lot more work for me - now that i know a little bit i have to go and research and become familiar with that design pattern. The possibilities could be interesting, maybe this would be a cool way to allow third parties to integrate into your application and give them a very limited access to your data or authentication services (i'm presuming that Chrome and IE8 totally isolate their addons).




Keywords: google, chrome, IE8, processes

Thursday, September 4, 2008

Chrome, 24 hours after installation

Okay, i need to correct a statement i made about Chrome in a previous blog post. If i'm going to criticise products then it is only fair that i be as accurate as possible.

Yesterday i stated "it must offer me some way to configure its options.....so there is no excuse for not having an options/settings UI". This could possibly be interpreted to mean that Chrome has no configuration options at all, when it does in fact have some. It kind of has some very basic tab configuration, you can clear the stored cookies and history, and there are some basic security settings, but not a lot else.


But wait, there's more!! In the last 24 hours i have found a few more things to have a spew about:

 - my machine is 64bit, running a 64 bit operating system. So why did the Chrome installer decide to install the 32 bit (x86) version of the application? Is it because some lazy slack ass developers have not compiled an x64 version? There must be something like 10 billion 64 bit machines out in the wild by now, so why is there no 64 bit version of Chrome?

 - if i go to the options and try to change the proxy settings.... WAIT ONE COTTON-PICKING MOMENT!!!!! Changing those proxy settings is also going to change the proxy settings for IE, and any other application that uses a plugin browser component, such as Windows Explorer, my RSS reader, Outlook, etc. WTF???!!!! Why doesn't Chrome use it's own set of proxy settings, instead of relying on the system ones? Firefox and Opera both have their own settings, why couldn't Chrome?

 - i can clear the browser history, but i can't alter any other history settings, like how long to keep it. I don't want my history retained. When i type Ctrl-T to open a new tab, i don't want a bunch of "you recently visited these sites" links. I want that new tab to be empty, with the focus already set to the address bar so that i can start typing. This is how real men browse, only weenies and Mac fanboi's want a bunch of recently visited links automatically populated onto their new tab.

 - it is OPEN SOURCE!!!! OMG!!! GASP!!! Yawn. Like the world hasn't already had umpteen open source browsers*, like a very famous one called, ummm, "Mozilla Firefox". Exactly what was the point of making it open source? Is it because that is the trendy thing to do at the moment? Or was it done that way so that Google could put their hand on their heart and swear on their mother's grave that "honestly, we are not trying to take over the world and 0wn all your base, and track everything you type in the address bar and every site you visit and send lots of secret tracking data to our great database in the sky, honestly, just check our source code"? Yeah right. Unlike a lot of morons out there, i do not trust or respect a product any more simply because it is open source. What is the point in releasing YAOSB? (Yet Another Open Source Browser).

I know that Chrome is in beta (and probably will be for the next three decades), but the only feature that has impressed me so far is the rendering speed. The rest of the browser has been somewhat underwhelming.


*Don't believe me? Check this link, scroll about halfway down, look at the table immediately underneath the General Information heading. Remember, those are just the browsers that made it big. Now check Sourceforge, there are about one zillion OSS web browsers under development there. I think that is enough to prove the point, we don't need to go check the various other publicly open source repositories.



Keywords: google, chrome, open source software

Wednesday, September 3, 2008

Google Chrome

Application being named and shamed: Google Chrome

I downloaded Chrome today to have a play with it, and before i start i must mention that it is a beta, but i think the shortcomings i am going to critique are important. I installed this onto a machine running Vista Ultimate, so your experience may differ. I am typing this blog post in Chrome, not that it really matters to what i am going to say :)

First problem: It's called "Chrome". This is also the term used to describe the title bar and menu bar area etc of the application window. This screenshot is the chrome area of IE:



It might be a cool sounding name and easy to market, but wtf using a piece of terminolgy that is in common use amongst Microsoft folks? Is this a sly shot at the company you are in close competition with?

Second: When you download Chrome, you are downloading a stub installer that when run goes back to the net without asking or notifying me and retrieves the rest of the install package. This may make life easier when you roll out the beta, because if any issues occur you can just fix the main install and you don't have to replace the stub which people have already downloaded. But it is bad because the installer has not told me what version of the product i am installing, and the version that i install on another machine tomorrow can be different from the version i installed today. The installer needs to be fully open and informative about what it is doing and exactly what it is installing. Where did the install package come from? Why do i not get a chance to virus scan it before installation?

Third: Chrome automatically installed itself into a user specific area on my machine. At no stage did i get asked for an install location. This is incredibly bad - this is my machine, and i dictate where things go on it (as it happens, i don't install *any* applications to my C: drive, they all go onto a drive reserved specifically for applications). 

Fourth: This is also bad because Chrome's data gets stored in the same location, and Chrome provides no way of changing that. This means that anything that is downloaded gets cached in this location. For me this is unacceptable - all the browsers i use (IE, Firefox and Opera) are set to store their cache items on a totally different drive. This means i can quickly and easily check, search or delete the cached content without starting the applications or navigating deeply into a folder structure. It means i can easily set ACLs (permissions) on folders and files, all in the one place. It also means that temporary files are not needlessly filling up my boot drive!!!!

Fifth: WTF is with all the processes being started by Chrome? This was with two tabs opened:



Sixth: if i right-click in the chrome area of Chrome (that was funny!!!!!!!), i get a context menu, and one of the options is "Task Manager":






too late Google - that name is already taken, it is the little system utility that i start up to check on system resources or to kill errant processes. But you know that already, so why choose that name for your dialog? Call it something else, maybe "Application Tasks", or "Tab Manager".

I have nothing against Google, and to be honest there are some things i also like about Chrome (but i'll blog about those once i've had more time to play). But to make simple and fundamental mistakes like they have is unacceptable, and if i was an IT administrator there would be no way i would allow this browser on my corporate network. Just because you are Google you don't have exemption from the rules or conventions.

When an application installs on my machine, it must:
1: if it fetches extra components then it must tell me what they are and where they are coming from - it is my machine and i have the right to know.
2: it must give me an option of where to install it to. If they want to restrict my choices that is fine, but i should have the right to cancel the install if i don't like the options available to me.
3: it must offer me some way to configure its options, like where the cached items are to be stored, and the tab behaviour i want, and whether i want to allow script to execute in pages, etc. A lack of configurability is fine in alpha software, but not beta. Labelling your software as "beta" means you are on the home stretch towards "going gold", not just starting the race**, so there is no excuse for not having an options/settings UI.


* i know that installing an application into %APPDATA% could solve a couple of issues, namely the application should have no issue directly accessing its data files/folders because Vista's file virtualisation shouldn't be triggered, but that's not the point - this area is for data, not the application. Or maybe Vista restricted the install to installing there as it was downloading (installing) from an untrusted location. At the very least the application should be installed into %PROGRAMFILES%. Better still it should give me an option, defaulting to %PROGRAMFILES%.

** i have to mention that this is something that Google seems to have redefined with their other applications, like Gmail. It is labelled as "beta" as soon as it is opened to the public, and is still called "beta" several (4? 5?) years later. Will this happen with Chrome as well?


Keywords: google, chrome, sub optimal, bad install