Yen's Blog

Lens, Wheels, Skates, Keyboard

Wix: Executing Custom Action Before Starting Windows Service

I’ve been trying to get a Wix installer to work. This particular scenario is pretty simple. I want to configure the database via a custom action, then start a Windows service which then queries the database before starting. This would seem like a commonplace scenario. However, Wix documentations are sparse and I’ve been wrangling with this for some time. I finally found a solution. While I loathe to reference a StackOverflow answer, as a favor to my future self I’m going to do so anyways.

The following defines the component that installs and starts the service and creates a feature that references it.

1
2
3
4
5
6
7
8
9
10
11
<ComponentGroup Id='b_SyncSvcComps' Directory='b_SyncInstallDir'>
  <Component Id='b_SyncSvc'>
    <File Id='b_SyncExe' Name='MyService.exe' Source='$(var.syncSrcDir)\MyService.exe' DiskId='1' KeyPath='yes' />
    <ServiceInstall Id='b_InstallSyncSvc' Type='ownProcess' Name='MyService' DisplayName='My Service' Description='My Service' Start='auto' Account='[SERVICEACCOUNT]' Password='[SERVICEPASSWORD]' ErrorControl='normal' />
    <ServiceControl Id='b_StartSyncSvc' Start='install' Stop='both' Remove='uninstall' Name='MyService' Wait='yes' />
  </Component>
</ComponentGroup>

<Feature Id='b_ConnectedModeFeature' Title='Connected Mode Features' Level='1'>
  <ComponentGroupRef Id='b_SyncSvcComps' />
</Feature>

The following snippet in Product.wxs actually installs the feature:

1
2
3
<Feature Id='b_Features' Title='[ProductName]' Level='1'>
  <FeatureRef Id='b_ConnectedModeFeature' />
</Feature>

This is the initial version of the custom action that I want to run.

1
<CustomAction Id="DbBatchCmd" Directory='B_DBINSTALLDIR' Execute="immediate" Impersonate="yes" Return="check" ExeCommand="[SystemFolder]\cmd /c &quot;&quot;setup_database.cmd&quot; &quot;[b_WebServer]&quot; &quot;[b_DbServer]&quot;&quot;" />

The following snippet in Product.wxs runs the custom action. Here it is run after InstallFinalize, the last possible step in the installation’s sequence of events. The condition ensures that it is only run if the product isn’t already installed.

1
2
3
<InstallExecuteSequence>
  <Custom Action="DbBatchCmd" After="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>

According to the above, the installer tries to start the service before it runs the custom action to configure the database. Of course, since the service requires the database to be set up, it balks.

Among others, I tried running the custom action earlier using the Before="StartServices" and After="InstallFiles" attributes. The latter makes sense because the installer needs to copy files to the file system before it can execute the script. When inspected with Orca, the MSI has the correct InstallExecuteSequence:

...
InstallFiles              4000
DbBatchCmd  NOT Installed 4001
InstallServices VersionNT 5800
StartServices VersionNT   5900
...
InstallFinalize           6600

However, the installer never executes the custom action. It always tries to start the service and almost immediately fails. In fact, the custom action only runs when it’s set to After="InstallFinalize" as above.

One of the key things was provided by the helpful if somewhat verbose Windows installer log, which is created when you start the installer as follows:

msiexec /i myinstaller.msi /l*v myinstaller.log

Someday, Microsoft will have consistent command-line arguments. The log has this to say about the service:

MSI (s) (3C:A8) [17:10:02:607]: Note: 1: 2262 2: Error 3: -2147287038 
Info 1721.There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: DbBatchCmd, location: C:\inetpub\wwwroot\MyApp\Database\, command: C:\Windows\SysWOW64\\cmd /c ""setup_database.cmd" "my_webserver" "my_dbserver"" 
Action ended 17:10:02: ShipBatchCmd. Return value 1.

This is followed by entries for InstallServices and StartServices. So the installer does try to run the custom action but fails.

The answer is provided by Rob Mensching, who created Wix. According to him, After="InstallFiles" is correct. However, the execution needs to be “deferred” until the files are actually copied to the file system. Below is the corrected XML.

