Damon Payne: Hand waving software architect

103db signal to noise ratio at < .03% total harmonic distortion
Solution Architect, software developer, geek
Damon Payne at Blogged
2009 Microsoft MVP - Client App Dev
2007 Microsoft MVP - Solution Architecture
 Monday, March 31, 2008

A friend of mine sent me this link today, which was also waiting in my RSS reader for me: http://arstechnica.com/news.ars/post/20080331-creative-irate-after-modder-spruces-up-vista-x-fi-drivers.html

Let me reproduce some of the most relevant parts here:

"We are aware that you have been assisting owners of our Creative sound cards... by providing unofficial driver packages for Vista that deliver more of the original functionality that was found in the equivalent XP packages for those sound cards." writes O'Shaughnessy. "In principle, we don't have a problem with you helping users in this way.. .Where we do have a problem is when technology and IP owned by Creative or other companies that Creative has licensed from, are made to run on other products for which they are not intended."

He continues, "By enabling our technology and IP to run on sound cards for which it was not originally offered or intended, you are in effect, stealing our goods. When you solicit donations for providing packages like this, you are profiting from something that you do not own... If we choose to develop and provide host-based processing features with certain sound cards and not others, that is a business decision that only we have the right to make."

But the real issue isn't the money. Read between the lines, and O'Shaughnessy is proclaiming that his company has the sacred right to enable and disable features in-driver even when the sound card's hardware is perfectly capable of performing the tasks at hand.

According to Creative's own FAQ, sound cards from the X-Fi and Audigy families are incapable of decoding Dolby Digital or DTS, due to the fact that "these functions are not supported at driver level in Windows Vista." This isn't true.  When two of the driver files from a standard X-Fi card are replaced with two driver files drawn from a Dell-specific driver available at the company's support web site, DTS and DD decoding immediately reappear as options and function correctly. Creative might be able to get away with saying that DTS and DD decoding aren't enabled at the driver level for X-Fi and Audigy cards, but the functionality is clearly baked into the driver and is thus supported. Creative may never come straight out and say "It's Microsoft's fault that your cards doesn't work," but the Vista support pages are loaded with descriptions of how Vista's audio system broke Creative products. 

This, presumably, is the feature that ticked Creative off, though Daniel_K has enabled other features Creative didn't plan to support in Vista, including DVD-Audio. Anecdotal forum evidence implies that his drivers are of much higher quality and are more stable than those Creative officially provides, but Ars has not verified this. "

So, this is my opinion based on my understanding of US Law and what I believe to be Ethical behavior.  First of all, I don't believe it is legal for the programmer in question to do this.  When you purchase the Creative product, you are agreeing to their license terms by using the product.  This alone should be enough to allow free markets to satisfy any need that could possibly arise in this situation:

  • If computer users do not wish to agree to any terms they feel are overly restrictive (not being able to hack drivers), they should not buy the product.  If companies lose sales because of this, products will arise to meet this demand.  The term "open source hardware" has been thrown around, where the platform itself is out there for people to implement their own software on top of.
  • If consumers do not purchase sound cards because they can't understand the EULA and yet they understand they'll be bound by the EULA, the market can respond by providing simpler EULA.
  • Taking this one step furthur, buying a pre-built machine from Alienware, for example, would implicitly require agreeing to the EULA of every single piece of the PC.  Again, if people do not buy as many computers because of this, the market can respond with PCs that are Easier to Own.

This is my logic concerning the compay's rights:

  1. Few people would argue with the fact that a compay (like Creative) might want to offer different products, with different levels of Capabilities, at different price points.
  2. The market self-organizes, with people who want the extra capabilities able to get them if they are able to pay for them.
  3. Most people would have no issue deciding for themselves whether or not to buy the V6 Mustang or the V8 Mustang based on their own resources and how important more horespower was to them personally.  In the case of less-tangible Products such as software, it seems that people lose their ability recognize Property as Property, theft as theft, which I've written about before.
  4. A company may determine at some point in it's product development lifecycle that it is cheaper for them to include the same hardware in both the less featured and the fully featured versions of the final product.  When R&D and Testing costs are taken into account this can have some surprising manifestations.  At some point many years ago I bought an Aragon Stage One preprocessor (made by Klipsch) for my home theater, this product retailed for $4,000 at the time.  Shortly thereafter I bought the Klipsch Promedia Ultra 5.1 computer speakers for perhaps $299 or so.  I found out later that the SAME processing chip was in each one, the good, expensive surround processing chip.  Klipsch had determined it would be cheaper to use the chip from the Stage One even though the Silicon was very expensive: it was already proven and could be implemented with almost no engineering work.
  5. Creative should be allowed to turn on or off features in its Products via hardware or software as it sees fit.  The fact that the chip could do this but is "artificially" held back by drivers is an aspect of the agreement between the customer and Creative Labs, and is not a chance for people who disregard Intellectual Property rights to get something for nothing.  Whatever features are purposely crippled in this or that product are value-add features that Creative Labs intended to sell for money.


Monday, March 31, 2008 1:20:28 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

Before I go ahead and publish my next article, I have two minor gripes about Extension Method Syntax.  Suppose I have a simple extension method:

public static string FriendlyClassName(this object o)

{

string className = o.ToString();

return className.Substring(className.LastIndexOf(".") + 1);

}

I don't care much for explicity use of "this", sice this is basic OO:

//I don't like Extension Methods making me explicitly use "this"

Console.WriteLine(this.FriendlyClassName() + " complete");

I also don't care much for the following being invalid syntax:

Console.WriteLine(base.FriendlyClassName() + " complete");

That one informs me that type "Object" does not contain the FriendlyClassName() method, it would appear that the "this" keyword triggers the syntactical sugar of the extension method paths of the compiler? 

Take a look at the next one, which Dan thinks is wonderful.  Using this extension method:

        public static bool IsNullOrEmpty(this System.Collections.ICollection collection)

        {

            if (null == collection || 0 == collection.Count)

            { return true; }

            return false;           

        }

