tag:blogger.com,1999:blog-4960841067241850732024-03-14T02:23:08.661+13:00TechFilthshanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.comBlogger19125tag:blogger.com,1999:blog-496084106724185073.post-42243154652445091562012-05-08T00:24:00.001+12:002012-05-08T00:25:24.676+12:00Uh Oh - don't stop the Application Information service!The alternative title for this post is <i><span style="font-family: "Trebuchet MS",sans-serif;">How to successfully move the SoftwareDistribution folder</span></i>.<br />
<br />
Today I had an issue... I could no longer elevate applications to Administrator level.
Let me explain how I got into this mess.<br />
<br />
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 <a href="http://windirstat.info/" target="_blank">WinDirStat</a> 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.<br />
<br />
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 <a href="http://www.hanselman.com/blog/GuideToFreeingUpDiskSpaceUnderWindows7.aspx" target="_blank">this</a> and <a href="http://www.hanselman.com/blog/MoreOnVistaReparsePoints.aspx" target="_blank">this</a> 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 <i>try </i>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 <i><span style="font-family: inherit;">ReportingEvents.log</span></i> file), I thought I would do another clever thing: set it's startup mode to disabled, and then reboot.<br />
<br />
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.<br />
<br />
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).<br />
<br />
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.<br />
<br />
So the two critical things to remember if you are going to mess with important services or try and move the SoftwareDistribution folder:<br />
<br />
<ul>
<li>make sure your user account is in the local Administrators group </li>
<li>do the work in safe mode, or reboot into safe mode to fix issues </li>
</ul>
<br />
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!<br />
<br />
<br />
<br />
<span style="color: #f3f3f3; font-size: xx-small;">keywords: application information service, safe mode, softwaredistribution, uac, appinfo </span>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com9tag:blogger.com,1999:blog-496084106724185073.post-21343375583033422462011-07-09T13:00:00.013+12:002011-07-10T00:29:54.400+12:00Taking data binding, validation and MVVM to the next level - part 2In <a href="http://techfilth.blogspot.com/2011/07/taking-databinding-validation-and-mvvm.html">part 1</a> of this series, you saw how to:<br /><ul><li>create a Validation rule</li><li>add that rule to the databinding of your TextBox</li><li>show a negative validation result in the tooltip of the TextBox</li></ul><br />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:<br /><ul><li>you can use them declaratively in XAML</li><li>you can pass extra parameters to the validation rule</li><li>validation rules encapsulate logic and separate that logic out from your model or view model</li><li>validation rules are <span style="font-weight: bold;">highly testable</span>, unlike validation done via the <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.idataerrorinfo.aspx">IDataErrorInfo</a> interface</li><li>you can specify multiple different validation rules on a binding</li><li>you have some control over when they are invoked</li></ul><br />Okay, on to business. We are going to use two handy static methods on the <a href="http://msdn.microsoft.com/en-us/library/3bdzys9w.aspx">Path</a> class, <a href="http://msdn.microsoft.com/en-us/library/system.io.path.getinvalidpathchars.aspx">GetInvalidPathChars </a>and <a href="http://msdn.microsoft.com/en-us/library/system.io.path.getinvalidfilenamechars.aspx">GetInvalidFileNameChars</a>. 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:<br /><br /><pre class="brush: csharp">using System;<br />using System.IO;<br />using System.Linq;<br />using System.Windows.Controls;<br /><br />namespace FilePathValidation1<br />{<br /> public class FilePathValidationRule : ValidationRule<br /> {<br /> public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)<br /> {<br /> if (value != null && value.GetType() != typeof(string))<br /> return new ValidationResult(false, "Input value was of the wrong type, expected a string");<br /><br /> var filePath = value as string;<br /><br /> if (string.IsNullOrWhiteSpace(filePath))<br /> return new ValidationResult(false, "The file path cannot be empty or whitespace.");<br /><br /> //check the path:<br /> if (Path.GetInvalidPathChars().Any(x => filePath.Contains(x)))<br /> return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file path.", GetPrinatbleInvalidChars(Path.GetInvalidPathChars())));<br /><br /><br /> return new ValidationResult(true, null);<br /> }<br /><br /> /// <summary><br /> /// Gets the printable characters from the passed char array.<br /> /// </summary><br /> /// <param name="chars">The array of characters to check.</param><br /> /// <returns>Returns a string containing the printable characters.</returns><br /> private string GetPrinatbleInvalidChars(char[] chars)<br /> {<br /> string invalidChars = string.Join("", chars.Where(x => !Char.IsWhiteSpace(x)));<br /> return invalidChars;<br /> }<br /><br /> }<br />}</pre><br /><br />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.<br /><br />Checking the file name itself is quite similar:<br /><br /><pre class="brush: csharp"> public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)<br /> {<br /> if (value != null && value.GetType() != typeof(string))<br /> return new ValidationResult(false, "Input value was of the wrong type, expected a string");<br /><br /> var filePath = value as string;<br /><br /> if (string.IsNullOrWhiteSpace(filePath))<br /> return new ValidationResult(false, "The file path cannot be empty or whitespace.");<br /><br /> //check the path:<br /> if (Path.GetInvalidPathChars().Any(x => filePath.Contains(x)))<br /> return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file path.", GetPrinatbleInvalidChars(Path.GetInvalidPathChars())));<br /><br /> //check the filename (if one can be isolated out):<br /> string fileName = Path.GetFileName(filePath);<br /> if (Path.GetInvalidFileNameChars().Any(x => fileName.Contains(x)))<br /> return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file name.", GetPrinatbleInvalidChars(Path.GetInvalidFileNameChars())));<br /><br /><br /> return new ValidationResult(true, null);<br /> }<br /></pre><br /><br />Because we have already dealt with a possibly null <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >filePath</span> value earlier on in the function, we don't need to worry about the <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >GetFileName()</span> function returning a null, it will return either the file name, or <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >string.Empty</span>.<br />However.... we have a catch-22 situation here - we need to get the file name so we can check it for invalid characters, but <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >GetFileName()</span> will itself throw an <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ArgumentException</span> if it encounters an invalid character. So the answer is to wrap that statement in a try...catch:<br /><br /><pre class="brush: csharp"> //check the filename (if one can be isolated out):<br /> try<br /> {<br /> string fileName = Path.GetFileName(filePath);<br /> if (Path.GetInvalidFileNameChars().Any(x => fileName.Contains(x)))<br /> throw new ArgumentException(string.Format("The characters {0} are not permitted in a file name.", GetPrinatbleInvalidChars(Path.GetInvalidFileNameChars())));<br /> }<br /> catch (ArgumentException e) { return new ValidationResult(false, e.Message); }<br /></pre><br /><br />Rather than code up two different lines returning a <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ValidationResult</span>, I have employed the cheap'n'nasty hack of returning it from the catch clause, and throwing my own <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ArgumentException</span> if necessary to get to it. <span style="font-size:-1;font-style: italic;">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.</span> By doing this I can piggyback upon the exception message returned by the call to <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;">GetFileName()</span>.<br /><br />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.<br /><br /><pre class="brush: csharp"> public class FilePathValidationRule : ValidationRule<br /> {<br /><br /> public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)<br /> {<br /> if (value != null && value.GetType() != typeof(string))<br /> return new ValidationResult(false, "Input value was of the wrong type, expected a string");<br /><br /> var filePath = value as string;<br /><br /> //check for empty/null file path:<br /> if (string.IsNullOrEmpty(filePath))<br /> {<br /> if (!AllowEmptyPath)<br /> return new ValidationResult(false, "The file path may not be empty.");<br /> else<br /> return new ValidationResult(true, null);<br /> }<br /><br /> //null & empty has been handled above, now check for pure whitespace:<br /> if (string.IsNullOrWhiteSpace(filePath))<br /> return new ValidationResult(false, "The file path cannot consist only of whitespace.");<br /><br /> //check the path:<br /> if (Path.GetInvalidPathChars().Any(x => filePath.Contains(x)))<br /> return new ValidationResult(false, string.Format("The characters {0} are not permitted in a file path.", GetPrinatbleInvalidChars(Path.GetInvalidPathChars())));<br /><br /> //check the filename (if one can be isolated out):<br /> try<br /> {<br /> string fileName = Path.GetFileName(filePath);<br /> if (Path.GetInvalidFileNameChars().Any(x => fileName.Contains(x)))<br /> throw new ArgumentException(string.Format("The characters {0} are not permitted in a file name.", GetPrinatbleInvalidChars(Path.GetInvalidFileNameChars())));<br /> }<br /> catch (ArgumentException e) { return new ValidationResult(false, e.Message); }<br /><br /> return new ValidationResult(true, null);<br /> }<br /><br /> /// <summary><br /> /// Gets and sets a flag indicating whether an empty path forms an error condition or not.<br /> /// </summary><br /> public bool AllowEmptyPath { get; set; }<br /><br /><br /> private string GetPrinatbleInvalidChars(char[] chars)<br /> {<br /> string invalidChars = string.Join("", chars.Where(x => !Char.IsWhiteSpace(x)));<br /> return invalidChars;<br /> }<br /><br /> }</pre><br /><br /><pre class="brush: xml"><Window x:Class="FilePathValidation1.MainWindow"<br /> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br /> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br /> Title="MainWindow"<br /> SizeToContent="WidthAndHeight"<br /> <br /> xmlns:this="clr-namespace:FilePathValidation1"<br /> ><br /> <br /> <Window.Resources><br /> <Style TargetType="TextBox"><br /> <Style.Triggers><br /> <Trigger Property="Validation.HasError" Value="true"><br /> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/><br /> </Trigger><br /> </Style.Triggers><br /> </Style><br /> </Window.Resources><br /> <br /> <Grid><br /> <Grid.RowDefinitions><br /> <RowDefinition Height="Auto" /><br /> </Grid.RowDefinitions><br /> <StackPanel Orientation="Horizontal" Margin="20" ><br /> <TextBlock Text="Enter the path to your file" VerticalAlignment="Bottom" /><br /> <TextBox x:Name="FilePathTextBox" Width="350" Margin="5,0,0,0"><br /> <TextBox.Text><br /> <Binding Path="FilePath" UpdateSourceTrigger="PropertyChanged" ><br /> <Binding.ValidationRules><br /> <this:FilePathValidationRule AllowEmptyPath="True" /><br /> </Binding.ValidationRules><br /> </Binding><br /> </TextBox.Text><br /> </TextBox><br /> <Button x:Name="FileBrowseButton" <br /> Content="..." <br /> Command="{Binding FileBrowseCommand}" <br /> Width="20" Margin="5,0,0,0" <br /> /><br /> </StackPanel><br /> </Grid><br /></Window></pre><br /><br />As you can see, the only change to the XAML was the use of the new <controltitle>AllowEmptyPath</controltitle> property (which you only have to set if you need a value different from its default of <span style="font-style:italic;">false</span>). From the next three images, you'll see that our new rule conditions are working quite nicely:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-iPd4pSOfA2Q/Thg-1h_fdVI/AAAAAAAAAEI/ht_g1gSOU0E/s1600/filepathvalidation2-01.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 74px;" src="http://4.bp.blogspot.com/-iPd4pSOfA2Q/Thg-1h_fdVI/AAAAAAAAAEI/ht_g1gSOU0E/s400/filepathvalidation2-01.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627316823778096466" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-qEUVMCCDnaA/Thg_wLDlkXI/AAAAAAAAAEQ/O9NSEV3APcM/s1600/filepathvalidation2-02.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 74px;" src="http://4.bp.blogspot.com/-qEUVMCCDnaA/Thg_wLDlkXI/AAAAAAAAAEQ/O9NSEV3APcM/s400/filepathvalidation2-02.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627317831233540466" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-f67VWqKc_bo/Thg_7_BvfgI/AAAAAAAAAEY/7I2aQVUhdtM/s1600/filepathvalidation2-03.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 73px;" src="http://1.bp.blogspot.com/-f67VWqKc_bo/Thg_7_BvfgI/AAAAAAAAAEY/7I2aQVUhdtM/s400/filepathvalidation2-03.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627318034163006978" /></a><br /><br /><br />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:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-kuzxGvBxDyk/ThhADkHKi6I/AAAAAAAAAEg/BXn68f37W6I/s1600/filepathvalidation2-04.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 77px;" src="http://3.bp.blogspot.com/-kuzxGvBxDyk/ThhADkHKi6I/AAAAAAAAAEg/BXn68f37W6I/s400/filepathvalidation2-04.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627318164376947618" /></a><br /><br />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!).shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-60329691443143211602011-07-07T20:40:00.024+12:002011-07-10T00:28:27.165+12:00Taking data binding, validation and MVVM to the next level - part 1I'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.<br /><br />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.<br /><br />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:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-fqUPB7GVIF8/ThejeOkOi7I/AAAAAAAAAD4/RYNSdOJ-U24/s1600/filepathvalidation1-01.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 81px;" src="http://4.bp.blogspot.com/-fqUPB7GVIF8/ThejeOkOi7I/AAAAAAAAAD4/RYNSdOJ-U24/s400/filepathvalidation1-01.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627145999124171698" /></a><br /><br />The XAML code for this is very simple:<br /><pre class="brush: xml"><Window x:Class="FilePathValidation1.MainWindow"<br /> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br /> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br /> Title="MainWindow"<br /> SizeToContent="WidthAndHeight"<br /> ><br /> <Grid><br /> <Grid.RowDefinitions><br /> <RowDefinition Height="Auto" /><br /> </Grid.RowDefinitions><br /> <StackPanel Orientation="Horizontal" Margin="20" ><br /> <TextBlock Text="Enter the path to your file" VerticalAlignment="Bottom" /><br /> <TextBox x:Name="FilePathTextBox" Text="{Binding FilePath}" Width="350" Margin="5,0,0,0" /><br /> <Button x:Name="FileBrowseButton"<br /> Content="..."<br /> Command="{Binding FileBrowseCommand}"<br /> Width="20" Margin="5,0,0,0"<br /> /><br /> </StackPanel><br /> </Grid><br /></Window></pre><br /><br />And the class behind:<br /><br /><pre class="brush: csharp">using System;<br />using System.Windows;<br />using System.Windows.Input;<br />using Microsoft.Win32;<br />using System.ComponentModel;<br /><br />namespace FilePathValidation1<br />{<br /><br /> /// <summary><br /> /// Interaction logic for MainWindow.xaml<br /> /// </summary><br /> public partial class MainWindow : Window, INotifyPropertyChanged<br /> {<br /> public MainWindow()<br /> {<br /> InitializeComponent();<br /> this.Loaded += new RoutedEventHandler(MainWindowLoaded);<br /> }<br /><br /> private void MainWindowLoaded(object sender, RoutedEventArgs e)<br /> {<br /> this.DataContext = this;<br /> }<br /><br /> /// <summary><br /> /// Gets the command used to browse for a file.<br /> /// </summary><br /> public ICommand FileBrowseCommand<br /> {<br /> get<br /> {<br /> if (_fileBrowseCommand == null)<br /> _fileBrowseCommand = new RelayCommand(OpenFileBrowseDialog);<br /> return _fileBrowseCommand;<br /> }<br /> }<br /><br /> /// <summary><br /> /// Gets and sets the file path.<br /> /// </summary><br /> public string FilePath<br /> {<br /> get { return _filePath; }<br /> set<br /> {<br /> if (!string.Equals(value, _filePath, StringComparison.InvariantCultureIgnoreCase))<br /> {<br /> _filePath = value;<br /> OnPropertyChanged("FilePath");<br /> }<br /> }<br /> }<br /><br /> private void OpenFileBrowseDialog(object context)<br /> {<br /> OpenFileDialog dlg = new OpenFileDialog();<br /> var retVal = dlg.ShowDialog();<br /> if (retVal.HasValue && retVal.Value)<br /> {<br /> FilePath = dlg.FileName;<br /> }<br /> }<br /><br /> /// <summary><br /> /// Raises the <see cref="E:PropertyChanged"> event.<br /> /// </see></summary><br /> /// <param name="propertyName">The name of the property that changed.<br /> private void OnPropertyChanged(string propertyName)<br /> {<br /> if (PropertyChanged != null)<br /> PropertyChanged(this, new PropertyChangedEventArgs(propertyName));<br /> }<br /><br /> /// <summary><br /> /// Occurs when a property value changes.<br /> /// </summary><br /> public event PropertyChangedEventHandler PropertyChanged;<br /><br /><br /> private ICommand _fileBrowseCommand;<br /> private string _filePath;<br /> }<br />}<br /></pre><br /><br />Just remember this code isn't designed to win any awards for being pretty.<br />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?<br /><br />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 <a href="http://msdn.microsoft.com/en-us/library/ms617871.aspx">ValidationRule</a> class that is already built into the framework, and the ValidationRules property that is built into the WPF binding mechanism.<br /><br />Let's start with the validation rule:<br /><br /><pre class="brush: csharp">using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Text;<br />using System.Windows.Controls;<br /><br />namespace FilePathValidation1<br />{<br /> public class FilePathValidationRule : ValidationRule<br /> {<br /><br /> public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)<br /> {<br /> if (value != null && value.GetType() != typeof(string))<br /> return new ValidationResult(false, "Input value was of the wrong type, expected a string");<br /><br /> var filePath = value as string;<br /><br /> if (string.IsNullOrWhiteSpace(filePath))<br /> return new ValidationResult(false, "The file path cannot be empty or whitespace.");<br /><br /><br /> return new ValidationResult(true, null);<br /> }<br /> }<br />}<br /></pre><br /><br />This validation rule simply extends the <a href="http://msdn.microsoft.com/en-us/library/ms617871.aspx">System.Windows.Controls.ValidationRule</a> class that is found in the PresentationFramework assembly, we've got just a couple of simple checks in it for now.<br /><br />Here is how we use it in the XAML:<br /><pre><Window x:Class="FilePathValidation1.MainWindow"<br /> xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br /> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br /> Title="MainWindow"<br /> SizeToContent="WidthAndHeight"<br /> <br /> <span style="background-color:yellow;">xmlns:this="clr-namespace:FilePathValidation1"</span><br /> ><br /> <Grid><br /> <Grid.RowDefinitions><br /> <RowDefinition Height="Auto" /><br /> </Grid.RowDefinitions><br /> <StackPanel Orientation="Horizontal" Margin="20" ><br /> <TextBlock Text="Enter the path to your file" VerticalAlignment="Bottom" /><br /> <TextBox x:Name="FilePathTextBox" Width="350" Margin="5,0,0,0"><br /><span style="background-color:yellow;"> <TextBox.Text><br /> <Binding Path="FilePath" UpdateSourceTrigger="PropertyChanged" ><br /> <Binding.ValidationRules><br /> <this:FilePathValidationRule /><br /> </Binding.ValidationRules><br /> </Binding><br /> </TextBox.Text></span><br /> </TextBox><br /> <Button x:Name="FileBrowseButton"<br /> Content="..."<br /> Command="{Binding FileBrowseCommand}"<br /> Width="20" Margin="5,0,0,0"<br /> /><br /> </StackPanel><br /> </Grid><br /></Window><br /></pre><br /><br />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).<br /><br />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 <span style="font-style:italic;">whammo!!</span> 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:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-4oWvf-A59vk/ThelTXtZZEI/AAAAAAAAAEA/wzGYNxDCdW4/s1600/filepathvalidation1-02.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 80px;" src="http://4.bp.blogspot.com/-4oWvf-A59vk/ThelTXtZZEI/AAAAAAAAAEA/wzGYNxDCdW4/s400/filepathvalidation1-02.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5627148011623244866" /></a><br /><br />Pretty nifty, yeah?<br />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 <a href="http://techfilth.blogspot.com/2011/07/taking-data-binding-validation-and-mvvm.html">part 2</a>, where I show you why validating a file path can be so darned tricky.shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-61948449234799936782010-12-09T09:17:00.022+13:002010-12-09T23:14:09.357+13:00File search: something I discovered in VS2010Anyone who uses Visual Studio should be familiar with the <controltitle>Find in Files</controltitle> functionality (shortcut: <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>F</kbd> ).<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Qp19D1g5eY0/TQBP5jEs-nI/AAAAAAAAADc/ilq5tgH9XYY/s1600/vs%2Bsearch%2B1.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 65px;" src="http://1.bp.blogspot.com/_Qp19D1g5eY0/TQBP5jEs-nI/AAAAAAAAADc/ilq5tgH9XYY/s400/vs%2Bsearch%2B1.png" alt="" id="BLOGGER_PHOTO_ID_5548522591006554738" border="0" /></a><br /><br />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.<br /><br />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 <span style="font-style: italic;">outside</span> 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 <controltitle>Find in Files</controltitle>, and on the <controltitle>Look in</controltitle> combo (that is usually set to <span style="font-style: italic;">Entire Solution</span> or <span style="font-style: italic;">Current Document</span>), click the ellipsis to the right of it:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Qp19D1g5eY0/TQBPmqDbzzI/AAAAAAAAADM/a-2JCNMIf4I/s1600/vs%2Bsearch%2B2.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 366px;" src="http://4.bp.blogspot.com/_Qp19D1g5eY0/TQBPmqDbzzI/AAAAAAAAADM/a-2JCNMIf4I/s400/vs%2Bsearch%2B2.png" alt="" id="BLOGGER_PHOTO_ID_5548522266462768946" border="0" /></a><br /><br />and you will be presented with a dialog you never knew existed.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Qp19D1g5eY0/TQBPzF64HkI/AAAAAAAAADU/bZ0MuuGREbY/s1600/vs%2Bsearch%2B3.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 309px;" src="http://3.bp.blogspot.com/_Qp19D1g5eY0/TQBPzF64HkI/AAAAAAAAADU/bZ0MuuGREbY/s400/vs%2Bsearch%2B3.png" alt="" id="BLOGGER_PHOTO_ID_5548522480101498434" border="0" /></a><br /><br />Just navigate your file system using the <controltitle>Available folders</controltitle> combo and its up-a-level button, and select whichever folders or drives you want to search from the listbox. Hit <btnText>OK</btnText> then <btnText>Find All</btnText> and Visual Studio will start searching for you.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Qp19D1g5eY0/TQBP_71FqZI/AAAAAAAAADk/5o0MAq1FMEc/s1600/vs%2Bsearch%2B4.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 278px;" src="http://3.bp.blogspot.com/_Qp19D1g5eY0/TQBP_71FqZI/AAAAAAAAADk/5o0MAq1FMEc/s400/vs%2Bsearch%2B4.png" alt="" id="BLOGGER_PHOTO_ID_5548522700731165074" border="0" /></a><br /><br />Super user tips:<br /><ul><li>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. </li><li>If you want to avoid that browse dialog, you can just enter a semi-colon delimited set of paths straight into the <controltitle>Look in</controltitle> combo</li><li>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 <controltitle>Folder set</controltitle> combo and click <btnText>Apply</btnText>. You can then reselect that entry in later searches.</li></ul>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-89679978339643710652010-02-26T13:12:00.010+13:002010-03-01T14:24:53.393+13:00Streamlining property notifications in MVVMAnyone 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:<br /><pre class="brush: csharp">public string MyProperty<br />{<br /> get { return _myProperty; }<br /> set<br /> {<br /> bool changed = value != _myProperty;<br /> if (changed)<br /> {<br /> _myProperty = value;<br /> OnPropertyChanged("MyProperty");<br /> }<br /> }<br />}<br /></pre><br /><br />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.<br /><br />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.<br /><br />To solve this issue i came up with the following:<br /><pre class="brush: csharp">protected void SetProperty<t>(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)<br />{<br /> bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));<br /> currentValue = newValue;<br /> if (changed)<br /> {<br /> OnPropertyChanged(propertyName);<br /> if (additionalProperties != null)<br /> foreach (string additionalProperty in additionalProperties)<br /> OnPropertyChanged(additionalProperty);<br /> }<br />}<br /></pre><br /><br />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:<br /><pre class="brush: csharp">public class ViewModelBase : INotifyPropertyChanged<br />{<br /> <br /> protected void SetProperty<T>(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)<br /> {<br /> bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));<br /> currentValue = newValue;<br /> if (changed)<br /> {<br /> OnPropertyChanged(propertyName);<br /><br /> if (additionalProperties != null)<br /> foreach (string additionalProperty in additionalProperties)<br /> OnPropertyChanged(additionalProperty);<br /> }<br /> }<br /><br /> protected virtual void OnPropertyChanged(string propertyName)<br /> {<br /> if (this.PropertyChanged != null)<br /> this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));<br /> }<br /><br /> public event PropertyChangedEventHandler PropertyChanged;<br />}<br /><br />public class MyRealViewModel : ViewModelBase<br />{<br /> public int NumberOfItems<br /> {<br /> get { return _numItems; }<br /> set { SetProperty(ref value, ref _numItems, true, "NumberOfItems"); }<br /> }<br /><br /> public bool SomeKindOfFlag<br /> {<br /> get { return _flag; }<br /> set { SetProperty(ref value, ref _flag, false, ""); }<br /> }<br /><br /> public LightSabre WeaponOfChoice<br /> {<br /> get { return _weapon; }<br /> set { SetProperty(ref value, ref _weapon, true, "WeaponOfChoice", "SomeKindOfFlag", "NumberOfItems"); }<br /> }<br /><br /> private bool _flag;<br /> private int _numItems;<br /> private LightSabre _weapon;<br />}<br /><br />public class LightSabre<br />{<br /> public string LightSabreName { get; set; }<br /><br /> public override bool Equals(object obj)<br /> {<br /> if (obj != null && obj as LightSabre != null)<br /> return ((LightSabre)obj).LightSabreName == this.LightSabreName;<br /><br /> return false;<br /> }<br />}<br /></pre>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com1tag:blogger.com,1999:blog-496084106724185073.post-9559425848069247362010-01-18T15:27:00.002+13:002010-01-18T15:54:38.003+13:00Wow, so many posts in so few days.... someone should give me a medal.<br /><br />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.<br /><br /><blockquote style="font-family: courier new;"><span style="font-size:85%;">The document at the url http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc was not recognized as a known document type.<br />The error message from each known type may help you fix the problem:<br />- Report from 'DISCO Document' is 'There was an error downloading 'http://server2008.workflow.local/ProjectServices/WebServices/MyWCFService.svc?disco'.'.<br /> - The request failed with HTTP status 502: Proxy Error ( Host was not found ).<br />- Report from 'WSDL Document' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.<br />- 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').'.<br />- Report from 'XML Schema' is 'The document format is not recognized (the content type is 'text/html; charset=UTF-8').'.<br />Metadata contains a reference that cannot be resolved: 'http://192.168.0.999/ProjectServices/WebServices/MyWCFService.svc'.<br />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.<br /><span style="background-color:yellow;">The remote server returned an error: (415)</span> Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..<br />If the service is defined in the current solution, try building the solution and adding the service reference again.</span></blockquote><br /><br />It turns out i was missing a mex entry from my service endpoint addresses in my web.config. As soon as i added it:<br /><blockquote style="font-family: courier new;"><span style="font-size:65%;"><br /><services><br /> <service behaviorConfiguration="BaseServiceBehavior" name="MyProject.Web.Services.MyWCFService"><br /> <endpoint binding="basicHttpBinding" bindingConfiguration="MyWCFService" contract="MyProject.Web.Services.IMyWCFService" /><br /><span style="background-color:yellow;"> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /></span><br /> </service><br /></services></span><br /></blockquote><br />the Add Service Reference started working. W00t.<br /><br />*note for those who spotted the invalid url: as usual, identities have been changed to protect the innocent.shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-68429937062778880092010-01-12T11:23:00.006+13:002010-01-12T11:45:56.055+13:00Better file searching in Windows 7Wow, i just found another cool feature in Windows 7 - a nice way to do file searching.<br /><br />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:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Qp19D1g5eY0/S0uoMh7bYmI/AAAAAAAAACs/967DoeMeHRQ/s1600-h/date+modified+search+001.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 162px;" src="http://3.bp.blogspot.com/_Qp19D1g5eY0/S0uoMh7bYmI/AAAAAAAAACs/967DoeMeHRQ/s400/date+modified+search+001.png" alt="" id="BLOGGER_PHOTO_ID_5425615109317419618" border="0" /></a><br /><br /><br />The syntax is<br /><br /> <span style="font-size:85%;"><span style="font-family: courier new;">datemodified:<start date> .. <end date></span></span><br /><br />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.<br /><br />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!!)<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Qp19D1g5eY0/S0uoZ_fqB0I/AAAAAAAAAC0/9QgoGQJiIgM/s1600-h/date+modified+search+002.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 112px;" src="http://3.bp.blogspot.com/_Qp19D1g5eY0/S0uoZ_fqB0I/AAAAAAAAAC0/9QgoGQJiIgM/s400/date+modified+search+002.png" alt="" id="BLOGGER_PHOTO_ID_5425615340592301890" border="0" /></a>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-40100542066109036042010-01-11T22:48:00.003+13:002010-01-11T23:09:40.103+13:00Dynamic ControlTemplate in Silverlight<span xmlns=""><p>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.<br /><br />To achieve this is simple enough - just override the <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ControlTemplate</span> that is assigned to the <span style=";font-family:Consolas;font-size:8pt;" >DisplayTemplate</span> 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 <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ControlTemplate</span> programmatically.<br /><br />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 <em>generic.xaml</em> 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<br /><br /><br /> <span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">ControlTemplate</span> ct = <span style="color: rgb(43, 145, 175);">Application</span>.Current.Resources[<span style="color: rgb(163, 21, 21);">"MyImageColumnTemplate"</span>] <span style="color:blue;">as </span><span style="color: rgb(43, 145, 175);">ControlTemplate</span>;</span><br /><br />Unfortunately, this didn't work, <span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">Application</span>.Current.Resources</span> had no idea about my <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ControlTemplate</span>. Then i realised that because it was defined in a <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ResourceDictionary</span>, i needed to load that <span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">ResourceDictionary </span></span>and merge it with the resources associated with the current application. To do that is pretty simple:<br /><br /> </p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> ResourceDictionary</span> dict = <span style="color:blue;">new</span><span style="color: rgb(43, 145, 175);"> ResourceDictionary</span>();<span style="color: rgb(43, 145, 175);"><br /> </span>dict.Source = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"path to resource dictionary"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Absolute);</span></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">Application</span>.Current.Resources.MergedDictionaries.Add(dict);</span><br /> </p><p><br /></p><p>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:<br /></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> Uri</span> uri = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"pack://application:,,,/MySilverlightControls;component/themes/generic.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Absolute);</span><br /> </p><p>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:<br /></p><p><span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" ><br /> </span><span style="font-size:9pt;"><strong>UriFormatException was unhandled by user code<br /></strong></span></p><p><span style="font-size:9pt;">Invalid URI: A port was expected because of there is a colon (':') present but the port could not be parsed.<br /></span></p><p>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:</p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;">if</span> (!<span style="color: rgb(43, 145, 175);">UriParser</span>.IsKnownScheme(<span style="color: rgb(163, 21, 21);">"pack"</span>))</span></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> UriParser</span>.Register(<span style="color:blue;">new</span><span style="color: rgb(43, 145, 175);">GenericUriParser</span>(<span style="color: rgb(43, 145, 175);">GenericUriParserOptions</span>.GenericAuthority), <span style="color: rgb(163, 21, 21);">"pack"</span>, -1);<br /></span></p><p><br /></p><p>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.<br /></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"><br /> </span>dict.Source = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"/themes/generic.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Relative);</span><br /> </p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"><br /> </span>dict.Source = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"./../../themes/generic.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Relative);<br /></span></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"><br /> </span>dict.Source = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"../../themes/generic.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Relative);</span><br /> </p><p>all produced the error<br /></p><p><span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" ><br /> </span><span style="font-size:9pt;">Error HRESULT E_FAIL has been returned from a call to a COM component.<br /></span></p><p><span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" ><br /> </span><span style="font-size:9pt;"><em>at MS.Internal.XcpImports.CheckHResult(UInt32 hr)<br /></em></span></p><p><span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" ><br /> </span><span style="font-size:9pt;"><em>at MS.Internal.XcpImports.SetValue(INativeCoreTypeWrapper obj, DependencyProperty property, String s)<br /></em></span></p><p><span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" ><br /> </span><span style="font-size:9pt;"><em>at etc, etc...<br /></em></span></p><p><br /></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"><br /> </span>dict.Source = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"pack://application:,,,/themes/generic.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Absolute);</span><br /> </p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"><br /> </span>dict.Source = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"pack://application:,,,/MySilverlightControls;component/themes/generic.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Absolute);</span><br /> </p><p>both produced the error<br /></p><p><span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" ><br /> </span><span style="font-size:9pt;">Exception from HRESULT: 0x80072EE5<br /></span></p><p>which effectively means "Invalid URI".<br /></p><p>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 <span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">ResourceDictionary</span><br /> </span>(called Dictionary1.xaml) under the Themes folder, put the <span style=";font-family:Consolas;font-size:8pt;color:blue;" >MyImageColumnTemplate</span> XAML in there, and went back through my seven different ways of specifying the URI for the <span style="color: rgb(43, 145, 175);font-family:Consolas;font-size:8pt;" >ResourceDictionary</span>. Suddenly i had success! This ended up being the code that worked:<br /></p><p>XAML:<br /></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(163, 21, 21);"><br /></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"><</span><span style="color: rgb(163, 21, 21);">ControlTemplate</span><span style="color:red;"> x</span><span style="color:blue;">:</span><span style="color:red;">Name</span><span style="color:blue;">="MyImageColumnTemplate" ><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"><</span><span style="color: rgb(163, 21, 21);">Grid</span><span style="color:red;"> MaxHeight</span><span style="color:blue;">="20"</span><span style="color:red;"> MaxWidth</span><span style="color:blue;">="20"><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"><</span><span style="color: rgb(163, 21, 21);">Grid.Resources</span><span style="color:blue;">><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"><</span><span style="color: rgb(163, 21, 21);">localGrid</span><span style="color:blue;">:</span><span style="color: rgb(163, 21, 21);">EnumColumnImageConverter</span><span style="color:red;"> x</span><span style="color:blue;">:</span><span style="color:red;">Key</span><span style="color:blue;">="ImageContentConverter"/><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"></</span><span style="color: rgb(163, 21, 21);">Grid.Resources</span><span style="color:blue;">><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"><</span><span style="color: rgb(163, 21, 21);">Image</span><span style="color:red;"> Source</span><span style="color:blue;">="{</span><span style="color: rgb(163, 21, 21);">Binding</span><span style="color:red;"> EditValue</span><span style="color:blue;">,</span><span style="color:red;"> Converter</span><span style="color:blue;">={</span><span style="color: rgb(163, 21, 21);">StaticResource</span><span style="color:red;"> ImageContentConverter</span><span style="color:blue;">}}" /><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"></</span><span style="color: rgb(163, 21, 21);">Grid</span><span style="color:blue;">><br /></span></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"></</span><span style="color: rgb(163, 21, 21);">ControlTemplate</span><span style="color:blue;">></span></span><br /> </p><p><br /></p><p>C#:<br /></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">Uri</span> uri = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">Uri</span>(<span style="color: rgb(163, 21, 21);">"/MySilverlightControls;component/themes/Dictionary1.xaml"</span>, <span style="color: rgb(43, 145, 175);">UriKind</span>.Relative);<br /></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">ResourceDictionary</span> dict = <span style="color:blue;">new </span><span style="color: rgb(43, 145, 175);">ResourceDictionary</span>();<br /></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" >dict.Source = uri;<br /></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">Application</span>.Current.Resources.MergedDictionaries.Add(dict);<br /></span></p><p><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span xmlns=""><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);"> </span></span></span><span style=";font-family:Consolas;font-size:8pt;" ><span style="color: rgb(43, 145, 175);">ControlTemplate</span> ct = (<span style="color: rgb(43, 145, 175);">ControlTemplate</span>)<span style="color: rgb(43, 145, 175);">Application</span>.Current.Resources[<span style="color: rgb(163, 21, 21);">"MyImageColumnTemplate"</span>];<br /></span></p><p><span style=";font-family:Consolas;font-size:8pt;" ><span style="color:blue;"> this</span>.DisplayTemplate = ct;</span><br /> </p><p>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!).<br /></p><p><br /></p><p>Wow, this post was a bit long but i tried my best to keep it simple, and i hope it helps someone out there.<br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p></span>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com7tag:blogger.com,1999:blog-496084106724185073.post-84185540191485974552009-09-23T21:19:00.004+12:002009-09-23T21:50:42.107+12:00Rogue msiexec processes after installing VS2010I need to give big props to Mebyon Kernow for his blog post <a href="http://blogs.msdn.com/morgan/archive/2009/07/01/msiexec-taking-50-cpu.aspx">here</a> with the answer to this problem.<br /><br />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.<br /><br />The answer in Mebyon's post was quite simple, and finding it meant i didn't have to think too hard for myself.<br /><br />* I actually used Bing, but saying "a quick bing" doesn't have the same zhoosh as saying "a quick google".<br /><br /><br /><br /><br />Keywords: VS2010, rogue process, msiexecshanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-88681663828913412392009-08-03T12:56:00.023+12:002009-08-04T10:00:24.205+12:00SourceSafe history report - from the command lineSometimes 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?<br /><br />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?).<br /><br /><br />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 <span style="font-family:courier new;">srcsafe.ini</span> file of the repository:<br /><br /><span style="color: rgb(0, 204, 204);font-family:courier new;font-size:85%;" ><span style="color: rgb(51, 204, 0);">C:></span>set SSDIR=c:\Program Files\Sourcesafe\</span><br /><br />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:<br /><br /><span style="color: rgb(0, 204, 204);font-family:courier new;font-size:85%;" ><span style="color: rgb(51, 204, 0);">C:></span>cd C:\Program Files\Microsoft Visual SourceSafe</span><br /><br />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.<br /><br /><span style="color: rgb(0, 204, 204);font-family:courier new;font-size:85%;" ><span style="color: rgb(51, 204, 0);">C:\Program Files\Microsoft Visual SourceSafe></span>ss history "$/Projects/My Project" -Oc:\history.txt -R -vd30/07/09;09:00a~01/07/09;09:00a</span><br /><br />Breaking down the command line arguments:<br /><table style="border: 1px solid black; width: 100%;" cellpadding="0" cellspacing="0"><br /><thead style="background-color: lightgrey; color: rgb(0, 0, 0);"><td><b>Command option</b></td><td><b>What it means</b></td></thead><tbody><br /><tr><br /><td>history </td><td><i>ss.exe</i> can be used for many things - we are telling it to do a history report.</td><br /></tr><br /><tr><br /><td>"$/Projects/My Project"</td><td>Path to the project i want reported on in the SourceSafe repository pointed to by SSDIR.</td><br /></tr><br /><tr><br /><td>-Oc:\blah.txt</td><td><i>-O</i> means <i>output</i>, and then i specify the file i want the data outputed to.</td><br /></tr><br /><tr><br /><td>-R</td><td>This is the recursive flag, IOW it means do all projects (folders) under the project specified as the start point.</td><br /></tr><br /><tr><br /><td>-vd</td><br /><td>This 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'.<br /></td><br /></tr><br /></tbody></table><br /><br />This gives me a nice little text file called <span style="font-family:courier new;">history.txt</span> that i can scan through (or programmatically parse), it looks a little like this:<br /><span style="color: rgb(51, 102, 255);font-size:85%;" ><br />**********************<br />Label: "1.0.0.635"<br />User: Builder Date: 31/07/09 Time: 5:02p<br />Labeled<br />Label comment: Automated Build of Version 1.0.0.635<br /><br />***** AssemblyInfo.cs *****<br />Version 29<br />User: Builder Date: 31/07/09 Time: 5:02p<br />Checked in $/Projects/My Project/Properties<br />Comment: Automated Build of Version 1.0.0.635<br /><br />***** GridView.cs *****<br />Version 41<br />User: Shane Date: 16/07/09 Time: 4:27p<br />Checked in $/Projects/My Project/Controls/GridView<br />Comment: case 12345, changed how a column was rendered</span><br /><br /><br /><br /><br /><br /><br /><span style="font-size:78%;">Keywords: sourcesafe, history report, command line</span>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com3tag:blogger.com,1999:blog-496084106724185073.post-92156787980213905132009-07-04T19:08:00.004+12:002009-07-04T20:26:00.840+12:00Activating Office 2007 on Windows 7 RCI 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.<br /><br />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 <span style="font-style: italic;">Run as administrator</span> option when you right click on them.<br /><br /><ul><br /><li> determine where the Office product is installed to*, i.e. it will usually be <span style="font-size:85%;"><span style="font-family:courier new;">C:\Program Files (x86)\Microsoft Office\Office12</span></span><br /></li><li> find the exe of one of the products, so you want to find <span style="font-size:85%;"><span style="font-family:courier new;">WINWORD.EXE</span></span>, <span style=";font-family:courier new;font-size:85%;" >EXCEL.EXE</span>, <span style="font-size:85%;"><span style="font-family:courier new;">OUTLOOK.EXE</span></span> or <span style="font-size:85%;"><span style="font-family:courier new;">MSACCESS.EXE</span></span>, right click on the file, select <span style="font-style: italic;">Run as administrator</span><br /></li><li> click <span style="font-style: italic;">Yes</span> on the UAC prompt, or select the Administrator user and enter the admin password if you get that prompt<br /></li><li> if you are running Word/Excel/Access, click on the launch orb in the top left corner, select <span style="font-style: italic;">Word Options</span>/<span style="font-style: italic;">Excel Options</span>/<span style="font-style: italic;">Access Options</span>, in the Options popup dialog select <span style="font-style: italic;">Resources</span>, then select <span style="font-style: italic;">activate Microsoft Office</span><br /></li><li> if you are running Outlook from the step above, select <span style="font-style: italic;">Help</span>-><span style="font-style: italic;">Activate Product</span><br /></li><li> in the activation dialog, select the activate via internet option, click <span style="font-style: italic;">Next</span><br /></li><li> Office should now activate. If it doesn't then you may have a firewall, proxy, or general connection problem.<br /></li></ul><br /><br />*note that because the Office products have a special sort of shortcut installed, you cannot just right click on the short cut and go <span style="font-style: italic;">Properties</span>-><span style="font-style: italic;">General </span>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<br /><br /><br /><br />Keywords: Office 2007 activation, activation error, Windows 7shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-69475043885926958732009-04-11T19:14:00.004+12:002009-04-11T21:02:13.865+12:00Goodbye RSS BanditI'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.<br /><br />What annoyed me the most about Bandit?<br /> - 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.<br /><br /> - 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.<br /><br /> - 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.<br /><br /> -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.<br /><br />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.<br /><br />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 :)<br /><br />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.<br /><br />I might revisit Bandit it in a year and see what progress has been made. Until then, c'est la vie.<br /><br /><br /><br /><br />Keywords: rss bandit, losing posts, feeddemon, rssshanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-42212047801308316662009-02-05T12:19:00.006+13:002009-02-05T13:04:20.501+13:00I 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:<br /><br /><span style="color: rgb(51, 153, 153);font-family:courier new;" >Error: Sys.InvalidOperationException: InitializeError error #2104 in control '[insert my control id here]': Could not download the Silverlight application. Check web server settings</insert></span><br /><br />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.<br /><br />The best solution i found was this blog post: <span style="color: rgb(51, 102, 255);">http://web.iotap.com/Blogs/tabid/277/EntryId/65/Configuring-Silverlight-2-0-Application-in-IIS.aspx</span><br /><br />You need to ensure that the website hosting the Silverlight control has the following MIME types registered:<br /><br /><table cellpadding="0" cellspacing="0" style="border: 1px solid black;"><br /><thead style="background-color: lightgrey; color: #000000;"><td><b>Extension</b></td><td><b>MIME type</b></td></thead><tbody><tr><td style="padding-left: 4px;">.application</td><td style="padding-left: 4px;">application/x-ms-application</td></tr><tr><td style="padding-left: 4px;">.deploy</td><td style="padding-left: 4px;">application/octet-stream</td></tr><tr><td style="padding-left: 4px;">.manifest</td><td style="padding-left: 4px;">application/manifest</td></tr><tr><td style="padding-left: 4px;">.xaml</td><td style="padding-left: 4px;">application/xaml+xml</td></tr><tr><td style="padding-left: 4px;">.xap</td><td style="padding-left: 4px;">application/x-silverlight-app</td></tr><tr><td style="padding-left: 4px;">.xbap</td><td style="padding-left: 4px;">application/x-ms-xbap</td></tr><tr><td style="padding-left: 4px;">.xps</td><td style="padding-left: 4px;">application/vnd.ms-xpsdocument</td></tr></tbody></table><br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Qp19D1g5eY0/SYonpGrcDwI/AAAAAAAAACg/hSqJgw-Y-aQ/s1600-h/20.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 368px; height: 400px;" src="http://1.bp.blogspot.com/_Qp19D1g5eY0/SYonpGrcDwI/AAAAAAAAACg/hSqJgw-Y-aQ/s400/20.gif" alt="" id="BLOGGER_PHOTO_ID_5299091498675736322" border="0" /></a><br /><br /><br />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.<br /><br />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?).<br /><br /><br /><br /><br />keywords: silverlight 2, deploy, error 2104, silverlight mime typesshanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-18401876850307733112008-09-10T00:20:00.002+12:002008-09-10T00:36:48.626+12:00Chrome and its processesWow, 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 (<a href="http://techfilth.blogspot.com/2008/09/google-chrome.html">here</a>), and in an excellent post he has answered that exact question. You can find his post <a href="http://www.hanselman.com/blog/MicrosoftIE8AndGoogleChromeProcessesAreTheNewThreads.aspx">here</a>. <div><br /></div><div>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).</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size: x-small;">Keywords: google, chrome, IE8, processes</span></div>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-45969357025360190122008-09-04T21:31:00.005+12:002008-09-04T22:38:05.298+12:00Chrome, 24 hours after installationOkay, 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.<div><br /></div><div>Yesterday i stated "<span style="color: rgb(204, 204, 204); line-height: 20px; font-family:'Trebuchet MS';font-style: italic;font-size:13px;">it must offer me some way to configure its options.....so there is no excuse for not having an options/settings UI</span>". 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.</div><br /><div><br /></div><div>But wait, there's more!! In the last 24 hours i have found a few more things to have a spew about:</div><div><br /></div><div> - 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?</div><div><br /></div><div> - 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, <span class="Apple-style-span" style="font-weight: bold;"><span class="Apple-style-span" style="font-style: italic;">and any other application that uses a plugin browser component</span></span>, 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?</div><div><br /></div><div> - 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.</div><div><br /></div><div> - 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).</div><div><br /></div><div>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.</div><div><br /></div><div><br /></div><div>*Don't believe me? <a href="http://en.wikipedia.org/wiki/Comparison_of_web_browsers">Check this link</a>, 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 <a href="http://sourceforge.net/search/?type_of_search=soft&words=web+browser">Sourceforge</a>, 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.</div><div><br /></div><div><br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size:small;">Keywords: google, chrome, open source software</span></div>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-38608980434190282362008-09-03T23:11:00.005+12:002008-09-04T00:34:12.653+12:00Google ChromeApplication being named and shamed: Google Chrome<div><br /></div><div>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 :)</div><div><br /></div><div>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:</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Qp19D1g5eY0/SL5_RqLW6UI/AAAAAAAAACI/Y9XguEIgTZs/s1600-h/40.gif"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_Qp19D1g5eY0/SL5_RqLW6UI/AAAAAAAAACI/Y9XguEIgTZs/s400/40.gif" border="0" alt="" id="BLOGGER_PHOTO_ID_5241766957662267714" /></a><br /><div>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?</div><div><br /></div><div>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?</div><div><br /></div><div>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). </div><div><br /></div><div>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!!!!</div><div><br /></div><div>Fifth: WTF is with all the processes being started by Chrome? This was with two tabs opened:</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Qp19D1g5eY0/SL5_XDuspfI/AAAAAAAAACQ/S9wfw2ZoZZU/s1600-h/45.gif"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_Qp19D1g5eY0/SL5_XDuspfI/AAAAAAAAACQ/S9wfw2ZoZZU/s400/45.gif" border="0" alt="" id="BLOGGER_PHOTO_ID_5241767050420725234" /></a><br /><div>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":</div><br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Qp19D1g5eY0/SL6BfdHaoWI/AAAAAAAAACY/CSDZQ78ffug/s1600-h/46.gif"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_Qp19D1g5eY0/SL6BfdHaoWI/AAAAAAAAACY/CSDZQ78ffug/s400/46.gif" border="0" alt="" id="BLOGGER_PHOTO_ID_5241769393697497442" /></a><br /><br /><br /><div>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".</div><div><br /></div><div>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.</div><div><br /></div><div>When an application installs on my machine, it must:</div><div>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.</div><div>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.</div><div>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.</div><div><br /></div><div><br /></div><div>* 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 <span class="Apple-style-span" style="font-weight: bold;">data</span>, 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%.</div><div><br /></div><div>** 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?</div><div><br /></div><div><br /></div><div><span class="Apple-style-span" style="font-size: small;">Keywords: google, chrome, sub optimal, bad install</span></div>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-30582080479393545332008-08-30T16:48:00.000+12:002008-08-30T18:22:46.476+12:00Cannot debug web based apps in Visual Studio (VS2008)The company or product being named and shamed: Eset NOD32 AntiVirus.<br /><br />Here's why: I was trying to debug a Silverlight application i had just whipped up, but i had an issue - i would press F5 in Visual Studio, IE (IE7 to be exact) would pop up, but i would just get a "Internet Explorer cannot display the webpage" message in IE, along with the usual list of suggestions to fix the problem. Upon checking the url in the address bar, i see it is pointing to the correct place:<br /><br /><span style="color: rgb(51, 204, 0);font-size:85%;" ><span style="font-family:courier new;">http://localhost:33392/SilverlightApplication2TestPage.aspx</span> </span><br /><br />so of course i initially think the problem was my fault and spend some time checking the code for errors, but of course there were none. So i loaded up a current web application that i am working on, hit F5, and the same thing happens. It has been about three weeks since i worked on that web application, and the only changes that have been made to this machine in that time are when i installed NOD32 and SP1 for Vista. As i had installed SP1 just a day ago, and because it is a major install (i.e. heaps of critical OS files get updated), i thought that it must have caused the issue. So i did what any self respecting geek does - i asked Google. It's not easy formulating a search query when you don't know exactly what the problem is, so i started with:<br /><br /><span style="font-style: italic; color: rgb(51, 204, 0);">vista sp1 cannot debug web application</span><br /><br />and after checking a few likely hits on the list i start noticing a recurring theme: Eset NOD32 was being blamed for the behaviour. The problem occurs because NOD32 adds an entry to your hosts file:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_Qp19D1g5eY0/SLjgsbGxIxI/AAAAAAAAABs/LVc9jKTOWuY/s1600-h/1.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_Qp19D1g5eY0/SLjgsbGxIxI/AAAAAAAAABs/LVc9jKTOWuY/s400/1.gif" alt="" id="BLOGGER_PHOTO_ID_5240185220240581394" border="0" /></a><br /><br />it's the entry at the bottom, the one that says "<span style="color: rgb(51, 204, 0);font-family:courier new;font-size:85%;" >::1 localhost</span>". I'm not sure exactly *why* it adds the entry, but obviously it's there to foil those badass pieces of malware that people get when they visit dodgy porn sites. But it also has the side effect of breaking the mapping between the loopback 127.0.0.1 and the name "localhost", so when Cassini spins up and tries to serve my web or silverlight application it cannot resolve "localhost" back to the local machine.<br /><br />Fixing this is easy - just remove the entry from the hosts file. And i have to say shame on you Eset, for not considering this behaviour, and for not checking for the presence of development tools in your install process.<br /><br />To remove the entry:<br />- find your hosts file. This resides in %windows%\system32\drivers\etc, it is just called "hosts", with no suffix. This location is hidden, so you will have to make sure that both hidden and system files are visible.<br />- if you are running XP, you can just load the file into Notepad, remove the line, save it, then rerun your VS project.<br />- if you are running Vista, you have to jump through a few more hoops first. You need to take ownership of the file, to do this start a command prompt with administrative privileges:<br /><br />either type Start-R, type cmd and then press Ctrl-Shift-Enter, and accept the UAC prompt<br />- or -<br />Start->All Programs->Accessories, right click on <span style="font-style: italic;">Command Prompt</span>, choose <span style="font-style: italic;">Run As Administrator</span>, and accept the UAC prompt<br /><br />Then type the following commands:<br /><br />take ownership of the file (so that you can change the ACLs on it)<br /><br /><span style="font-size:85%;"><span style="font-family:courier new;">c:\><span style="color: rgb(51, 204, 0);">takeown /f c:\windows\system32\drivers\etc\hosts</span></span> </span><br /><br />then you need to grant yourself full access rights to the file:<br /><br /><span style="font-size:85%;"><span style="font-family:courier new;">c:\><span style="color: rgb(51, 204, 0);font-family:courier new;" >icacls c:\windows\system32\drivers\etc\hosts /grant </span></span><span style="color: rgb(51, 204, 0);font-family:courier new;" >[YourUserName]</span></span><span style="color: rgb(51, 204, 0);font-family:courier new;font-size:85%;" >:f</span><br /><br />then you can just load it into Notepad, remove the line, and save it. Of course it also wouldn't be a bad idea to revoke your access rights to the file once you have edited it, just to make sure it doesn't "accidentally" get modified without you realising it (modifying this file is an easy attack vector for malware). This is what the process looks like:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_Qp19D1g5eY0/SLjln-3w-uI/AAAAAAAAAB8/KvrFbo6KpNo/s1600-h/2.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_Qp19D1g5eY0/SLjln-3w-uI/AAAAAAAAAB8/KvrFbo6KpNo/s400/2.gif" alt="" id="BLOGGER_PHOTO_ID_5240190641500125922" border="0" /></a><br /><br /><br />Those whited-out bits are just my username, you don't need to know what that is :) Interestingly enough, when i edit this file and save it, Windows Defender captures the change and prompts me to accept or revoke the change, but this never happened when i installed NOD32. I can't believe that Defender gets suspended while an application is installed (otherwise it would never detect that some malware just installed itself), so maybe NOD32 is doing something special during its install process.<br /><br /><br />Here are a couple of references that were helpful in diagnosing this problem:<br /><span style="font-size:85%;"><a href="http://forums.vnunet.com/message.jspa?messageID=1023002">http://forums.vnunet.com/message.jspa?messageID=1023002</a><br /><a href="http://www.superwasp.net/weblog/2008/03/fix-localhost-unavailable-with-eset.htm">http://www.superwasp.net/weblog/2008/03/fix-localhost-unavailable-with-eset.htm</a><br /><a href="http://www.wilderssecurity.com/archive/index.php/t-198020.html">http://www.wilderssecurity.com/archive/index.php/t-198020.html</a></span><br /><br /><br /><br /><br /><span style="font-size:78%;">Keywords: VS2008, Eset, Nod32, debugging, F5</span>shanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-16453944204378971532008-08-14T15:22:00.000+12:002008-08-14T16:57:56.637+12:00Using Trim() in SSRS2005 - and how it doesn't workI found a situation today where the <span style="color: rgb(51, 255, 51);font-family:courier new;" >Trim()</span> function doesn't work when used in a textbox expression.<br /><br />I had a cell in a table that was showing the notes associated with a record, so the expression was simply:<br /><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" >= Fields!Notes.Value</span><br /><br />While viewing the generated report, it was noticed that some notes had a lot of whitespace either at the leading or trailing end of the note, which caused the whole row in the table to expand in size as the note text wrapped, so I ended up with an effect like this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_Qp19D1g5eY0/SKO3FdYV8dI/AAAAAAAAABU/e-BfaAgq7EY/s1600-h/20.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_Qp19D1g5eY0/SKO3FdYV8dI/AAAAAAAAABU/e-BfaAgq7EY/s400/20.gif" alt="" id="BLOGGER_PHOTO_ID_5234228496348606930" border="0" /></a><br /><br />This can look pretty ugly on a report. So the simple solution would be to trim the whitespace from the note text as part of the textbox expression:<br /><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" >= Trim(Fields!Notes.Value)</span><br /><br />Bzzzzzzzztttttt!!!!!! Wrong answer - it didn't trim the note text at all. OK, time for Plan B, do a specific trim:<br /><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" >=LTrim(RTrim(Fields!Notes.Value))</span><br /><br />But once again, no cigar <span style="font-style: italic;">(and yes, i know that <span style="font-family:courier new;">Trim()</span> should just call </span><span style="font-style: italic;font-family:courier new;" >LTrim()</span><span style="font-style: italic;"> and </span><span style="font-style: italic;font-family:courier new;" >RTrim()</span><span style="font-style: italic;"> under the hood, but i would be a bad developer if i just went with that assumption and didn't try this option)</span>. I'm thinking that this was caused by a failure to implicitly cast the <span style="color: rgb(51, 255, 51);font-family:courier new;" >Field!Notes.Value</span> from an object to a string, so the various trim functions failed. So i though of another approach:<br /><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" >=Fields!Notes.Value.ToString().Trim()</span><br /><br />and success!!!!!!...... and failure. This expression worked perfectly on rows that had a valid note, but on the ones where the value was null i got the familiar <span style="color: rgb(51, 255, 51);font-family:courier new;" >#Error</span> statement in the textbox instead of a blank value:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_Qp19D1g5eY0/SKO3OponnxI/AAAAAAAAABc/HEzdH-iPTts/s1600-h/22.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_Qp19D1g5eY0/SKO3OponnxI/AAAAAAAAABc/HEzdH-iPTts/s400/22.gif" alt="" id="BLOGGER_PHOTO_ID_5234228654256922386" border="0" /></a><br /><br />This meant one more modification was required - i needed to check for a null value before i attempt the <span style="color: rgb(51, 255, 51);font-family:courier new;" >ToString().Trim()</span> on the value. I am not a fan of the VB style <span style="color: rgb(51, 255, 51);font-family:courier new;" >IIf</span> statement, as it doesn't shortcut (this means every argument in the function gets evaluated, regardless of the result of the expression), this means that the expression:<br /><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" >=IIf(IsNothing(Fields!Notes.Value), Nothing, Fields!Notes.Value.ToString().Trim())</span><br /><br />would still error because the false part of the <span style="color: rgb(51, 255, 51);font-family:courier new;" >IIf</span> would still get evaluated every time. The easy way round this is to just use a bit of custom code, and this is what i ended up with:<br /><br /><span style="font-style: italic;">Textbox expression:</span><br /><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" >=Code.TrimStringValue(Fields!Notes.Value)</span><br /><br /><span style="font-style: italic;">and the custom code:</span><br /><span style="color: rgb(102, 255, 255);font-family:courier new;" ><br />Public Function TrimStringValue(ByVal fieldValue As Object) As Object<br /> If IsNothing(fieldValue)<br /> TrimStringValue = Nothing<br /> Else<br /> TrimStringValue = fieldValue.ToString().Trim()<br /> End If<br />End Function</span><br /><br /><br /><br />Keywords: ssrs2005, ssrs, trim, ltrim, rtrim, iifshanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com0tag:blogger.com,1999:blog-496084106724185073.post-50760205843727053392008-07-23T11:34:00.000+12:002008-07-24T14:43:59.096+12:00Calculate a median on a group in SSRSToday i needed to do something that turned out to be impossible to do in the conventional manner. I was doing a report on some items that are measured in days, each row in the report was actually a group, and i needed to calculate a median for that group. Step by step, here is how i did it.<br /><br />First, here is the report layout i started with:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_Qp19D1g5eY0/SIfnoiaD1-I/AAAAAAAAABM/KsXb2zvh-Xs/s1600-h/3.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_Qp19D1g5eY0/SIfnoiaD1-I/AAAAAAAAABM/KsXb2zvh-Xs/s400/3.gif" alt="" id="BLOGGER_PHOTO_ID_5226400576203642850" border="0" /></a><br /><br /><br />as you can see i have two row groups, with the detail row of the second group hidden (because i don't want to list all the details, there are hundreds of them).<br /><br />Initially this seemed quite simple - for each detail row i would call a custom function which would add the value to an array, and then in the group footer i could just call another custom function called <span style="color: rgb(102, 204, 204);font-family:courier new;" >CalculateMedian()</span> to get the median of the group. But the problem is, when i called <span style="color: rgb(102, 204, 204);font-family:courier new;" >CalculateMedian()</span>, my array was empty, so i had no values to calculate with. I double checked, and yes the <span style="color: rgb(102, 204, 204);font-family:courier new;" >AddToMedian() </span>function was being called for every detail row, so wtf? After some digging around and some Googling, i found one or two blog posts that mention group header and footer expressions get evaluated before the group detail expressions - this meant i was trying to calculate the median before the values had even been added to the array! What the???!!!! Who the fuck came up with that brilliant design, and why? I can understand processing group headers before the details (for sorting puroses, etc), but surely footers should be done last? I mean, for 99.9% of cases, footers will contain aggregate or summary information, right? (I don't care about the other 0.1% of cases and whatever they are trying to put in the footer, if they want to do non-standard stuff then they should be prepared to feel the pain of getting it to work).<br /><br />So, i couldn't use the group footer to show my summary information, a different approach was required. To achieve my median calculation, three things need to happen:<br />- at the start of each category group the array used to accumulate the values needed to be emptied.<br />- i needed to iterate the detail rows of each category group, as there is no other way of passing the group values through to a custom function.<br />- at the end of each category group i need to calculate and display the median value.<br /><br />To begin with, i replaced the table that had the division and category row groups on it with a list container for the division group (call it the <span style="color: rgb(102, 204, 204);font-family:courier new;" >DivisionList</span>). Above that i put a rectangle with a textbox for each column title, this replicates what was in the heading of the table:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_Qp19D1g5eY0/SIfAKe54PZI/AAAAAAAAAAM/toG2lXqtTFk/s1600-h/5.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_Qp19D1g5eY0/SIfAKe54PZI/AAAAAAAAAAM/toG2lXqtTFk/s400/5.gif" alt="" id="BLOGGER_PHOTO_ID_5226357178913799570" border="0" /></a><br /><br /><br />After that, i nest another list inside the <span style="color: rgb(102, 204, 204);font-family:courier new;" >DivisionList</span> container, this has a grouping set to group by category and is called the <span style="color: rgb(102, 204, 204);font-family:courier new;" >CategoryList</span>:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_Qp19D1g5eY0/SIfBaKdj89I/AAAAAAAAAAU/zktqGb19NIU/s1600-h/6.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_Qp19D1g5eY0/SIfBaKdj89I/AAAAAAAAAAU/zktqGb19NIU/s400/6.gif" alt="" id="BLOGGER_PHOTO_ID_5226358547815855058" border="0" /></a><br /><br /><br />Note that immediately inside each list container i have a textbox which shows the name of the grouped item. The <span style="color: rgb(102, 204, 204);font-family:courier new;" >CategoryList </span>is now going to perform the same function as each row did in the original table, so i add a few more textboxes to hold the values for that "row":<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_Qp19D1g5eY0/SIfDRPXW6wI/AAAAAAAAAAc/-x6odgDSoL8/s1600-h/8.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_Qp19D1g5eY0/SIfDRPXW6wI/AAAAAAAAAAc/-x6odgDSoL8/s400/8.gif" alt="" id="BLOGGER_PHOTO_ID_5226360593536445186" border="0" /></a><br /><br /><br />Note that i have already added the expressions, the important one is the median textbox, which has the expression:<br /><br /><span style="color: rgb(102, 204, 204);font-family:courier new;" >=Code.CalculateMedian()</span><br /><br />So this leaves the big question: i have the required grouping set up, and my aggregate/summary expressions in place, but how am i going to achieve objectives 1 and 2 from above, which was to reset the array holding the values, and to iterate rows in the category group so that i can place the individual values into the array the median is calculated from?<br /><br />Hmmm, how can we iterate the values? I know - let's bring back the table, just give it one column:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_Qp19D1g5eY0/SIfdw-T18JI/AAAAAAAAAA8/NRbyiudRnPg/s1600-h/10.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_Qp19D1g5eY0/SIfdw-T18JI/AAAAAAAAAA8/NRbyiudRnPg/s400/10.gif" alt="" id="BLOGGER_PHOTO_ID_5226389726016434322" border="0" /></a><br /><br /><br />if you are paying attention, you will see that it is inside the <span style="color: rgb(102, 204, 204);font-family:courier new;" >CategoryList </span>container, with all the textboxes. Check out the expression, as the values get placed into the table rows they also get lodged in the array that is used for the median calculation. That's objective #2 resolved, now for objective #1, resetting the array. It's time for sly trick #1, using the knowledge that the table header expressions are processed before the table detail expressions, we can do this:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_Qp19D1g5eY0/SIfdqqFkHEI/AAAAAAAAAA0/bjKn20iw6tg/s1600-h/12.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_Qp19D1g5eY0/SIfdqqFkHEI/AAAAAAAAAA0/bjKn20iw6tg/s400/12.gif" alt="" id="BLOGGER_PHOTO_ID_5226389617508621378" border="0" /></a><br /><br /><br />see what i did? I used an expression in the table header to reset the array. I also set the visibility of the table to hidden - i don't want this to show on the rendered report, remember? Sweet, now i have just one little detail left to take care of - how can i ensure that the table rows are filled before i call <span style="color: rgb(102, 204, 204);font-family:courier new;" >CalculateMedian()</span> from my median textbox?<br /><br />To achieve this, i employ sly trick #2: reports are rendered top to bottom, left to right. I've not seen this documented anywhere, it's just what i've observed, i've depended upon that behaviour in the past and it hasn't let me down yet. So, make the table 0.1cm wide, and make it the same height as the textboxes that are in the <span style="color: rgb(102, 204, 204);font-family:courier new;" >CategoryList </span>container:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_Qp19D1g5eY0/SIfdgzE4b9I/AAAAAAAAAAs/Y0mAt5kVK_E/s1600-h/14.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_Qp19D1g5eY0/SIfdgzE4b9I/AAAAAAAAAAs/Y0mAt5kVK_E/s400/14.gif" alt="" id="BLOGGER_PHOTO_ID_5226389448122986450" border="0" /></a><br /><br /><br />see that orange kind of spot on the report there? I added some colour to the table just so you could see what i've done. Now that it is resized, i move it to the left and above of the category label (IOW move it to location 0,0 of the <span style="color: rgb(102, 204, 204);font-family:courier new;" >CategoryList </span>container, look for the orange spot). This ensures that it gets filled/rendered before the rest of the textboxes in that list container:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_Qp19D1g5eY0/SIfdZshG3-I/AAAAAAAAAAk/C_HKzopakpk/s1600-h/16.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_Qp19D1g5eY0/SIfdZshG3-I/AAAAAAAAAAk/C_HKzopakpk/s400/16.gif" alt="" id="BLOGGER_PHOTO_ID_5226389326103240674" border="0" /></a><br /><br /><br />And that's all. Now, i just adjust the height of my list containers, and i run the report. This is what the final result looks like:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_Qp19D1g5eY0/SIffxKbNAYI/AAAAAAAAABE/AupGHgEytH0/s1600-h/18.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_Qp19D1g5eY0/SIffxKbNAYI/AAAAAAAAABE/AupGHgEytH0/s400/18.gif" alt="" id="BLOGGER_PHOTO_ID_5226391928291787138" border="0" /></a><br /><br /><br />As you can see, the final result is almost the same as if i had used a table, but with this method i get to calculate the median on grouped rows - what a mission that was :(<br /><br />For those of you who need it, here is the code for accumulating the median values and calculating them. It's pretty sandard stuff, nothing too exciting.<br /><br /><span style="color: rgb(0, 204, 204);font-size:85%;" ><span style="font-family:courier new;">Dim Public Shared MedianArray(0) As Integer</span><br /><br /><span style="font-family:courier new;">Public Function ResetMedian()</span><br /><span style="font-family:courier new;"> ReDim MedianArray(0)</span><br /><span style="font-family:courier new;">End Function</span><br /><br /><span style="font-family:courier new;">Public Function AddToMedian(fieldValue As Integer) </span><br /><span style="font-family:courier new;"> Dim i As Integer</span><br /><span style="font-family:courier new;"> i = UBound(MedianArray) + 1</span><br /><span style="font-family:courier new;"> ReDim Preserve MedianArray(i)</span><br /><span style="font-family:courier new;"> MedianArray(i) = fieldValue</span><br /><span style="font-family:courier new;"> AddToMedian = fieldValue</span><br /><span style="font-family:courier new;">End Function</span><br /><br /><span style="font-family:courier new;">Public Function CalculateMedian() as String</span><br /><span style="font-family:courier new;"> Dim arraySize as Integer</span><br /><span style="font-family:courier new;"> Dim ii as Integer</span><br /><span style="font-family:courier new;"> Dim jj As Integer</span><br /><span style="font-family:courier new;"> Dim itemMoved As Boolean</span><br /><span style="font-family:courier new;"> Dim temp As Integer</span><br /><br /><br /><span style="font-family:courier new;"> 'sort it & calculate it</span><br /><span style="font-family:courier new;"> arraySize = UBound(MedianArray)</span><br /><span style="font-family:courier new;"> If arraySize = 1 Then</span><br /><span style="font-family:courier new;"> CalculateMedian = CStr( MedianArray(0) )</span><br /><span style="font-family:courier new;"> Exit Function</span><br /><span style="font-family:courier new;"> Else If arraySize > 1 Then</span><br /><span style="font-family:courier new;"> For ii = 0 To arraySize - 1</span><br /><span style="font-family:courier new;"> itemMoved = false</span><br /><span style="font-family:courier new;"> For jj = LBound(MedianArray) To UBound(MedianArray) - 1</span><br /><span style="font-family:courier new;"> If MedianArray(jj) > MedianArray(jj + 1)</span><br /><span style="font-family:courier new;"> temp = MedianArray(jj)</span><br /><span style="font-family:courier new;"> MedianArray(jj) = MedianArray(jj + 1)</span><br /><span style="font-family:courier new;"> MedianArray(jj + 1) = temp</span><br /><span style="font-family:courier new;"> itemMoved = True</span><br /><span style="font-family:courier new;"> End If</span><br /><span style="font-family:courier new;"> Next</span><br /><span style="font-family:courier new;"> If itemMoved = False Then Exit For</span><br /><span style="font-family:courier new;"> Next</span><br /><br /><span style="font-family:courier new;"> 'calculate it</span><br /><span style="font-family:courier new;"> If arraySize Mod 2 = 0 Then</span><br /><span style="font-family:courier new;"> 'average the two middle values</span><br /><span style="font-family:courier new;"> CalculateMedian = CStr( (MedianArray(arraySize / 2) + MedianArray((arraySize / 2) + 1)) / 2)</span><br /><span style="font-family:courier new;"> Else</span><br /><span style="font-family:courier new;"> 'get the middle value</span><br /><span style="font-family:courier new;"> CalculateMedian = CStr( MedianArray(Floor((arraySize / 2)) + 1) )</span><br /><span style="font-family:courier new;"> End If</span><br /><span style="font-family:courier new;"> End If</span><br /><span style="font-family:courier new;">End Function</span></span><br /><br /><br /><br /><br /><br /><br /><br /><br />keywords: SSRS, SSRS2005, median, row group, calculate, custom aggregateshanehttp://www.blogger.com/profile/07565638370666265398noreply@blogger.com6