1
2
3
4
5
<CustomAction Id="DbBatchCmd" Directory='B_DBINSTALLDIR' Execute="deferred" ExeCommand="[SystemFolder]\cmd /c &quot;&quot;setup_database.cmd&quot; &quot;[b_WebServer]&quot; &quot;[b_DbServer]&quot;&quot;" />

<InstallExecuteSequence>
  <Custom Action="DbBatchCmd" After="InstallFiles">NOT Installed</Custom>
</InstallExecuteSequence>

Wix: Batch Files. Yes, Really.

Batch files are the last resort due to its Neanderthal abilities and exasperating syntax. But if I want to run a bunch of things on bare-boned Windows Server 2003, this seems like the quickest if not only option.

I tried something like this, by creating a property for the system executable cmd.exe, which would execute the batch script via the /c switch. Then I created a custom action that relies on the property. The batch script and its arguments must be enclosed in quotes. In addition, each argument should be enclosed in quotes in case it contains a space.

1
2
3
4
5
6
<Property Id="CMD">
  <DirectorySearch Id="SysDir" Path="[SystemFolder]" Depth="1">
    <FileSearch Id="CmdExe" Name="cmd.exe"  />
  </DirectorySearch>
</Property>
<CustomAction Id="BatchScript" Property="CMD" Execute="immediate" Impersonate="yes" Return="check" ExeCommand="/c &quot;&quot;[INSTALLDIR]\batch_script.cmd&quot; &quot;my arg1&quot; &quot;my arg2&quot;&quot;" />

Since I want to use property values (derived from user inputs) as arguments, the “beauty” of this (and I say so with a straight face) is simply the replacements of raw argument values with property references.

1
2
3
4
<Property Id="b_arg1">my arg1</Property>
<Property Id="b_arg2">my arg2</Property>

<CustomAction Id="BatchScript" Property="CMD" Execute="immediate" Impersonate="yes" Return="check" ExeCommand="/c &quot;&quot;[B_DBINSTALLDIR]\batch_script.cmd&quot; &quot;[b_arg1]&quot; &quot;[b_arg2]&quot;&quot;" />

Then I hit a snag. The batch script needs to call other batch scripts in the same directory. With this approach, I can’t set the working directory because can have either a Directory attribute or a Property attribute but not both. Here cmd.exe simply runs under the directory it defaults to, c:\windows\system32.

With a slight modification by including the entire command in the ExeCommand attribute, the custom action can use a working directory.

1
<CustomAction Id="BatchScript" Directory='INSTALLDIR' Execute="immediate" Impersonate="yes" Return="check" ExeCommand="[SystemFolder]\cmd /c &quot;&quot;batch_script.cmd&quot; &quot;[b_arg1]&quot; &quot;[b_arg2]&quot;&quot;" />

Here’s the initial version of the batch script. The database is configured via sqlcmd with the arguments which are passed to the script.

set arg1=%1
set arg2=%2
sqlcmd -E -d database_name -i "sql_script.sql" -v arg1="%arg1%" arg2="%arg2%"

However, some of the script arguments are already enclosed in quotes, which must be there because they may contain space. This means the sqlcmd arguments now have extra quotes. I suppose if I made sure all batch file arguments are enclosed in quotes, I wouldn’t need the quotes in the sqlcmd parameters list. Nevertheless, I decide to strip the quotes before forwarding the arguments to sqlcmd.

set arg1=%arg1:"=%
set arg2=%arg2:"=%

Bruins Training Camp 2013

Having moved to the ‘burbs recently, I look forward to attending the Bruins’ regular-season practice sessions at Ristuccia Arena in Wilmington, now that it’s only a few miles away. The 2013 Training Camp, though, was held at TD Garden the weekend of September 14-15. Of course I was going to be there.