The following code does not throw any exceptions:

            List<string> nullList = null;

            if (nullList.IsNullOrEmpty())

            {

                //Whiskey Tango Foxtrot?

            }

Hmm... you can read about the NullObject pattern here http://www.cs.oberlin.edu/~jwalker/nullObjPattern/

In a disgusting way, we could use a set of Extension Methods to impliment the NullObject pattern.  IF it makes sense in your program, IF doing the usual coding for nulls isn't appropriate, IF the classical Null Object pattern isn't appropirate, IF Nullable<T> isn't appropriate for your scenario.  That's a lot of ifs.  Still, I found this to be useful in certain cases.



Monday, March 31, 2008 12:58:11 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

http://rss.slashdot.org/~r/Slashdot/slashdot/~3/261336950/article.pl

I believe this to be awesome news.  While Intel, John Carmack, NVidia, and others debate the degree to which Ray Tracing is useful, I'll be hoping that the Direct X Ray Tracing solution using the Task Paralell Library (or something like it) to exploit multiple cores for ray tracing a scene.



Monday, March 31, 2008 11:17:06 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, March 30, 2008

A small part in my decision to make the leap to Vista 64 was Scott Hanselman's findings upon building his home office and a new workstation therein: moving to 64bit was a non issue.  I wish I could report the same.

My first issue was with installation.  With certain hardware configurations Vista x64 will crash with 4gb of RAM installed until a hotfix is applied.  My understanding of the state of the 64bit world is this: the current 64bit processors are not entirely 64bit.  They have 64 bit registers and some 64 bit instructions.  To the average PC hotrodder or developer the preceived benefit is the ability to use 4gb of RAM instead of 2gb.  I installed the hotfix, got my 4gb of RAM working, and set about playing the Sweeny Todd soundtrack whilst I decided what order to install things in...

The drivers for my X-Fi sound card installed, I started Windows Media Player.  Windows Media Player crashes the first time I start it 100% of the time.  Vista Sp1 and a horde of updates has not fixed this.  Since the X-Fi is probably the most popular aftermarket sound card in the known universe at this time this ought to to work.  It may have nothing to do with sound drivers at all.  The 2nd or 3rd time I launch WMP it will work happily forever.

My graphics card is a PNY GeForce 8800GT series.  It clearly lables itself as having both 32 and 64 bit drivers on the driver disc.  I experienced an new Vista phenomenon with this product: installing the drivers using the OEM program declared that no changes were made to the system.  I pointed Vista at the .inf file on the disc for x64 and Vista basically said its own driver ("Standard VGA Port", yeah right) was better and refused to take the OEM drivers.  Only the Nvidia unified driver install would work.  Odd...

My next stage is to get my various messenger programs up and running so I can chat whilst I wait on things to intall.  MSN messenger is objectively the best messenging experience by far so that went worst, no issues there.  I have used Pidigin for AIM (most CarSpot folks and some friends are on AIM) since having crashtastic experiences with Trillian and an absolutely astounding clusterfuck with the actual AIM product ruining a Vista install; it would seem AOL is the reigning champ for the national heavyweight Invasive Install Championships.  Pidgin has similar behavior as WMP, it will crash and then run perfectly for as long as the machine is up.

My Blu-Ray drive was working fine as a Serial ATA DVD drive.  Folks, installations of things like Office 2007 are actually quite painless with something better than an IDE CD drive.  This alone is worth the extra expense.  Still, it came with software that can play Blu-Ray and there are nights (like tonight) where I'd like to watch a movie on my 1080p monitor while some long-running tasks scroll by on my ancient 19" CRT.  My first attempts at getting Blu-Ray playback working met with failure.  The install process (damn you Pioneer) demanded ridiculously old and specific versions of DirectX that were clearly not going to happen on Vista 64.  Some combination of Windows Update and SP1 magically fixed this, so I watched Blood Diamond on Blu-Ray while logs scrolled by tonight. 

My last two 64bit issues dealt with actual software development.  Visual Studio and the like installed and ran fine except for one issue so infuriating I have a dent in my forehead: copying an ASP.net solution from one machine to another suddenly started claiming that "{MyCustomThinger} RoleProvider has already been added", and commenting this part of Web.config out certainly allows the site to run.  As expected, putting this web.config with the role provider commented out on any other machine (including my development laptop and the two servers where the site actually lies) crashes because of course the configuration is incorrect.  I still can't explain this one.  The next issue was the most time consuming.

MySQL comes in 32 and 64 bit flavors for Windows.  I needed MySQL for one of my development efforts.  MySQL x64 does not like Vista x64.  In fact, the slightly out of date platform notes on their site claims Vista x64 is not supported, but there are enough success stories out there suggesting this is just a CYA that I gave it a whirl anyway.  MySQL 64bit would not run, claimin a "side by side configuration" was incorrect.  OK, knowing very litting about anything this sounds like some sort of thunking layer issue, so I'll try the 32bit version.  Same error.  What does Google say?  Google says that the Application Manifest in the server configuration process is broken for 64bit windows.  The MySQL forums claim that only a program called Reshack can fix this.  The problem with Reshack is that it runs on 32bit platforms only.  For those who don't know: the various Icons, embedded resources, and execution manifests for a Windows .exe get compiled into a special section of a windows PE and programs like Reshack can read/write this area without doing anything to the code itself.  I eventually found a Delphi program posted by a company in the UK that would work for me.  Having had some bad experience with libraries without a strong name that I am missing the code to recompile, I was glad MySQL doesn't attempt to use authenticode signatures with their releases.  I changed the Vista application manifest XML to the appropriate "requireAdministrator" and I was finally off and running.

