Rants, rambles, news and notes from another geek

Rupture First Look: Interesting, but Lots of Issues...

Rupture is a new social network (guild management?) system by Shawn Fanning of Napster-fame. Nice website. Good concept. But at this point I’m not pleased.

Why? They need to hire a better .NET developer.

I run on Vista. I run w/ User Account Controls enabled. I am an administrator, but with UAC an app that doesn’t indicate it needs elevated privileges doesn’t get it and can’t do all kinds of things like write to protected directories and registry keys.

As expected, the installer for the Rupture client prompted me to elevate privileges (yes, it uses a client to manage your data sync-up with the website). So far so good. And since the installer launched the app at its conclusion (bad #1) the app came up with full admin privileges. Well, I’ve gotten used to this kind of thing, so I immediately shut it down and restarted it.

BLAMMO!

App crashed at startup. Right there at the very beginning of the app they are doing something that requires elevation. I’m guessing they incorrectly opened the World of Warcraft registry key (which is located in HKLM–a protected area) with write permissions (bad #2), but without firing up regmon to see if I’m right, I can’t really be sure. I might check that later, but for now I decided to try letting it come up as admin and see what it does.

Now, one of my favorite things about the way WoW is coded is that it is XCOPY deployable. As I’ve said before I like to repave my computer 5 or&nbsp_place_holder;6 times a year (don’t ask) and reinstalling from DVD isn’t going to happen. So I just back it up to my external hard drive and restore it. I also don’t keep it in my C:\Program Files directory, because WoW also doesn’t behave as a good non-admin app should and insists on writing to its install folder. I keep my WoW installed in C:\World of Warcraft and open up the permissions there to let the auto-updater take care of itself.

But since I don’t install WoW from DVD, I don’t get the registry keys I mentioned before. Unless I add them manually, I just don’t have them at all. (Now, truth be told, I do often have it because I like to use addon updaters like WAU and my PowerShell script that use this key to find the addons folder, but today I didn’t happen to have that key in place).

So… the Rupture client keeps claiming it can’t find any of my characters. Even after I made sure the addon was installed, launched WoW, logged into one of my characters, etc., as described on the Rupture site. Still nothing. Why? Because it doesn’t know where to look. Fine. You know what? There is no way in the settings for me to configure it (bad #3). Nothing. Grrr…

Fine. I’ll add the key.

[HKEY_LOCAL_MACHINE\SOFTWARE\Blizzard Entertainment\World of Warcraft]
“InstallPath”=”C:\World of Warcraft”

Looks alright to me, eh? Fire up Rupture again (elevated of course). Still no character data!! Thankfully I accidentally discovered a “Show Logs” menu item on the systray icon and there I discovered why. Here’s the log:

0] : Attempting to load game plugins... 1] : Plugin for loaded. 2] : Loaded 1 plugins successfully. 3] : Analyzing files: Source[Plugins\World of Warcraft\Addon\] <> Dest[C:\World of WarcraftInterface\Addons\Rupture\] 4] : Comparing: [C:\World of WarcraftInterface\Addons\Rupture\Rupture.lua: XM+8lK66I2FZOlGSV35qCLEDYjc=] [Plugins\World of Warcraft\Addon\Rupture.lua: XM+8lK66I2FZOlGSV35qCLEDYjc=] 5] : Comparing: [C:\World of WarcraftInterface\Addons\Rupture\Rupture.toc: qqrZ5X+KXIEZEakyxUqkUB2/ufc=] [Plugins\World of Warcraft\Addon\Rupture.toc: qqrZ5X+KXIEZEakyxUqkUB2/ufc=] 6] : Rupture v1.0.2617.29345 successfully loaded. 7] : CLIENT WANTS TO START UP A NEW THREAD.. THREADCOUNT IS NOW 1 8] : WE ARE UNDER THE LIMIT.. STARTING NEXT THREAD... 9] : Analysis complete: no outdated files detected. 10] : CLIENT WANTS TO START UP A NEW THREAD.. THREADCOUNT IS NOW 1 11] : WE ARE UNDER THE LIMIT.. STARTING NEXT THREAD... 12] : Thread finished.. now at queue count: 0 13] : Thread finished.. now at queue count: 0