The 2013 season was a bit of a roller coaster ride. It was thrilling at times, but ultimately disappointing. It was a hard pill to swallow watching the Blackhawks celebrate with the Cup on our ice. Then came the off-season changes. The departures of Tyler Seguin and Rich Perveley were certainly shocking, particularly Seguin who were expected to be the next Bruins superstar. Then came the arrival of Jarome Iginla, whom most Bruins fans including myself were gleefully cheering against especially during the Penguins series. Seeing Iginla wearing the spoked B would be a little weird, to say the least. Even the anticipated free-agency departures of popular Bruins Nathan Horton (to Columbus) and Andrew Ference (to Edmonton) were hard to take. I like Ference in particular, for his leadership qualities and environmental activism. It didn’t help matter that a very solid goalie and likeable player in Anton Khudobin also left, which was a surprise.

Having endured the summer of baseball and the start of football season, I am beyond thrill at the return of hockey. Judging from the turnout for the training camp, despite the early weekend hours, many Bruins fans were feeling the same way. Part of the eagerness must come from anticipating the new faces on the roster. But for the most part, I just wanted to spectate the most exciting team sports invented by man.

The Training Camp consisted of 2 groups. With 50-odd participants and only a few open roster spots, it was clear that most of the new guys were going to end up elsewhere, whether with the baby Bruins in Providence, their junior/college team or some place like Saskatoon. Nevertheless, it was a chance for the coaches to evaluate players or for fans like me to familiarize ourselves with our prospects.

I’m guessing the opening-day roster will look something like this:

Milan Lucic (17) – David Krejci (46) – Jarome Iginla (12)
Brad Marchand (63) – Patrice Bergeron (37) – Loui Eriksson (21)
Carl Soderberg (34) – Chris Kelly (23) – ???
Daniel Paille (20) – Gregory Campbell (11) – Shawn Thornton (22)

Zdeno Chara (33) – Johnny Boychuk (55)
Dennis Seidenberg (44) – Dougie Hamilton (27)
??? – Adam McQuaid (54)

Tuukka Rask (40)
???

On defense, Matt Bartkowski (43) and Torey Krug (47), both left-shots, are likely candidates. Both played last season, Krug having stood out in the Rangers series. Local guy David Warsofsky is probably also in consideration. The empty forward and goalie spots are wide-open. That’s why we’re here.

Seth Griffith misses an open-net chance on Niklas Svedberg.

Dennis Seidenberg patrols the blue line on his unusual t-blade.

Crowd around Svedberg.

Chris Kelly cirling the net.

New signee Matthew Lindblad trying to get a bead on Tuukka Rask.

Brad Marchand buzzes the net as usual.

Patrice Bergeron looks to be in full form after the serious injuries sustained in the Blackhawks series.

Newcomer Loui Eriksson (21) drives to the net, guarded by Dougie Hamilton (27).

Another look at Eriksson. He seemed to be an open-ice player who isn’t afraid of going to the crease. He’s bigger than Seguin and has good speed. The demise of our team speed with the departures of Seguin and Peverley is greatly exaggerated.

Jordan Caron (38) and Reilly Smith. Caron has been up and down with the big club. Of all the young guys he probably has the most NHL experience. Maybe he can stick around this time.

Chris Casto in full flight. Hockey players can look like they’re sprinting. Actually, it’s not quite true. They’re much faster than a sprinter.

Marchand trying to shake off Hamilton. That’s probably not a great gap for Hamilton, but you can’t stay too close to Marchand or he’ll make you look bad.

Mike Moore finds out the hard way where the goal post is.

Hamilton should probably be between his check and the net instead of chasing him but, meh, it’s practice. He’s huge, at 6’5 he’s second only to Chara.

This gutsy display of sportsmanship and determination after his leg was broken by an Evgeni Malkin slapshot made Gregory Campbell a Bruin forever. This isn’t Lionel Messi crying after he got tripped.

Matt Bartkowski sees something wince-worthy. On the right is Carl Soderberg.

Adam McQuaid with a rare smile. In the background Shawn Thornton juggles the puck on the blade of his stick. The tough guy has mad skillz.

Milan Lucic having a laugh with (or at?) Bartkowski.

I’m going with “laugh with”. Bart is such a jokester.

Chara having a chat with Nick Johnson (32) and Alexander Fallstrom (59). Those are not midgets—both are listed at 6’2. Chara is just a monster, and I mean that in an admiring way.

He can move too.