The last issue I experienced this weekend was with some code I "inherited".  It uses Microsoft Jet ODBC to treat a file as a "data source", access the file rows using DataReader constructs, and sort the contents in a DataGrid.  This code bombed when I ran it.  Google tells me that Jet does not exist for x64.  I had planned on rewriting this code anyway but wasn't up to the task tonight, so I kept looking.  .NET programs are usually targetted to "any CPU" by default, but Google told me changing the target specifically to x86 would allow some extra Thunking Magic to happen, and as soon as I did this I was back on my way with Jet magically found now.

After these experiments, I'm definately going to test all of my production code to make sure I haven't done anything that won't work with 64bit editions of Windows or the .NET framework.  I also need to take it upon myself to do some research: what does 64bit do for me and for Joe Consumer besides 4gb of RAM on Vista?

.NET | ASP.NET | Rant


Sunday, March 30, 2008 1:17:26 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Wednesday, March 26, 2008
yay

It would appear the new workstation is stable now, after flashing the BIOS and other tomfoolery. 

Despite being "overkill" in terms of hardware, it still takes for freaking ever to install things in Vista, Live Messenger being the worst offender by far.  Now I need to reorganize my office, set up the extra monitors and speakers, and go about intalling my various development tools.  I should have a very productive weekend.



Wednesday, March 26, 2008 10:34:20 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

I had to comment on this article over at Slashdot:

http://developers.slashdot.org/article.pl?sid=08/03/25/0145208&from=rss

I don't have a feel for how people in France feel about concurrency programming, but it certainly seems like a hot topic among .Net developers in the USA these days.  The Task Paralell Library, Polyphonic C#, the inreased interest in functional programming languages because of the benefits functional programming can bring to concurrency; I would say interest in this aspect of programmnig is ever-rising.

This reminds me, if I ever get my workstation running, to finish my next concurrency in .NET article.



Wednesday, March 26, 2008 10:38:36 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Yesterday when I got home I plugged the new mobo in, and wonder of wonders it would post.

Last night I had birthing classes, had to go get our daughter from Grandma's after than, so at 11pm I sat down to finish building the workstation.  Vista kept treating me to bluescreen/stop errors after copying files.  Doing some research I found that many motherboards + Vista64 have an issue booting with 4gb of ram installed, so I eventually took some RAM out and got windows installed.  There was a hotfix for this issue but I kept having hard lockups in Vista.  Long story short: I flashed the BIOS after about 4 Vista lockups, Vista insisted on using it's "standard VGA driver" instead  of the Vista 64 driver that came with my 8800 GT.  I'll have to see tonight if Vista is happy with the new BIOS and/or the NVIDIA unified drivers when I try that. 

Stupid computers...



Wednesday, March 26, 2008 9:35:46 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Tuesday, March 25, 2008

I've always been a proactive person:

"Rochester researchers showed for the first time that a natural antioxidant found in grape skins and red wine can help destroy pancreatic cancer cells by reaching to the cell's core energy source, or mitochondria, and crippling its function. The study is published in the March edition of the journal, Advances in Experimental Medicine and Biology.

The study also showed that when the pancreatic cancer cells were doubly assaulted -- pre-treated with the antioxidant, resveratrol, and irradiated -- the combination induced a type of cell death called apoptosis, an important goal of cancer therapy."

http://www.scienceblog.com/cms/mounting-evidence-shows-red-wine-antioxidant-kills-cancer-15734.html

The part about "Pre-treated" is especially precious.  Now please excuse me, I'm going to go pre-treat my cells against cancer.



Tuesday, March 25, 2008 5:04:03 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

It would appear the UPS man has dropped off my replacement, time for attempt #2.



Tuesday, March 25, 2008 1:23:53 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, March 22, 2008

I thought that a nice feature for my KlipschCorner System Profiles Area might be a deep zoom for the people who upload very high res photos.  I quickly realized I do not currently see any way to automate the creation of the Deep Zoom .bin files needed by the MultiScaleImage control.  One step beyond automation, it ought to be available in a server-side Silverlight API for the many people in hosted environmnets where they may not be able to install Deep Zoom Composer and shell execute an .exe. 



Saturday, March 22, 2008 11:58:13 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

I am now one NewEgg hat and $50 better off than I was the other day, but my replacement part still has not shipped.  So much for having it overnighted to me.  In addition to the claimed system glitches that kept this from shipping when it was supposed to, their systems showed on Friday that the part was boxed but no tracking number had made it out yet.  This morning, I find an email notification that my $$ has been refunded for the part.  I'm not sure if this is additional goodwill or another mistake, but I'll have to wait until Monday to find out.



Saturday, March 22, 2008 5:21:52 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, March 21, 2008

http://www.forbes.com/opinions/2008/03/19/yaron-campaign-finance-oped-cx_ybr_0321yaron.html

I encourage you to leave comments on Forbes as well.



Friday, March 21, 2008 10:16:01 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, March 20, 2008

So, I set about to RMA my motherboard on Wedesday.  To make a long story short, they gave me a UPS shipping lable and the transit times plus their RMA processing time put the round trip at something like 3 weeks for me.  This is complete BS since by then my window to return/exchange the other parts will be gone.  I shouldn't have to wait a month for something I bought to be made right.  I called NewEgg's customer support to explain this to them, and lo and behold without the slightest bit of resistance they agree to overnight me a new one without making me wait on the whole RMA Process.  I left them a strongly positive note at Reseller ratings and I should be in Quad Core Heaven Friday night or Saturday.

There's an important item to not here.  A DOA Asus motherboard is not the fault of NewEgg, but I am more directly their customer than a customer of Asus.  A company cannot directly pin their customer's satisfaction on the performance of their own vendors.  A layer of indirection is needed.



Thursday, March 20, 2008 11:02:03 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, March 18, 2008

It would appear, at least on my machine, that I can add "No script debugging" to my list of IE8 complaints.  Despite making the necessary Internet Options changes I can't hit breakpoints in javascript in VS2008 any longer.  It's a beta browser and I don't really need this feature, but be warned.