Do you see it? Look at like #4. There is a path separator missing between the registry key value (yes I forgot to terminate the path with a backslash) and the “Interface\Addons” part. This tells me that they are doing string concatenation to create that path instead of the build in System.IO.Path.Combine() function in .NET (bad #4). Fine. I can fix that too by adding the trailing backslash to my registry key, but at this point if I weren’t a geeky .NET WoW junkie I would have completely given up.

Finally after all of that, I was able to get into the client.

What I found seemed to be nothing more than a chat client. I didn’t see any guild management stuff yet, but maybe that is coming. And I certainly noticed a perf hit on my computer, so I’m guessing they are still working through some issues there.

At this point my conclusion has to be that it looks interesting but they have a ways to go before I would consider running this on my system day by day. And honestly, I don’t need another chat system. I already have too many.

I’ll give it another month or so and try it again. Maybe Shawn or someone else will read this and fix some of this stuff.

World of Warcraft Addon Updater in PowerShell

UPDATE: I am now maintaining this script over on my Warcraft Wiki site on pbwiki. Please go there for documentation and the most recent updates.

I couldn’t resist. Sometimes I don’t even know why I do these things. Last night I decided to start playing with System.Net.WebClient from Windows PowerShell and four hours later I had something close. A few more hours today and now I have a PowerShell script that:

  • Updates Subversion working copies (can be disabled w/ a switch)
  • Can be configured to check for updates from wowinterface.com using a simple data file in your addons folder called addons.ps1 (see the comment header below for a sample of this file) and a nice XML endpoint provided by the guys at wowi (thanks Dolby!)
  • Will check to see if an addon is one of the WowAce family of addons and will try to update it from their using their RSS feed for information.

I know I can add more addon sites to this later, but now that Auctioneer is up on wowi (ID: 4812) I don’t know if I have any addons that I use that can’t be updated by this script. Perhaps one or two, but that isn’t bad given that I have 169 addons in my Addons directory.

This script has a few dependencies for extracting ZIP and RAR files. You will need to get unzip.exe from Info-ZIP and UnRar.exe from RARLabs&nbsp_place_holder;and put them both on your path. Both are free.

Anyway, here is the script. There is a download at the bottom of this post for those of you who just want to have it.