New “old” face Jarome Iginla. He potted 2 against the Habs two nights after this in a pre-season game in Montreal. Welcome to Boston, Iggy.

Lucic with a wicked snap shot.

The 3rd member of the projected top line and my favorite player. The bruising line of Lucic-Iginla-Krejci is going to give defensemen nightmares. They have great hands, decent speed, hit like a train and are impossible to contain. They bring back fond memories of the so-called 700 Pound Line of Joe Thornton, Mike Knuble and Glen Murray.

Torey Krug with Mark Messier’s patented snap shot off the wrong leg. Which, by the way, everyone is using these days.

Fallstrom with a good flex of the stick.

A first look at Malcolm Subban. You know, the brother of Boston’s public enemy #1 PK Subban. He did say he never liked his brother anyways. The kid is all right.

Kevan Miller who hails from, get this, Los Angeles. That’s right, a California kid playing pro hockey.

Close-up of Daniel Paille.

Fun With CSS Sibling Selectors

View the live example

It seems like I’m always working on search boxes, but here goes another. My goal here is to display an icon inside the search box. Initially, the search box has a dark background. When clicked, the background changes to white. The icon should changes its color based on whether or not the search box has focus. If we were using a font-based icon such as Fontawesome, which is awesome by the way, styling the icon would be a simple exercise. However, we’re using an image-based icon, so it’s not possible to change its color.

One solution is to use 2 icons, dark and light. We swap them depending on whether the input has focus. If the input is inactive, we show the light icon and hide the dark icon. If the input is focused, we do the opposite.

To access the icon in the context of the input, we use the CSS sibling selector ~. Unlike the other CSS sibling selector, +, the ~ selector doesn’t require that the second element be immediately adjacent to the first. This is important since we have 2 icons, the second of which wouldn’t be adjacent to the input. We also use the not() selector to select the element that lacks the specified class.

1
2
3
4
5
6
7
8
9
10
11
12
.navbar-search-wrapper input ~ .icon-search.icon-white {
  display: block;
}
.navbar-search-wrapper input:focus ~ .icon-search.icon-white {
  display: none;
}
.navbar-search-wrapper input ~ .icon-search:not(.icon-white) {
  display: none;
}
.navbar-search-wrapper input:focus ~ .icon-search:not(.icon-white) {
  display: block;
}

Search Box Using KnockoutJS

View the live example

We’re gradually gravitating toward AngularJS because it’s more powerful and flexible. However with AngularJS we have some issues with IE7, which unfortunately we’re stuck supporting. So when I want to add some simple enhancements to the search box, I went back to KnockoutJS. For a simple scenario such as this, KnockoutJS works great. If we had to do this using plain jQuery, we’d have to juggle keypress, focus and blur events which is just unnecessary drudgery!

The enhanced search text box should display a prompt; since the HTML5 “placeholder” property isn’t supported in IE9 and below, we’ll need to use JavaScript. When focused it should expand and show a cursor. It should submit when the user hits Return, but only if the user enters (or copies and pastes) 2 or more characters into the text box. When the user starts typing, it should display an icon to clear the text box.

The markup is as follows:

1
2
3
4
5
6
<form id="site-search" action="/Search" data-bind="css: { active: isActive() }, submit: submit">
  <label class="search-magnify" data-bind="click: edit"><i class="icon-search"></i></label>
  <div class="search-overlay" data-bind="click: edit, visible: !isActive()">Search site...</div>
  <input class="search-input" name="searchText" autocomplete="off" type="text" data-bind="hasfocus: editing, value: text, valueUpdate: ['afterkeydown','propertychange','input']" />
  <label class="search-reset" data-bind="click: clear, visible: hasText()"><i class="icon-remove-sign"></i></label>
</form>

To show the prompt, I’m using an overlay container. It’s not very elegant, but I want to avoid changing the content of the textbox. With an MVC framework like KnockoutJS, the behavioral directives are wired into the markup via the data binding syntax; you don’t have to peruse a separate JavaScript file. Even if you don’t know the syntax, it’s almost as understandable as plain English. So clicking the magnifying glass or the overlay puts the search box in “edit” mode; clicking on the text also activates “edit” mode by giving it focus via the hasfocus binding. The Reset/Clear icon is visible only when there is some text. When the search box is “active”, the form has a special CSS class, “active”, and the overlay becomes invisible.