Tuesday, March 18, 2008 9:19:01 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Securing ClickOnce Deployments

 

I have written in the past (http://www.damonpayne.com/PermaLink,guid,da078e56-eb5b-4616-8605-13c38d54efc3.aspx) about some of the plumbing involved in ClickOnce and Client Application Services geared towards preventing unauthorized operation of a client application.  At the time I noted that there is at least one issue with this level of “security”.  While it does keep unauthorized users from running your application, it doesn’t protect your code.  As has been lamented elsewhere, a ClickOnce application deployed on the Internet does not have any supported means of keeping people from getting your code.  Even with obfuscation this may be undesirable: we don’t want people getting a hold of the application manifest file and subsequently the assemblies.

A while back I started down a few paths to secure the code and the Application Deployment Manifest.  I have a pretty decent partial solution for protecting your code.  Read on.

Step 1: Security by Obscurity?

Your ClickOnce deployment consists of a deployment manifest, the executable, and all of the referenced assemblies and content files required to run your Windows Forms or WPF application.  By turning off directory browsing and not having any publicly available links to your .application file you can discourage casual spelunking.   I’m not very satisfied with this as the only aspect of a code protection solution. 

It’s fairly unlikely than an uneducated attacker (outside job) could guess the names of the assemblies that make up your application.  If only you could lock down the .application file…

Eliminating the Obvious Suspects

To get the obvious thoughts out of the way:

1)      You cannot protect a ClickOnce application deployment manifest using Forms authentication.  The client side aspects of ClickOnce that get installed with .NET 2.0 can’t handle Forms authentication.

2)      Windows Authentication is at best a poor option here, and not a real option for most people.  The performance of “Remember me” in an NTLM challenge form is flaky and is not supported in the Internet zone.

3)      ClickOnce does not support HTTP Basic authentication schemes.

An Idea

One nice thing about ClickOnce is that, like Silverlight, it’s really a Client Side technology.  You don’t NEED to be running Windows Server and IIS in order to benefit from these technologies.  However since I do know some Microsoft server side technologies I had an idea.

I wanted to associate a Product Key of some kind with a ClickOnce deployment.  Product Keys are not the final word on application licensing but it is a means of accountability.  Look at Windows Genuine Advantage: you can’t keep people from sharing CD keys but if you see the same CD key being used 1,000 times from different hosts all over the nation, you can shut it down.  Given a test deploy URL:

http://localhost:5785/CarSpot/MyApp.application

It would be nice to be able to do something like this:

http://localhost:5785/CarSpot/MyApp.application?ProductKey=ZZZZ-ZZZZ-ZZZZ-ZZZZ

… and have it be meaningful.

HttpHandler Attempt 1

To keep people from getting at my .application file, I thought a simple IHttpHandler implementation configured to intercept requests for the .application extension could be the answer. 

Web.config and sample code:

<add verb="GET,HEAD" path="*.application" type="ClickOnceHandler"/>

    public void ProcessRequest(HttpContext context)

    {

        string key = context.Request.QueryString["key"];

        if (ValidateKey(key))

        {

            //Go ahead and give up the .application file

        }

        else

        {

            throw new ApplicationException("Invalid or missing product Key");

        }

    }

I had a notion of allowing users to sign up for the application online by entering a privately exchanged product key in a web page:

The code behind for the install button is simple:

Response.Redirect("Publish/MyApp.application?key=" + _prodKeyTxt.Text);

Sadly, the solution is not this simple.  The Client side ClickOnce libraries exhibit some bizarre behavior.  Using Fiddler and the debugger, this is what I see:

1)      The .application file is requested and the HttpHandler validates the product key.

2)      Mysteriously, ClickOnce requests the entire URL of the .application file again: http://localhost:5785/CarSpot/Publish/MyApp.application?key=123; The HttpHandler validates the key and writes the contents of the .application file to the Response.

3)      Mysteriously (and infuriatingly) ClickOnce requests the .application file again: http://localhost:5785/CarSpot/Publish/MyApp.application ; note the missing piece of the query string.

I was unable to dig up any reason for what ClickOnce is doing here, but I do know that without getting that product key my scheme is ruined.  Looking at the .application file there’s a likely explanation:

<deploymentProvider codebase="http://127.0.0.1:5785/CarSpot /Publish/MyApp.application" />

This is where I left it for a long time, while dealing with some other things at work.

HttpHandler Attempt 2

The next, step, I felt, was to dynamically alter the application deployment file so that the provider codebase as recorded by the client included the product key on the URL.  I added some code to do this:

                XmlDocument doc = new XmlDocument();

                doc.Load(path);

                //Get around the issue of the query strong not being

                doc.DocumentElement["deployment"]["deploymentProvider"].Attributes["codebase"].Value += "?key=123";

However, this introduces another issue.  When you choose to give your ClickOnce application Full Permissions (Otherwise why not go the XBAP route?) you will find that you are signing it.   Look what Visual Studio did for me:

<publisherIdentity name="CN=CORNZOR\Damon" issuerKeyHash="" /><Signature Id="StrongNameSignature" {nothing to see here}</Signature></asmv1:assembly>

Yes, my laptop is named cornzor.  At any rate, this signature is there for your clients’ protection: if a man in the middle alters the XML payload of the .application file it will not verify and ClickOnce will refuse to do anything further.  Since I’m not a man in the middle I assumed there must be a way to dynamically do what I want.

http://msdn2.microsoft.com/en-us/library/acz3y3te.aspx

http://msdn2.microsoft.com/en-us/library/bb384246.aspx

I started out with the two links above, researching how the manifests are generated, and initially experimenting with manually generating un-signed deployment manifests from the command line.  After an infuriating XML character encoding setback, these articles did lead me to the answer.  Now I can protect my .application file in an appropriate fashion.

HttpHandler Complete