DISCLAIMER: This script is supplied without warranty or support. If you play WoW and don’t know anything about PowerShell, this might be a bit much for you. Take a look at WowAceUpdater or WUU instead.

  1 #########################################################################
  2 # Name: update-addons.ps1
  3 # Version: 1.0
  4 # Author: Peter Provost <peter@provost.org>
  5 #
  6 # Usage: update-addons [-skipSvn]
  7 #
  8 # Remarks: This is a simple powershell script for updating your
  9 #   World of Warcraft addons. It will autodetect SVN working copies
 10 #   and update them. It will look for a file called addons.ps1
 11 #   in your Addons folder to define special locations for downloading
 12 #   addons (see below for a sample addons.ps1). After that it will test
 13 #   if the addon can be updated from [http://files.wowace.com](http://files.wowace.com). Any addon
 14 #   still unmatched will be skipped.
 15 #
 16 #   This sample addons.ps1 hows how to configure updates from 
 17 #   wowinterface.com:
 18 #
 19 #   $wowiAddons = @{
 20 #     'FlightMap' = 3897;
 21 #     'Clique' = 5108;
 22 #   };
 23 #
 24 #   At this point only wowinterface.com is supported in this data file
 25 #   but more may be added later.
 26 #
 27 #########################################################################
 28 
 29 **param** (
 30     **[switch]** $skipSvn
 31 );
 32 
 33 # Configuration - change these as needed
 34 $wowAddonDir = "C:\World of Warcraft\Interface\Addons";
 35 $stateFile = "PSUpdateAddons.state";
 36 
 37 **function** update-addon($addonSource, $downloadUrl, $remoteVersion, $fileName)
 38 {
 39     **if** ( $remoteVersion -**ne** $localVer) {
 40       **write-host** "$_ : ($addonSource) Update required: Current ver=$localVer, Remote ver=$remoteVersion";
 41 
 42       $tempFilePath = **join-path** $tempDir $fileName;
 43       **downloadextract-addon** $downloadUrl $tempFilePath;
 44 
 45       **write-host** "`tUpdating state file..." -noNewLine;
 46       $remoteVersion > $stateFilePath;
 47       "done." | **write-host**;
 48     } **else** {
 49       **write-host** "$_ : ($addonSource) Addon up-to-date. Skipping.";
 50     }
 51 }
 52 
 53 # Helper function for updating a single addon]
 54 **function** downloadextract-addon (**[string]** $uri, **[string]** $tempFile)
 55 {
 56   **write-host** "`tDownloading $uri to $($tempFile)..." -noNewLine;
 57   $wc.DownloadFile( $uri, $tempFile );
 58   **write-host** "done.";
 59 
 60   $ext = **[System.IO.Path]**::GetExtension($tempFile);
 61   **switch** ($ext) {
 62     ".rar" {
 63       **write-host** "`tExtracting RAR Archive..." -noNewLine;
 64       & unrar x -o+ $tempFile $wowAddonDir;
 65     }
 66     ".zip" {
 67       **write-host** "`tExtracting ZIP Archive..." -noNewLine;
 68       & unzip -o $tempFile -d $wowAddonDir;
 69     }
 70     **default** { **write-host** "UNKNOWN EXTENSION TYPE!" }
 71   }
 72   **write-host** "done.";
 73 
 74   **write-host** "`tDeleting zip file..." -noNewLine;
 75   **remove-item** $tempFile;
 76   **write-host** "done.";
 77 }
 78 
 79 **function** test-wowaceaddon( **[string]** $addonName )
 80 {
 81   **return** ((**get-wowaceaddon** $addonName)-**ne** $null)
 82 }
 83 
 84 **function** get-wowaceaddon( **[string]** $addonName )
 85 {
 86   $xpath = ("//item[title='ADDON']" -**replace** 'ADDON', $_.Name);
 87   **return** $indexXmlDoc.SelectSingleNode($xpath);
 88 }
 89 
 90 # Setup a few things before we get started
 91 $wc = **new-object** System.Net.WebClient;
 92 $tempDir = **join-path** (**get-content** env:\temp) "PsWowUpdater";
 93 **if** (-**not** (**test-path** $tempDir)) { **new-item** -type directory -path $tempDir; }
 94 
 95 # Load in the WowAce Index file
 96 **write-host** "Downloading latest.xml from [http://files.wowace.com](http://files.wowace.com)";
 97 $uri = "[http://files.wowace.com/latest.xml](http://files.wowace.com/latest.xml)";
 98 $rssData = $wc.DownloadString($uri);
 99 $indexXmlDoc = **[xml]** $rssData;