The valueUpdate binding ensures that KnockoutJS updates the text box’s value when the user types on the keyboard or pastes with the mouse.

The stylesheet (in SASS which is easier to read) is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#site-search {
  background-color: $bodyBackground;
  border-style: solid;
  border-width: 1px;
  border-color: $filterBorderShadow $filterBorder $filterBorderHighlight $filterBorder;
  border-radius: 12px;
  color: $grayTextColor;
  margin: 3px 0 0 0;
  padding: 0;
  position: relative;
  label,
  input,
  div {
    display: inline-block;
    @include ie7-inline-block();
  }
  .search-input {
    @include border-radius(0);
    @include box-shadow(none);
    @include transition(width 0.8s);
    background-color: transparent;
    border-style: none;
    color: $grayTextColor;
    line-height: 100%;
    margin: 0;
    outline: none;
    padding: 5px 25px;
    height: 100%;
    width: 100px;
    z-index: 1;
    &amp;:focus,
    &amp;:active {
      @include box-shadow(none);
    }
  }
  .search-overlay {
    font-style: italic;
    height: 100%;
    position: absolute;
    left: 25px;
    top: 3px;
    z-index: 0;
  }
  .search-magnify,
  .search-reset {
    cursor: default;
    position: absolute;
    top: 3px;
  }
  .search-magnify {
    left: 5px;
  }
  .search-reset {
    display: none;
    right: 5px;
    &amp;:hover {
      color: $linkColor;
    }
  }
}
#site-search.active {
  background-color: $white;
  color: $textColor;
  .search-input {
    color: $textColor;
    width: 150px;
  }
  .search-reset {
    display: inline-block;
    @include ie7-inline-block();
  }
}

Of note is when the form has the “active” CSS class, we change the style and width of the text box and also set the CSS3 “transition” property which results a nice animation (which of course won’t show up in IE9 or lower but that’s ok).

Finally the model which makes everything tick is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function SiteSearchViewModel() {
  var self = this;
  self.editing = ko.observable(false);
  self.edit = function() {
    self.editing(true);
  };
  self.isActive = function () {
    return (self.editing() || self.hasText());
  };

  self.text = ko.observable('');
  self.clear = function () {
    self.text('');
    self.editing(true);
  };
  self.hasText = ko.computed(function () {
    return $.trim(self.text()).length > 0;
  });

  self.submit = function () {
    return $.trim(self.text()).length >= 2;
  };
}

$(document).ready(function () {
  ko.applyBindings(new SiteSearchViewModel());
});

The search box is defined to be “active” when the text box has focus or when it contains text. The latter condition accounts for the scenario in which the user types something then clicks away; the search box should stay expanded instead of collapsed.

The submit() handler ensures that the user enters at least 2 non-white space characters.

Animate Textbox Using KnockoutJS

View the live example

KnockoutJS is a dynamic UI framework that can really liberate you from the tedium of low-level DOM manipulations. Even though I’ve only played with it for a little while, I’ve become a huge fan. One of my initial attempts is to create an animated text input similar to Twitter’s.

The UI is quite simple. The textbox should display a “placeholder” prompt. When the user clicks inside the textbox, expand it and display a save button, initially disabled. If the user types something (other than white space), enable the button. If the textbox loses focus, shrink it to its original size, unless it contains a valid comment.

To show the placeholder prompt, I could use the HTML5 “placeholder” attribute. Unfortunately this isn’t supported by all browsers (what else but IE 9 and below). So I’m using a div to wrap the textbox and display the prompt as an overlay.

The view model’s isEditing boolean observable reflects whether the textbox has focus. It’s wired to the hasfocus binding on the textbox. Additionally, clicking anywhere on the wrapper div should activate the observable, since the user should be able to focus by clicking on the overlaying prompt.

The content of the textbox is tied to the newComment observable. Per the valueUpdate binding, it is updated after a key press. Additionally, the propertychange and input parameters allow it to be updated when the user copies and pastes with the mouse or keyboard.