The articles above give us the tools we need to put the product key in the application manifest for each client and re-sign on the fly.

    /// <summary>

    /// Execute whatever business logic will make this a valid program key

    /// </summary>

    /// <param name="key"></param>

    /// <returns></returns>

    protected bool ValidateKey(string key)

    {

        return key.Equals("123");

    }

    /// <summary>

    /// Create a new signature in the deployment manifest

    /// </summary>

    /// <param name="path"></param>

    /// <param name="pfxPath"></param>

    protected void ReSign(string path, string pfxPath)

    {

        string mageArgFormat = "-Sign {0} -CertFile {1} -ToFile {2}";

        string mageExe = @"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\mage.exe";

        string mageArgs = string.Format(mageArgFormat, path, pfxPath, path);

        ProcessStartInfo info = new ProcessStartInfo();

        info.Arguments = mageArgs;

        info.FileName = mageExe;

        info.UseShellExecute = false;

        info.RedirectStandardError = true;

        info.RedirectStandardOutput = true;

        Process p = Process.Start(info);

        p.WaitForExit();

        using (StreamReader stdOutReader = p.StandardOutput)

        {

            string stdOut = stdOutReader.ReadToEnd();

            //Error handling here

        }

 

        using (StreamReader stdErrReader = p.StandardError)

        {

            string stdErr = stdErrReader.ReadToEnd();

            //Error handling here

        }

    }

This method uses mage.exe to re-sign the application manifest after we’ve modified it using the BuildClientApplicationFile method.  There are two things to note about this method.  You will see that I’m referring to mage.exe from the Visual Studio directory here.  Obviously, installing Visual Studio on servers is not ideal.  Mage.exe also ships separately with the SDKs as indicated in the two articles I’ve linked, which is a more palatable install than Visual Studio.  I have not yet tested to see if mage.exe will properly operate on Windows Server 2003 by itself.

The second note on this method is that we are passing in a fully qualified path to a .pfx key file.  I’ve only tested this using the same .PFX key I used when I initially published the application.

    private static void BuildClientApplicationFile(string path, string signedPath, string key)

    {

        XmlDocument doc = new XmlDocument();

        doc.Load(path);

        //Get around the issue of the query string not being persisted by the client

        doc.DocumentElement["deployment"]["deploymentProvider"].Attributes["codebase"].Value += "?key=" +key;

        doc.Save(signedPath);

    }

This method stores a special version of the application deployment manifest for this particular client.  I didn’t keep this in memory due to ClickOnce’s odd habit of re-requesting the manifest many times.  This means I’ve got one copy of the manifest per client sitting on my server.  In my testing I’ve found two side-effects for publishing updates using this method:

1)      If I re-activate from the URL and updates have been published, the client does not get prompted

2)      If I run the program from the start menu, the client does get prompted for updates.

Here is the main HttpHandler method:

    public void ProcessRequest(HttpContext context)

    {

        string key = context.Request.QueryString["key"];

        if (ValidateKey(key))

        {

            //Just in case the client doesn't know what to do with .application

            context.Response.ContentType = "application/x-ms-application";

 

            //The current version deploy URL

            string path = context.Server.MapPath("~/Publish/CarSpot.MyApp.application");

 

            //Check for existing signed manifest for this user:

            //keeping a specially signed copy of the manifest per client

            string signedFileName = key + ".CarSpot.MyApp.application";

            string signedFileServerPath = "~/Publish/" + signedFileName;

            string signedPath = context.Server.MapPath(signedFileServerPath);

 

            if (!File.Exists(signedPath))

            {

                //Load the generated deployment manifest as an XML document to make it easier

                //to edit.

                BuildClientApplicationFile(path, signedPath,key);

 

                string pfx = context.Server.MapPath("~/Publish/MyGeneratedKey.pfx");

                ReSign(signedPath, pfx);

                //Redirect to original path now that we've created a signed one...

                string redirUrl = context.Request.Url.ToString();

                context.Response.Redirect(redirUrl);

            }

            else

            {

                context.Response.WriteFile(signedPath);

                context.Response.Flush();

            }

        }

        else

        {

            context.Server.Transfer("~/Error.aspx?errormessage=Missing or invalid Application key");

        }

    }

I feel confident this is safe, look at the appref-ms file generated from this install and put in the start menu on the client:

http://127.0.0.1:5785/CarSpot /Publish/MyApp.application?key=123#CarSpot.MyApp.application, Culture=neutral, PublicKeyToken=blahblah, processorArchitecture=msil

The key is part of the URL, so it should be passed to the server every time.

Further reading

Now I’ve locked down my deployment manifest, at least you have to have a valid product key to get access to it.  This is a large step towards completely locking down my application.  Since this is ASP.Net code that I control, I can log the remote hosts that use each product key and disable one if it appears to be too promiscuous. 

What about your other files?  If someone somehow figures out that there’s a file called CarSpot.Snark.Zing.dll in the ~/ClickOnce directory?  I have some ideas for this too, along the same lines as what we’ve done here.  You will note that one of the Publish options for ClickOnce is to “Use .deploy file extension”.  Sounds like an HttpModule could be created to handle the .deploy extension.

Microsoft’s behavior regarding ClickOnce baffles me, but here we have a means of securing your deployments.



Tuesday, March 18, 2008 10:05:56 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Sunday, March 16, 2008

I found this "atheist prayer" via Friendly Atheist.  I enjoy Friendly Atheist, but I wanted to point out an issue with this post.

"Our brains, which art in our heads, treasured be thy names. Thy reasoning come. The best you can do be done on earth as it is. Give us this day new insight to resolve conflicts and ease pain. And lead us not into supernatural explanations, deliver us from denial of logic. For thine is the kingdom of reason, and even though thy powers are limited, and you’re not always glorious, you are the best evolutionary adaptation we have for helping this earth now and forever and ever. So be it. "