100 
101 # Load in the WOWI config database
102 . (**join-path** $wowAddonDir "addons.ps1")
103 
104 (**get-childitem** $wowAddonDir | ? { $_.PSIsContainer }) | % {
105 ## SVN UPDATE
106   **if** (**join-path** $_.Fullname ".svn" | **test-path** ) {
107     **if** ($skipSvn.isPresent) {
108       **write-host** "$_ : Skipping SVN working copy";
109     } **else** {
110       **write-host** "$_ : Updating SVN working copy";
111       svn up -q $_.Fullname;
112     }
113   }
114 
115 ## WOWINTERFACE.COM
116   **elseif** ($wowiAddons.Contains($_.Name)) {
117     $stateFilePath = **join-path** $_.Fullname "PSUpdateAddons.state";
118     $localVer = "";
119     **if** (**test-path** $stateFilePath) { $localVer = (**get-content** $stateFilePath); }
120 
121     $uri = ("[http://www.wowinterface.com/patcherXXXX.xml](http://www.wowinterface.com/patcherXXXX.xml)" -**replace** "XXXX", $wowiAddons[$_.Name]);
122     $wowiXml = **[xml]** $wc.DownloadString($uri);
123 
124     $downloadUrl = $wowiXml.UpdateUI.Current.UIFileURL;
125     $remoteVersion = $wowiXml.UpdateUI.Current.UIVersion;
126     $fileName = $wowiXml.UpdateUI.Current.UIFile;
127     $addonSource = "WowInterface.com";
128 
129     **update-addon** $addonSource $downloadUrl $remoteVersion $fileName
130   }
131 
132 ## WOWACE FILES
133   **elseif** (**test-wowaceaddon** $_.Name) {
134     $stateFilePath = **join-path** $_.Fullname "PSUpdateAddons.state";
135     $localVer = "";
136     **if** (**test-path** $stateFilePath) { $localVer = (**get-content** $stateFilePath); }
137 
138     $elt = **get-wowaceaddon** $_.Name;
139 
140     $downloadUrl = $elt.enclosure.url;
141     $remoteVersion = $elt.version;
142     $fileName = $downloadUrl.Substring($downloadUrl.LastIndexOf("/")+1);
143     $addonSource = "WowAce.com";
144 
145     **update-addon** $addonSource $downloadUrl $remoteVersion $fileName
146   }
147 
148 ## Unknown addon source
149   **else** {
150     **write-host** "$_ : Can't figure this one out. Skipping.";
151   }
152 }

Download update-addons.zip (2KB). And here is my addons.ps1 data file to update my wowi addons. Create a file like that in your Addons folder and the script will find it.

Shifting Powershell Arrays

In one of my recent posts, I showed a newer version of my ‘sudo for Powershell’ that was all writted in Powershell and left an open question about how to collect the variable arguments at the end.

Wes Haggard again comes through with the answer and points me to a post on the Powershell Blog called PowerShell Tip: How to “shift” arraysΓǪ

Using that tip, Wes gave me back a much tighter version of the elevation function:

function elevate
{
$file, [string]$arguments = $args;
$psi = new-object System.Diagnostics.ProcessStartInfo $file;
$psi.Arguments = $arguments;
$psi.Verb = "runas";
[System.Diagnostics.Process]::Start($psi);
}

Killer! Thanks again Wes!

Hack: How to Find the Name of a WoW Frame

Have you ever suddenly gone into World of Warcraft and found yourself wondering, “what the hell is that button?” It happened to me a while back after a patch day update and I couldn’t figure out which addon had put it there. It really started pissing me off.

I jumped over to the #wowace IRC channel on Freenode.net and asked if anyone had any ideas. They didn’t, but they did have a cool trick for figuring out the Frame’s name. Assuming you have any one of the Ace addons installed on your system, you can just use this slash command in game:

/print GetMouseFocus():GetName()

That will tell you the name of the frame which might help you figure out what addon created it. (It sure helped me.)

Enjoy!

Powershell Sudo (Sort of) for Vista UAC -- REDUX

Okay, after the email from Wes this morning I spent some of the time on the plane today re-doing my UAC privilege elevation stuff… this time in pure Powershell script.

As expected, it is quite simple:

function elevate {
$file = $args[0]; $param = "";
for($i=1;$i -lt $args.Length; $i++) { $param += $args[$i] + " "; }
$psi = new-object "System.Diagnostics.ProcessStartInfo"
$psi.FileName = $file; $psi.Arguments = $param; $psi.Verb = "runas";
[System.Diagnostics.Process]::Start($psi)
}

Now if only I could figure out how to have something like the Ruby “…” catch-all parameter, I could get rid of all that for-loop crap.

UPDATE 2007-02-26: I just posted a newer version of the elevate function that doesn’t have the for-loop. Use that one instead.

Powershell Sudo (Sort of) for Vista UAC