To animate the textbox, I use a custom binding which is also wired to the isEditing observable. The animation should be performed only when the textbox is empty or contains only white space. When the textbox has focus, it’s expanded. When it loses focus, it’s collapsed.

The button is shown when the textbox has focus. Additionally it’s only enabled when the user enters non-white space text. Clicking on the button simply adds adds the new comment to the comments observable array and clears the textbox. The beauty is that KnockoutJS automatically updates the UI to reflect the change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<span class="input-overlay" data-bind="click: isEditing">
  <div class="caption">Write something...</div>
  <textarea id="#newComment" name="newComment" cols="20" rows="2" data-bind="value: newComment, valueUpdate: ['afterkeydown','propertychange','input'], hasfocus: isEditing, animateTextbox: isEditing"></textarea>
</span>
<button data-bind="click: save, enable: hasNewComment, visible: shouldShowSave">Comment</button>
<div class="alert alert-info" data-bind="visible: comments().length == 0">No comments found</div>
<table data-bind="visible: comments().length > 0">
  <thead>
    <tr>
      <th>Date</th>
      <th>User</th>
      <th>Comment</th>
    </tr>
  </thead>
  <tbody data-bind="foreach: comments">
    <tr>
      <td data-bind="text: date"></td>
      <td data-bind="text: user"></td>
      <td data-bind="text: text"></td>
    </tr>
  </tbody>
</table>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function Comment(data) {
  this.date = ko.observable(data.date);
  this.user = ko.observable(data.user);
  this.text = ko.observable(data.text);
}

function CommentViewModel(data) {
  var self = this;
  self.comments = ko.observableArray(data);
  self.isEditing = ko.observable(false);
  self.newComment = ko.observable();
  self.hasNewComment = ko.computed(function () {
    return $.trim(self.newComment()).length > 0;
  });
  self.shouldShowSave = ko.computed(function () {
    return self.isEditing() || self.hasNewComment();
  });
  self.save = function () {
      self.comments.unshift(new Comment({
           date: (new Date()).toLocaleString(),
           user: 'Me',
           text: self.newComment() })
      );
      self.newComment(null);
  };
}

ko.bindingHandlers.animateTextbox = {
  init: function (element, valueAccessor) {
    ko.bindingHandlers.animateTextbox.animate(element, valueAccessor);
  },
  update: function (element, valueAccessor) {
    ko.bindingHandlers.animateTextbox.animate(element, valueAccessor);
  },
  animate: function (elem, valueAccessor) {
    var hasFocus = ko.utils.unwrapObservable(valueAccessor());
    var hasNewComment = $.trim($(elem).val()).length > 0;
    if (!hasNewComment) {
      $(elem)
          .animate({ height: hasFocus ? '100px' : '20px' }, 500)
          .closest('.input-overlay').toggleClass('off', hasFocus);
    }
  }
};

$(document).ready(function() {
  ko.applyBindings(new CommentViewModel([]));
});

Intermittent, Temporary Freezing on MacBook With OCZ Vertex 3

I recently upgraded the hard drive on my MacBook Pro running Snow Leopard to a 120GB OCZ Vertex 3 SSD. As advertised, the boot up and application start times are dramatically reduced. The SSD is everything I had hoped for. Well, almost. There has been a persistent and quite annoying issue. At random intervals, the computer would be come unresponsive. I could move the mouse or type on the keyboard, but nothing woud happen. When I hovered over the menu bar, I got the spinning “beachball”. The lag would last anywhere from 5-10 seconds. Then all of a sudden all the actions I had performed in the interim would be executed in rapid succession, as if the computer had been jerked awake. This was most noticeable during a streaming video, which would pause when one of these episodes hit. The problem made the computer borderline unusable.

I’m happy to report that a firmware update for the Vertex 3 appeared to solve this problem. I had firmware version 2.15, while the most current version was 2.25. To upgrade the firmware, I followed the instructions for using the OCZ Bootable Toolbox on the OCZ forum. I had to use the PC instructions, since the Mac instructions are not applicable to my pre-2009 MacBook. For me, the steps were to install the custom ISO disk image on a USB drive, then boot the PC from the USB drive. The Vertex 3 had to be connected to an internal SATA port, as the OCZ Toolbox was not able to detect it if mounted in a USB enclosure. This required me to open up the PC, a minor inconvenience. The PC should also have an Internet connection. Overall, the process was painless and took under 5 minutes.