I suppose this is meant as harmless humor, and I probably wouldn't flip out if it had been entitled the Atheist Credo or something similar.  Let's not kid ourselves though.  Equivocation is one of the main ways the un-religious are attacked today.  Sam Harris absolutely called this one right.  To call the extreme skepticism of the possibility of the existance of a god on the same plane as faith is one of the more popular parlor tricks of the faithful today.  To call acceptance of empirical evidence a "kind of faith" turns my FlipOut dial up to 11.  The faithful are having enough of a hayday with ridiculous equivocation arguments as it is.  Do we really need to add ammunition by creating secular versions of old catechisms?  I think not.

Main Entry:
1prayer Listen to the pronunciation of 1prayer
Pronunciation:
\ˈprer\
Function:
noun
Usage:
often attributive
Etymology:
Middle English, from Anglo-French priere, praiere, preiere, from Medieval Latin precaria, from Latin, feminine of precarius obtained by entreaty, from prec-, prex
Date:
14th century
1 a (1): an address (as a petition) to God or a god in word or thought <said a prayer for the success of the voyage>
 
Yeah, I think I'll abstain from using this term even in jest, lest it be misconstrued.


Sunday, March 16, 2008 6:06:29 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, March 15, 2008

Dan has posted an excellent article on implementing a Tree structure in C#.  He has beaten me to the punch, after talking about my "Tree of dependent tasks" idea over beers.  I should have something to add to the discussion in the coming weeks.



Saturday, March 15, 2008 3:45:31 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, March 14, 2008

http://www.ps3fanboy.com/2008/03/14/microsoft-predicts-blu-ray-irrelevance-in-12-18-months/

This is ludicrous talking head speak for the vast overwhelming majority of the movie-watching population.  If I can get better than 1.5mb/s DSL in the next 18 months I'll be shocked, let alone enough pipe to handle True HD video.  At this stage of the market, digital downloads and packaged optical media are NOT different versions of the same product, as these people seem to believe.  They are utterly different products.  38mb/s MPEG4 with 5 mb/s LPCM is not "the same" as the puny bitrates we get through video on demand at this time, and the average consumer can handle having a shelf full of optical discs much easier than they can prepare for terrabytes of digital storage medium.  How many average users have a backup strategy?  The first time you want to watch The Matrix but that hard drive died and you have to redownload you'll be mad.  When you realize the DRM might make re-downloading insanely painful or impossible (as Casey has shown) you'll be wishing you had a Blu-Ray player instead.

The market will figure this out, but not in 12 months, sorry Mr Pundit.