**UPDATE 2007-02-25 0815am:
**
Wes Haggard sent me an email this morning that caused me to “slap myself upside the head”:

Why got through this trouble when you can just user Process.Start?

PS> $psi = new-object System.Diagnostics.ProcessStartInfo “notepad.exe”
PS> $psi.Verb = “runas”
PS> [System.Diagnostics.Process]::Start($psi)

_Oops. My bad. Hahaha… can’t believe I didn’t try that. :)
_

I’ve been running with a Least-Privileged User (aka LUA) account for years on Windows XP. I was quite excited to get onto Vista w/ its UAC stuff built-in.

But then I discovered that as so often happens, they forgot those of us who like to use command shells. Now, you know that I love Powershell. I think it is one of the coolest developer innovations out of Microsoft in years. (To be honest, I’m surprised the Java and Ruby camps didn’t come up with the idea first, but I’m glad they didn’t. But&nbsp_place_holder;I digress…)

Anyway… I like running in my Powershell window. I have one open always. I have lots of functions, aliases and other stuff that do work for me. I love it.

Believe it or not, out of the box there is no way to say “Run as Administrator” in the UAC way. You can use runas.exe but that will prompt you for a complete username/password set instead of using your UAC elevation settings. I knew there had to be a way to do it from code, but my first 30 second search a few weeks ago didn’t turn up the answer so I put it aside. After a bit of digging today though, I discovered that you can accomplish this by simply calling ::ShellExecute with the little known and poorly (un)documented verb “runas”.

In other words,&nbsp_place_holder;a C++ Win32 program could do this…

::ShellExecute( NULL, "runas", "notepad.exe", NULL, NULL, SW_SHOWNORMAL );

…and Notepad would be run using UAC privilege elevation on Windows Vista. Right on. Seems like only&nbsp_place_holder;a little bit of work should be required to turn this into a Powershell Cmdlet and I would have it right at my fingertips.

Here’s the code. It is pretty self-explanatory. I went ahead and made the verb be a Cmdlet parameter so you can control it, and set the default to “open”. Then to elevate in Powershell, I just pass the ‘-verb runas’ option.

[Cmdlet("start", "process", SupportsShouldProcess = true)]
public class StartProcessCmdlet : Cmdlet
{
private string _process = String.Empty;
private string[] _arguments = { };
private string _verb = "open";
[Parameter(Position = 0,
Mandatory = true,
ValueFromPipeline=true,
HelpMessage = "The name of the process to start.")]
[ValidateNotNullOrEmpty]
public string Process
{
get { return _process; }
set { _process = value; }
}
[Parameter(Position = 1,
Mandatory = false,
ValueFromRemainingArguments=true,
HelpMessage = "Arguments for the process.")]
public string[] Arguments
{
get { return _arguments; }
set { _arguments = value; }
}
[Parameter(
Mandatory = false,
HelpMessage = "Specifies the verb to use when calling ShellExecute.")]
public string Verb
{
get { return _verb; }
set { _verb = value; }
}
protected override void ProcessRecord()
{
try
{
int result = SafeImports.ShellExecute(IntPtr.Zero, _verb, 
_process, GetParameters(_arguments), String.Empty, 
SafeImports.ShowCommands.SW_SHOWNORMAL);
if (result <= 32)
{
throw new ArgumentException("An error occurred calling ::ShellExecute().");
}
}
catch (Exception e)
{
ThrowTerminatingError(new ErrorRecord(e, "ERROR", ErrorCategory.NotSpecified, null));
}
}
private static string GetParameters(string[] args)
{
StringBuilder sb = new StringBuilder();
foreach (string arg in args)
{
sb.Append(arg);
sb.Append(' ');
}
return sb.ToString();
}
}

This is not a complete project, so you will probably want to install the Powershell SDK and a few other goodies if you want to compile and run this thing, but once you do that it is all easy as pie. Also, the SafeImports class (not shown here) just contains the DllImport directives for ShellExecute from PInvoke.net.

Enjoy!

My Latest Powershell/XML Hack