The MacBook has been humming along so far without a hiccup.

You Played With Who?

Since 1999 I’ve helped run a pickup hockey group at the Skating Club of Boston. We’re really not crazy about the rink there. The ice is intended for figure skaters so it’s soft and thus slow and quick to become choppy. There is no glass above the boards so if you shoot high enough you’d have to go fish for the puck under the stands. And since figure skaters generally stay in the middle of the ice, the edges are neglected and not well maintained; this is a problem if you try to “wrap the puck around the board”. Nevertheless, the location is great and we got to know the rink manager there quite well. He gives us extra ice time and at the end of the year we try to return the favor by collecting to give him a nice X-mas check.

Our group started off as a bunch of friends who played intramural hockey at MIT together. Over the years, people have come and gone, but I’m the only constant. Since I still put the roster together every week, year round, I’ve gotten to know a bit about the people who have played with us. I guess I could say I’m a sort of unofficial historian. Because of our academic origin, we’ve always had a strong contingent of people with advanced degrees. We’ve had several PhDs, MDs and lawyers. A few are current professors at local universities. But probably thanks to hockey’s grassroots appeal, I’m amazed at the diversity we’ve had. We’ve had cops, EMTs, firefighters, architects, engineers, scientists, pilots, artists, designers, writers, tech entrepreneurs, a maître d’hôtel, a restauranteur, a couple of finance guys, a bunch of software guys. Indie singers and DJs have leaped over the boards. Which is somewhat surprising because we obviously have no marketing department. It’s probably just word of mouth or pure serendipity that some of these guys end up playing with us. Over the years, there are some note-worthy names who have laced up the skates with us. So far as I remember here are some.

  • The 1999 World Junior Ice Dancing champion
  • A Boston Police superintendent and head of the Bureau of Investigative Services
  • The platform director at Twitter
  • The founder of Colorkinetics (now Philips)
  • The former CEO of Akamai
  • The current general manager of the Red Sox. He’s a quiet, good player who liked to be on defense. One of the complaints I remembered was that he didn’t get off the ice very much.
  • The bassist for the punk band NOFX. He just showed up at the rink with a hockey bag. The other guys told him to wait to check with me whether he could play (we don’t want too many skaters). We had room so he stayed. Turned out he was in town for a concert and, being a hockey enthusiast and player, he brought his gears along. Apparently he does this on road trips. Not all of us have heard of NOFX, but those who did were quite excited when they found out. He was only an average player, but does possess first-class enthusiasm.

Fix External Lockout for American Standard Freedom 90 Furnace

Some of my blogs are of the “I’d better write this down so I don’t forget it” variety. This is one of them. This is intended primarily for future me, but it would be cool if anyone else finds it useful.

The equipment in question is an American Standard (Trane) Freedom 90 gas furnace that’s about 7 years old. Recently, in fact right in the middle of a record-setting snowstorm, it started blowing only cold air.

I followed the instructions in the manual to reset it (off-on-off-on in less than 30 seconds), to no avail. The burners would get ignited, then seemed to flame out. The control board then tried to restart the process. This would go on 5 times or so. After that the blower fan would be going, but only ice-cold air was coming out. The red light on the control board flashed twice in roughly 3-second intervals. According to the manual, this indicated an “External Lockout (retries or recycles exceeded)” error.

Some online forums (google “external lockout”) implicated the flame sensor. Apparently it needs to be cleaned about once a year. I’ve never seen it much less touched it, so it’s highly suspect. Before working inside the furnace, it goes without saying that you must first turn down the thermostat and shut off power to the furnace. To get to it, remove the two panels on the front of the furnace starting with the bottom panel.

The burner unit is in an enclosed box at the top of the cabinet. There’s a round viewport on the front bezel where you can observe the burners in action (provided they are running).

DSC_5005