{Edit: I ironically realized after I posted that the words of someone linking to any website with "fanboy" in the name are probably deserving of a side of salt as well.  Oh well, it's the internet...}

Movies | Rant


Friday, March 14, 2008 3:37:05 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

What an astouning letdown: I was on track for a red letter day yesterday.  My parts came in, it was sunny, and I drank some beer and BBQ-ed some poultry.  Hickory smoked BBQ chicken is among my favorite foods and I haven't been able to have it for months because my wife has a pregnancy aversion to most forms of chicken and it's also been ridiculously cold.  I went to best buy and got Bioshock only to discover later that one of my parts is either DOA, I've plugged something in wrong, or the specifications are lying about compatibility.  What a letdown, I expected to be fighting issues like MySQL on 64bit Vista but instead I have no POST.  Luckily my brother recently bought very similar Intel hardware so I should be able swap parts around to find out which piece is dead.  As much as it's a badge of geek honor to build one's own screaming development PC, I think in the future I will not do so during times like now where I have plenty of other things to worry about.



Friday, March 14, 2008 10:53:05 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback

My sister had a little boy at 9pm last night, Daniel James Sapp.  I'm an uncle for the 3rd time in a short while.



Friday, March 14, 2008 10:44:02 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, March 13, 2008

I pulled the trigger on a new desktop setup which is out for delivery according to the internets.  Because of my recent research I went ahead and went the quad core route, and after reading Scott Hanselman's blog post about going x64 being a complete non issue I'll be giving Vista x64 a try.  I also got a 1080p monitor and a Blu-Ray drive; probably slightly overkill but why not.  It's difficult to explain to non-programmers, but an appropriate setup with the right desk space, music, multiple monitors, and a repsonsive machine can make developing software a joy.  It can be a joy without a Raptor drive and a 24" display but these things help. 

I got a decent enough video card to check out some of the recent games, Bioshock being at the top of the list.



Thursday, March 13, 2008 8:19:39 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, March 07, 2008

Catastrophic failure!  Now that is an error message worthy of my college dating life!... anyhoo, I digress.  Many controls in winforms and WPF probably try to do things in the constructor or an "OnLoad" event of some kind that are not design-time-friendly, but the magic of the environment mostly keeps things like this from happening.  This magic dust has not yet been sprinkled upon the Silverlight 2.0 tools for VS2008, but I'm still very excited about the possibilities as this platform matures.  The error below occurs if I add some test images into the loaded event of the carousel and try to view the designer. 



Friday, March 07, 2008 8:23:31 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Here's my carousel sample, as a user control within my Silverlight image uploader. The Tesla Roadster is such a sexy car it's perfect for any automotive industry technology demonstration.



Friday, March 07, 2008 8:07:43 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Ok, here is my silverlight 2 port of the ever popular Image Carousel sample.  For me, at least, it's much easier to follow what's going on than the JavaScript version.  Here's the markup required in the page:

<UserControl x:Class="ImageUploader.ImageCarousel"

    xmlns="http://schemas.microsoft.com/client/2007"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Width="800" Height="600" Canvas.Top="0">   

<Canvas x:Name="_mainCanvas" Loaded="mainCanvasLoaded" MouseMove="whenMouseMoves"

        MouseLeftButtonDown="mainDown" MouseLeftButtonUp="mainUp" MouseLeave="mainCanvasMouseLeave" >

    <Canvas Name="imagesHolder" Canvas.Left="0" Canvas.Top="0">

        <Canvas Name="mainImageHolder" Canvas.Left="200" Canvas.Top="60" Canvas.ZIndex="169" Opacity="1.0">

            <Image Name="mainImage" Stretch="UniformToFill" Height="225" Width="300"/>

        </Canvas>

    </Canvas>

</Canvas>

</UserControl>

And here is the code-behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;

namespace ImageUploader
{
public partial class ImageCarousel : UserControl
{
public ImageCarousel()
{
InitializeComponent();
_mainDown = false;
_urls = new Dictionary<Image, string>();
_angles = new List<double>();
_imagePaths = new List<string>();
//_imagePaths.Add("Upload/1.jpg");
//_imagePaths.Add("Upload/2.jpg");
//_imagePaths.Add("Upload/3.jpg");
_dispTimer = new System.Windows.Threading.DispatcherTimer();
_dispTimer.Interval = TimeSpan.FromMilliseconds(25);
_dispTimer.Tick += new EventHandler(dt_Tick);
_dispTimer.Stop();
}

private List<double> _angles;
private bool _mainDown = false;
private List<string> _imagePaths;
private List<ScaleTransform> _scaleTransforms = new List<ScaleTransform>();
private double _speed = -0.0425;
private Dictionary<Image, string> _urls;
private System.Windows.Threading.DispatcherTimer _dispTimer;

public void AddImage(string imageUrl)
{
_mainDown = false;
_dispTimer.Stop();
_angles.Clear();
_scaleTransforms.Clear();
_urls.Clear();

for (int i = 0; i < _imagePaths.Count; ++i)
{
Canvas holder = (Canvas)_mainCanvas.FindName("imgHolder" + i);
_mainCanvas.Children.Remove(holder);
}

_imagePaths.Add(imageUrl);
Image img = (Image)_mainCanvas.FindName("mainImage");
string srcPath = _imagePaths[_imagePaths.Count -1 ];
img.Source = new BitmapImage(new Uri(GetURLBase() + srcPath));
img.SetValue(Canvas.ZIndexProperty, 169);

BuildImages();
}

private void mainCanvasLoaded(object sender, RoutedEventArgs e)
{
if (_imagePaths.Count > 0)
{
Image img = (Image)_mainCanvas.FindName("mainImage");
img.Source = new BitmapImage(new Uri(GetURLBase() + _imagePaths[0]));
img.SetValue(Canvas.ZIndexProperty, 169);
//
BuildImages();
}
}

public List<string> ServerImagePaths { get; set; }

protected void BuildImages()
{
int leftPos = 0;
for (int i = 0; i < _imagePaths.Count; ++i)
{
Canvas imgHolder = new Canvas();
imgHolder.SetValue(Canvas.NameProperty, "imgHolder" + i);
imgHolder.SetValue(Canvas.LeftProperty, leftPos);
imgHolder.SetValue(Canvas.TopProperty, 0);
//Come back to reflections...
//Canvas imgHolderReflection = new Canvas();
//imgHolderReflection.SetValue(Canvas.NameProperty, "imgHolderRef" + i);
//imgHolderReflection.SetValue(Canvas.TopProperty, 160);
//imgHolderReflection.Opacity = 1;
//LinearGradientBrush lgb = new LinearGradientBrush();
//lgb.StartPoint = new Point(0, 0);
//lgb.EndPoint = new Point(0, 1);
//lgb.GradientStops.Add(new GradientStop());
//lgb.GradientStops.Add(new GradientStop());
//lgb.GradientStops[0].Offset = .37;
//lgb.GradientStops[0].Color = Color.FromArgb(0x00, 0x00, 0x00, 0x00);
//lgb.GradientStops[1].Offset = 1;
//lgb.GradientStops[1].Color = Color.FromArgb(0x88, 0x00, 0x00, 0x00);
//imgHolderReflection.OpacityMask = lgb;
//imgHolder.Children.Add(imgHolderReflection);
//
Rectangle rec = new Rectangle();
rec.SetValue(Rectangle.NameProperty, "rec" + i);
rec.SetValue(Canvas.TopProperty, -6);
rec.SetValue(Canvas.LeftProperty, -6);
rec.Height = 92.0;
rec.Width = 92.0;
rec.Fill = new SolidColorBrush(Colors.LightGray);
imgHolder.Children.Add(rec);
//
Image img = new Image();
img.Cursor = Cursors.Hand;
img.MouseEnter +=new MouseEventHandler(img_MouseEnter);
img.MouseLeave +=new MouseEventHandler(img_MouseLeave);
img.MouseLeftButtonDown +=new MouseButtonEventHandler(img_MouseLeftButtonDown);
img.MouseLeftButtonUp +=new MouseButtonEventHandler(img_MouseLeftButtonUp);
img.Stretch = Stretch.UniformToFill;
img.SetValue(Image.NameProperty, "img" + i);
BitmapImage imgSource = new BitmapImage(new Uri(GetURLBase() + _imagePaths[i]));
_urls.Add(img, GetURLBase() + _imagePaths[i]);
img.Source = imgSource;
img.Width = 300;
img.Height = 225;

img.Opacity = 1;
imgHolder.Children.Add(img);
ScaleTransform imgCanvasScaleTrans = new ScaleTransform();
_scaleTransforms.Add(imgCanvasScaleTrans);
imgCanvasScaleTrans.ScaleX = 1;
imgCanvasScaleTrans.ScaleY = 1;
imgCanvasScaleTrans.CenterX = 50;
imgCanvasScaleTrans.CenterY = 50;
imgHolder.RenderTransform = imgCanvasScaleTrans;
//
_mainCanvas.Children.Add(imgHolder);
//
_angles.Add( i*((Math.PI*2)/_imagePaths.Count) );
}
PositionItems();
_mainDown = true;// huh?
//Set dispatcher interval
_dispTimer.Start();
}

/// <summary>
/// Not used yet
/// </summary>
/// <param name="i"></param>
protected void AddOne(int i)
{
}

void dt_Tick(object sender, EventArgs e)
{
MoveItems();
}

protected void PositionItems()
{
int radiusX=400;
int radiusY=110;
int centerX=425;
int centerY = 260;

for(int i = 0; i < _imagePaths.Count;++i)
{
double myX = Math.Cos(_angles[i])*radiusX + centerX;
double myY = Math.Sin(_angles[i])*radiusY + centerY;
Canvas imgCanvas = (Canvas)_mainCanvas.FindName("imgHolder" + i);
imgCanvas.SetValue(Canvas.LeftProperty, myX);
imgCanvas.SetValue(Canvas.TopProperty, myY);
ScaleTransform stRef = _scaleTransforms[i];
double sc = (myY - stRef.ScaleY) / (centerY + radiusY-stRef.ScaleY);
stRef.ScaleX = sc;
stRef.ScaleY = sc;
_angles[i] += _speed;
imgCanvas.SetValue(Canvas.ZIndexProperty, (int)myY);
}
}

protected void MoveItems()
{
if (_mainDown)
{
PositionItems();
}
}

void img_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{

}

void img_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image img = (Image)sender;

     Image mainImg = (Image)_mainCanvas.FindName("mainImage");
mainImg.Source = new BitmapImage(new Uri(_urls[img]));
mainImg.SetValue(Canvas.ZIndexProperty, 169);
}

void img_MouseLeave(object sender, MouseEventArgs e)
{
//Change color BACK or something cool
}

void img_MouseEnter(object sender, MouseEventArgs e)
{
//Change color or something cool
}

private void whenMouseMoves(object sender, MouseEventArgs e)
{
Point pt = e.GetPosition(null);
double _root_xmouse = pt.X;
     double _root_ymouse = pt.Y    ;
_speed = ((_root_xmouse - 500) / 500) * 0.0755;
}

private void mainDown(object sender, MouseButtonEventArgs e)
{
_mainDown = true;
}

private void mainUp(object sender, MouseButtonEventArgs e)
{
_mainDown = false;

}

private void mainCanvasMouseLeave(object sender, MouseEventArgs e)
{

}


protected string GetURLBase()
{
string str = System.Windows.Application.Current.Host.Source.OriginalString;
return str.Substring(0, str.LastIndexOf("/")).Replace("ClientBin", string.Empty);
}
}
}


 