I have a little text file I keep quotes and silly quips in. I’ve been collecting it for years. I recently installed a little program called Messenger Plus Live that lets you run scripts and do all kinds of other interesting things to Live Messenger.

Long story short, there is a script for Messenger Plus that lets you have a database of quotes that rotate in your tagline in Messenger. I wanted my little text database in there, but it had its own XML file format.

Powershell to the rescue:

$lines = gc 'quotes.txt'
$dom = [xml] (gc 'quotes.xml')
$lines | % { 
$elt = $dom.CreateElement("sentence")
$elt.set_InnerText($_)
$dom.root.AppendChild($elt)
}
$dom.Save('quotes.xml')

God I love this thing! Who needs perl anymore? Not me.

The Evil Known as MAX_PATH

I’ve been working with Tom Hollander and the Enterprise Library team on and off for the past two years on an interesting pain point: MAX_PATH. Tom just wrote a very nice blog post explaining the problem in great detail, so I’ll refer you there for the whole thing, but here is the gist of it:

We’ve been running into MAX_PATH related errors on and off for some time with our codebase, and we know many of you have as well. The problem mainly manifests itself when compiling the source code inside Visual Studio or msbuild, although you can get it in other situations such as when installing from MSIs or unfolding GAX guidance package templates. At first we assumed that the issue, while unfortunate, was mitigated easily enough by avoiding installing the code into deep root paths. But over time the problem has just gotten worse, and even manifested itself with relatively modest root paths - so we decided we needed to take some action.

And then he shows what we’ve discovered to be the root cause of the problem: How Visual C# 2005 handles embedded resources.

Here’s a tidbit that Tom didn’t put in his post though… Visual Basic 2005 does it differently. Visual C# creates filenames for embedded resources that look like this:

ProjectDefaultNamespace.Folder.Folder.Filename.ext

But Visual Basic skips the “folder” part and does this for the same source tree layout:

ProjectDefaultNamespace.Filename.ext

If you think about this, it means it can be very hard to create code that can be easily converted to or from VB and C#. Suppose we have an icon in the Smart Client Software Factory that is set as an Embedded Resource. In C#, its “name” might be “Microsoft.Practices.SCSF.Sample.Images.Folder.bmp” while in VB its “name” could be “Microsoft.Practices.SCSF.Folder.bmp”, and any code that references this image by name is not-covertible.

Yuck.

Kzu on Software Factories

Daniel Cazzulino recently wrote a nice article called “Building Software Factories Today” where he outlines some of the challenges and techniques you can use (and we will be using) to make effective factories on today’s platform. Here’s an excerpt:

First and foremost: this article is NOT about the grand vision (still underway, rest assured, but nevertheless in the realm of the future) outlined in The Book: Software Factories: Assembling Applications with Patterns, Models, Frameworks, and Tools by&nbsp_place_holder;Jack Greenfield, Keith Short et all. I will talk about technologies that are ready for production today. This also means that the strict concept itself of a SF outlined in the book needs to relaxed for what’s currently possible with the available tools. You can think of today’s factories as infants that will grow with features as times goes by and the end-to-end SF vision becomes a reality. That doesn’t prevent the “infant”&nbsp_place_holder;from giving you a big boost in terms of productivity and software quality.

You can build SF today by using a combination of the following two tools:

  • DSL Tools: or Domain Specific Language Tools. This is a toolkit for creating VS-integrated graphical notations for a given model that you basically make up. It allows you to construct a custom-tailored designer for a model for your problem domain.
  • GAT/GAX: or Guidance Automation Toolkit/Extensions. This is a toolkit for extending VS with solution, project and item templates, code snippets and so-called recipes (or developer use cases) exposed as context menus within VS, that guide the developer in completing a complex task.

The problem is that integrating the two is not a trivial task. Why you may ask? Aren’t they both coming from Microsoft? And how does GAT/GAX fit in the SF vision? After all it’s not even mentioned in The Book!

Go read the rest of it on Daniel’s blog.