Remove the front door with an 8mm wrench. It hinges at the top and can be tricky to remove.

DSC_4999

DSC_4996

Locate the flame sensor.

DSC_4995

Remove it with a socket screwdriver.

DSC_4993

Thoroughly clean the sensor’s rod and contact with sandpaper. I’d use 150-grit as well as a finer sandpaper.

Replace the flame sensor. To test it out, you should replace just the burner door and the bottom panel of the cabinet; there’s a door switch that prevents operation unless the panel is in place (or it is manually depressed). You can view the burners through the viewport. Turn on the power and turn up the thermostat to call for heat.

In my case, the first attempt wasn’t successful. Before calling for service, I decided clean the flame sensor again even more thoroughly. There was some rust on the contact that I made sure to remove with a fine-grit sandpaper. This must have done the trick.

Note to self: Clean the air filter while you’re at it

Mini Project: Adding a Second Hard Drive to the Mac Mini

I want to replace the DVR that I get from Verizon Fios. Besides the monthly cost, it’s an older unit that’s slow and has the capacity for no more than a handful 3-hour hockey games recorded in HD. That is plainly unacceptable, especially during the Stanley Cup Playoffs. For the HD tuner, I decided to go with the SiliconDust HD Homerun Prime. For the DVR, I’m going with a Mac Mini. I’m a recent Mac convert, having used exclusively PCs until now. Even though I enjoy assembling PCs, I’m at a point where I’d like stuff to just work. Even more so, I’m more and more a fan of the elegance and simplicity of OS X.

The Mac Mini is like a work of art. The model I have, however, has a drawback. While it is a level above the base model and comes with an Intel Core i7 processor, it has a rather sluggish 5400rpm hard drive. Since I plan on using it to watch live TV and record shows, I need more performance.

The server edition of the Mac Mini comes in the same case, but has 2 internal hard drives. It turns out that all Mac Minis (2012) have 2 hard drive slots. For my Mac Mini, the second slot sits empty. I want to add a Solid State Drive (SSD). The SSD will host OS X and the applications, while the other hard drive will host media. Getting to it in the cramped, tightly packed innards of the Mini seems like a daunting task. Fortunately OWC comes to the rescue by providing a prepackaged solution.

The OWC hard drive upgrade kit for the Mini (2011-2012) comes with a flex cable to connect the hard drive, screws and tools. The cost for the kit is only a few dollars more than what you’d have to spend to buy the flex cable separately.

I also got an SSD from OWC. OWC’s in-house brand, Mercury Electra, is highly rated and assembled in the USA. I always look for something that’s at least partially made in the USA whenever that’s an option, even if it costs more.

DSC_4942 DSC_4948 DSC_4951

The online video instructions are quite excellent. Even though the installation is rated as highly difficult, it’s actually quite straight-forward if you’ve ever assembled computers before. All that’s required are some nimble fingers to navigate the Mini’s delicate insides. Here’s the Mini without its parts. It seems to have been carved out of a solid block of aluminum!

DSC_4945

The SSD fits snugly into a plastic cage. It would sit below the stock hard drive.

DSC_4954

With the power supply bar slid into the right side of the case.

DSC_4955

This is the logic board assembly. Isn’t it adorable?

DSC_4956

The logic board slides into place through an opening in the back.

DSC_4960

Now the old hard drive goes on top. The two tabs to its lower left are the SATA connection tabs. The old hard drive has two small plugs that inserts into two slots on the front of the case (top of picture). The AirPort antenna grate in turn screws into the drive. You can’t see the slots when inserting the drive back into the case. I didn’t seat the drive properly in my first attempt and couldn’t attach the antenna because the drive was misaligned. Turns out the slots are slightly elevated. I had to raise the hard drive slightly when inserting it.

DSC_4962

The AirPort antenna grate goes over the top. The instructions plead with you not to overtighten the screws.

DSC_4964

Finally the fan and the DIMMs.

DSC_4965

After connecting the Mini to an Ethernet switch and booting into Recovery mode (by holding down Command-R when booting), I selected Internet Recovery and let it install OS X Mountain Lion over the wire. Minutes later, I was done. Did I mention I love Macs?