Not formatted as well as my last posts, I know.  It seems that Google reader doesn't like it when I paste out of Word. Now, one thing that is not very apparent is how to add this UserControl to another UserControl, assuming that JUST having an image carousel is not what you're after.  You need to include the following on the top-level user control:

xmlns:CarSpot="clr-namespace:ImageUploader"

which then allows you to refer to the code:

<CarSpot:ImageCarousel x:Name="_carousel"></CarSpot:ImageCarousel>

This is an aspect of XAML I don't like. There's nothing (that I can see) in the structure/XSD of a XAML document that tells me that an attribute value starting with "clr-namespace:" is something special.  There's a lot of magic like this in XAML.  The Visual Studio "Silverlight Chainer" as the file calls the Beta 1 tools are definately buggy and incomplete.  I am lucky to be able to hit F5 10 times before I get errors trying to debug and having to kill Cassini and VS2008.  I've also found that it's very easy to crash the designer by having naughty things in your code-behind.  It appears that unlike more mature design-time experiences there is no "IsDesignTime" flag that would allow you to keep from having code execute while designing.  I can tell that this is Cider by the wonderfully descriptive stack traces I get, yet things like dragging the new Silverlight controls onto the surface or even moving them around (I'm sick of tweaking location with Canvas.Top/Canvas.Left) are not working yet.  This is a huge step forward, but I've got to think there'll be another beta after this one now.

And that's that.  I should have some more Silverlight 2 observations later, especially about the threading and HTTP aspects.



Friday, March 07, 2008 4:36:07 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [4]  |  Trackback

I have translated the Silverlight "Image Carousel" into Silverlight 2.0 and C# rather than the "JS writing XAML" model.  If I get the code cleaned up this afternoon I will post the carousel portion.



Friday, March 07, 2008 1:33:00 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, March 05, 2008
IE8

I am blogging from IE8.

Many useful things are broken, such as FreeTextBox which I am used to using to write this article.

Also broken, ironically enough, are the dynamic menus generated by asp.net from a sitemap data source. That alone will force me into "Emulate IE7" mode until Beta 2...



Wednesday, March 05, 2008 4:06:34 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, March 03, 2008

I have never been more ready for spring than I am this year.

It was a whopping 40degrees here yesterday and I took advantage of it.  I spent hours chopping ice out of my driveway, I can't type so well today due to the blisters on my fingertips.  The huge piles of snow on either side of my driveway make surprisingly good reach-level beer holding areas, and I pulled out the bbq pit and made some brats.

I also wrote a Mapping tool this weekend.  When you're first getting started it's interesting to note the similarities in flat file, XML, object-to-object, and SQL to Object mapping: at least in terms of the initial abstractions that present themselves.  I specifically wanted to solve a flat-file to complex object problem and write a tool that previews the mapping in real time and realized I couldd do quite a bit more if I weren't lazy.  The main place I could see using this in my own efforts are class-to-class mapping.  For example: if you are consuming a web service the proxy generated will return to you class instances using whatever naming conventions and object structure the designers of said service wanted to give you.  Even if another team in your organization is the maintainer of this service, Directly using these proxy types within your code is probably a mistake; mapping ProxyObject.PrimaryKey to MyDomainObject.Id quickly becomes tedious.  A mapping tool that does the annoying work for you and saves the mapping as an easy XML file or better yet generates transformation code might be helpful. 



Monday, March 03, 2008 9:50:15 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |  Trackback