Check out my new blog at https://shibumiware.blogspot.com

Thursday, June 29, 2017

ShibumiWare Common Controls: Progress Bar

Progress Bar

Introduction

I have been working on a set of controls for years. At a previous company I had access to DevExpress' Suite of controls and loved working with the controls, but for a hobbyist, they are simply too expensive. Work on the control is organic. Each time I run into a user interface problem and the out-of-the-box.NET controls don't do the job; I look for an open source project first. Unfortunately, I found many open source projects lacking. Also, I prefer to write to the control through an API versus using design-time features. I also love composition; some my controls are easily combined to create other controls--also something I found difficult to do with open source projects. Not that I haven't used any open source. I have an HTML Editor built by a former Microsoft Consulting Services friend who made a decent control; it just needs modernizing. That is on my list, and since it began open source, I will release it accordingly.

Control Library

Tonight I made my first video for YouTube. My son is getting into it. I bought him a new computer, a high-gain condenser microphone with a studio stand, and gave him a decent set of headphones. I decided I better catch up to him so I can help him out in this new endeavor. Of course, our choice of topics is unique.

The first control I cover is the ShibumiWare Common Control Progress Bar. For those who have been around Windows development for a while, you are likely all too familiar with the built-in progress bar's shortcomings. It was quite an event when Microsoft added continuous and marquee rendering to block rendering. The first thing I wanted that Windows' version couldn't give me was control over the border: I wanted to change the border's color, thickness, and provide the ability to have a normal and selected border.

I will show the general architecture of the control library in a later post, but for now, understand that each control inherits from SWBaseControl, which provides many features common to all controls, including borders. The next thing was getting rid of the green color. It is a beautiful color, but in many of my apps, it sticks out and isn't consistent with the look-and-feel.




Part I of Breaking Down the Code

I have to admit that I had a long day and I am actually tired, even though it is only 1:45 AM.   I work strange hours and I do my hobby projects on an even stranger schedule. I am going to give you just a taste, and actually the crux, of solving the paint problem.  I wanted a different background than white.  I wanted colors other than green.  I wanted borders, as mentioned earlier (and not covered in this post), and after a while, I figured out how to do gradients. 

The Progress Bar Control inherits from Windows progress bar.    The following code is critical in even starting to gain control over the paint operation:

public void SetStyle(ProgressBarStyle style)
{
    if (style != ProgressBarStyle.Marquee)
    {
        SetStyle(ControlStyles.UserPainttrue);
    }
}

This has the effect of telling the framework "hey, painting is done by this control so don't do anything."   Overriding the OnPaint event is where the bulk of the work is done:

protected override void OnPaint(PaintEventArgs e)
{
    if (_PaintNormal)
    {
        return;
    }
 
    if (OverrideBackground)
    {
        PrepareClientRectangle(BackColor);
    }
 
    if (Value == 0 || Maximum == 0)
    {
        return;
    }
 
    using (Image image = new Bitmap(WidthHeight))
    {
        using (Graphics graphics = Graphics.FromImage(image))
        {
            Rectangle rect = new Rectangle(0, 0, WidthHeight);
 
            if (ProgressBarRenderer.IsSupported)
            {
                ProgressBarRenderer.DrawHorizontalBar(graphics, rect);
            }
 
            rect.Width = (int)Math.Ceiling(rect.Width * ((double)Value / Maximum));
 
            if (rect.Width == 0)
            {
                rect.Width = 1;
            }
 
            using (LinearGradientBrush brush = new LinearGradientBrush(rect, BackColorProgressMarkerColorGradientMode))
            {
                graphics.FillRectangle(brush, 0, 0, rect.Width, rect.Height);
            }
 
            e.Graphics.DrawImage(image, rect, rect, GraphicsUnit.Pixel);
        }
    }
}

  I won't explain in detail tonight, but I can easily disable the advanced painting and revert back to the normal behavior.  If I want something besides the gray background, I set OverrideBackground = true:

public void PrepareClientRectangle(Color backColor)
{
    if (backColor == default(Color))
    {
        backColor = BackColor;
    }
 
    using (Graphics graphics = CreateGraphics())
    {
        using (SolidBrush brush = new SolidBrush(backColor))
        {
            graphics.FillRectangle(brush, Location.XLocation.YWidthHeight);
        }
    }
}

The rest is self-explanatory, but I will say that there are some rectangles that don't need to be there and I call an overload for filling the rectangle that is much simpler, but those are items left over from some experiments I did--and I left the code alone to remind me how to pickup that original train of thought if I need to. 


Wrap-Up

As you can see from the folder structure image above, I have quite a few controls I could cover.  I chose the progress bar because I was working with it tonight on another hobby project and figured out something new I wanted so it was at the for-front of my mind.  The reason I did this post, mainly, was to accompany my first YouTube video so when I see Parker next, I can show him that I am following his lead. I think he will like that.


Colby-Tait

Thursday, June 22, 2017

More on Setup & Updates: Programmatically Accessible .NET Version Information + Some History

Version
Related Posts: Automatic Updates - Tips & Tricks

Introduction

Off and on for years,  I have been involved in developing software for setup and servicing technology for various products.  On of my first projects, when I was a wee 20-year old, working for a startup in Redmond, WA, required an early version of "Office Update."

The Microsoft main campus was just a few blocks from our first "office," which doubled as the company's owner and his wife's house and our work area.  Later,  we moved into real office space in downtown Redmond, and we were the bee's knees; but that's another story.  It was a tremendous time of learning the trade from the inside out.  I was blessed (Glenn Minch, Garin Pangburn, Ken Inglis, and Adrian Jenkins, I am in your debt).

The company, Critical Path Technical Services, Inc., landed a contract with the Office Product Group (Building 18 for you 'Softies' out there) at Microsoft to develop an "Office Solution Automatic Update Service."  Keep in mind this was circa 1995 and, if you have forgotten how unsophisticated the Internet was, take a walk down memory lane with the image below of Microsoft's main site around that time.

It wasn't much.  That year I hand-coded CPTS' first website using Notepad.exe, FTP, and a book I had bought from Barnes & Noble about HTML.

The Call

Microsoft's Website Circa 1995 Courtesy of Microsoft Corporation
 
Microsoft's Website 1995

There is nothing like getting a call from the Mothership offering money to accelerate the pace of learning.  Essentially the idea was this: Microsoft was pushing Office automation heavily in those days, particularly the use of Visual Basic for Applications, which premiered in 1995. Microsoft wanted to combine the Internet and Office so that Office ISVs (Integrated Software Vendors) would have direct access to the burgeoning body of knowledge regarding VBA and Office solution development.

The problem was MSDN shipped on CD (no, not DVD) every quarter and there was very little by way of online content to help the ISV except for a few boards, chat channels, and a tiny number of dedicated websites.

To make a very long story short, CPTS was contracted to build an Internet-based Solutions Delivery Platform--or as we would think of it today, Office Update for developers. The idea was a grand vision. Microsoft hosted an FTP site, and CPTS contracted to write an automatic download system. The system would look for updates and new solution starters, provide a web-based menu of technical assets the developer could choose to download, and take care of downloading and installing the solution-starter (think template with a code-behind). Not unlike the first MSDN down-loader, an ActiveX control would create a modal Windows dialog that did the actual download to the local disk. I hand crafted, in C++, the download engine while another developer worked on the UI portion. The download engine was straight sockets and WinINet programming. Many of you may not know that WinINet.dll is the grandfather of all Internet-related DLLs in Windows. A simple search shows browsers, Google Drive, Excel, and 40 other processes running on my machine that take a dependency on WinINet.dll.  Talk about first-generation.

WinINet.Dll

That was my introduction to servicing and setup because each download was completely self-contained, installed itself into Office through the VBE Object and provided an updating mechanism to detect and install or update VBA solutions in the various Office products. It was some kind of strange, let me say that--but I thoroughly enjoyed writing the download engine (C & C++ remain my favorite languages) using an API that is vastly closer to the wire than what we .NET developers are accustomed today. I can also so say that, before anybody else at Microsoft had an automatic update service, the Office Product Group and CPTS built one.

Setup ImageA Fascination Developed

After that experience, I developed a fascination with software that upgraded itself.  General setup technology, too, became a favorite topic.  For better or worse (probably worse), I wrote the setup technology for Project Office v3.0 at Pacific Edge and EVMS forProject v1.0 - v2.0 at forProject Technologies. I always felt that out of the box setup technologies were geared toward simple end-user products and not enterprise products. I suppose this tendency grew out of my years at Microsoft, where each product, ranging from Microsoft SQL Server to SharePoint, had an in-group setup system with very little, if any, shared concepts or code.  These days, with WIX and others, there is a better platform but if you know what you are doing, you can build a much better setup experience for the user if you combine off-the-shelf with custom setup work.

Usually, when setup is mentioned in product planning, nobody raises their hand to volunteer to take on that part of the product, but there I was, gleefully, hand raised way up, ready to take on the challenge.   I always took it on and I am not sure why.  There must be some deep psychological reason in me that whispers "set it up." uot;

You see, the other thing about setup is that it's the first thing the customer experiences with your product.  If your setup experience is horrible, there is a pretty good chance the product also has issues. Notice the various customer experience programs and other telemetry Microsoft and other software companies use to gather end-user experience data-- you see how important it is to get it right.

Enough Story Telling

I have enough content regarding my adventures at Microsoft as the youngest product manager in the Office Product Group plus the start-ups I was involved with after to write post-after-post.  This post is really about something I was working on this past weekend on one of my geeky hobby projects.  I mentioned an earlier post "Automatic Updates - Tips & Tricks".  That post didn't originate from out of the blue. Aside from the setup work I did at previous companies, I have always kept one side-project alive, in one form or another, regarding setup and servicing.  For a while it was QuickPatch , which is a fairly complete piece of work but I am dissatisfied with it (plus the user interface really bothers me).

Nowadays, under the banner of my new hobby company ShibumiWare, I have developed a system for building, packaging, deploying, setting up, and servicing my hobby products. In fact, I have become so engrossed in the effort, all of my hobby product development has stalled out while I finish this work. Unlike QuickPatch, I don’t have a catchy name for it, but it's the best work I have done in this area to date. Everything goes very slowly at ShibumiWare because I love my day job, have kids, and  have this medical thing continually to deal with, but I plow on.

.NET Version Information

Version detection one of the first things done: check to see if your product's prerequisites are present and provide options to download or otherwise obtain the necessary dependencies before installing the product. I mentioned in the Tips & Tricks post that a baseline is selected for your products to run.

In my case, I choose .NET 2.0 as the baseline version for the installer and update system. The products or utilities individually are written in a later version of .NET but to even get a .NET-based installer to run, you have to pick a baseline. .NET 2.0 is a good baseline selection for several reasons. It is broadly distributed for down-version operating systems like Windows XP, it ships with later OS's like Windows 7, 8 and 10 plus if it's not there from Microsoft, there stands a good chance that some other product installed on the customer's computer required .NET 2.0, and it's there already. Regardless, my setup flow begins with a C++ "bootstrapper" that doesn't require .NET whatsoever. Its purpose is to determine if .NET 2.0 is installed. If it isn't, the bootstrapper provides a link to installation guidance for the user and halts the installation process. Bootstrapping provides an excellent user experience.

Beyond setup, as mentioned, my hobby products and tools require different versions of .NET. Mostly I stick to version 4.5.1 these days, but in my setup builder system, I provide a mechanism for choosing prerequisites, including .NET.  I was working on this part of the system this weekend and found that I could not find in the 2.0 base class libraries a complete list of the versions of .NET.  Of course not, this is 2.0, so it has no future knowledge of versions of .NET. I started thinking about a class that would have all of the versions (subject to new releases I would have to add that's no problem), plus information about what's new associated with each version; and finally, a higher-level source of information for each major grouping of .NET versions. 1.0 to 3.5 comprise one group, while 4.0 and beyond comprise another group—the distinction is the version of the CLR changed between groups.

Custom Attributes

First things first: Custom Attributes.  For those of you who aren't familiar with attributes, you probably are, you just don't know it.  Here is a pretty common example of importing an external function call from a specific DLL:

[DllImport("kernel32.dll", EntryPoint = "TerminateProcess")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TerminateProcess([In] IntPtr hProcess, uint uExitCode);

DLLImport is just one of the attributes in use here and is defined exactly the way a custom attribute is defined.  I have included a complete listing of DLLImport's implementation here .  I think of attributes as declarative markup for code, like XML tags.  Attributes have properties that are accessed by the run-time or your own custom framework to direct how the code is executed, prepared for, or to enforce run-time or compile-time directives, among other things.

In short, custom attributes are very powerful, as you can see from the DLLImport listing.  So, as I started thinking about a class the would encapsulate .NET version information, the first thing that popped in my mind was the use of attributes in two ways: one at the class level to provide version grouping information and second at the property level to provide specific information about the .NET version.  In this way, I could read the custom attributes and provide the user with contextual information about the various versions.  This ability would come in handy when defining a user interface where by a user selects a specific .NET version.  The user could be presented additional information about the version directly in the user interface.

The Version Description Attribute

Attributes inherit directly or indirectly from System.Attribute , which in turn implements the _Attribute interface. When an attribute is defined, it is important that an attribute is applied to the attribute called the AttributeUsage attribute:

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]

Attributes receive their property information through constructors so everything between the open parenthesis and the closing parenthesis above can be thought of a constructor and its parameters.  Just as with regular constructors, you can provided overloads and optional parameters.  Let's think about the AttributeUsage attribute above.  The first parameter is an enum value that defines the "scope" of the attribute.  In other words, it indicates where the attribute is valid to apply, such as a class, property, struct, assembly and so on.  There are sixteen values to choose from.  The next parameter, Inherited = false,  indicates that classes derived from classes that carry this attribute do not inherit the attribute.  The default value is true because you would expect a "base" anything to be overridden explicitly by the derived class rather than either forcing it to be present or burying its presence in the inheritance.  The 3rd parameter is important for our scenario: AllowMultiple=True.  The default is false because typically an attribute is self-contained and applying it twice to a member seems strange--except for when the attribute is purely an information attribute, which we will see an example of soon.

Here is the implementation of the VersionDescriptionAttribute:

[AttributeUsage(AttributeTargets.Property)]
public class VersionDescriptionAttribute : Attribute
{
    public VersionDescriptionAttribute(string description)
    {
        Description = description;
    }
 
    public string Description { getset; }
}

The first thing you notice is the application of the AttributeUsage attribute, which allows this attribute to be applied to properties, it is inherited by derived classes (default value) and it only allows one application per property (again, the default value).  Applied, the attribute is used like this:

[VersionDescription(DotNetVersionDescriptions._1_0_0)]
public static Version _1_0_0 { getset;}

A rule in developing attributes is that all parameters must be a constant value, a typeof expression, or an array creation. In other words, you cannot have computed values as inbound data on the parameters. If you could, I would opt to put the version description text in a resource file, but that resolves to a call to ResourceManager.GetObject, which is a computation. The specific rules are here.

The first two components of the rule are easily understood.  The third requires a little explanation.  It simply means that a parameter can take the form of: type[] {x,y,z} as long as z, y, and z are constants.   The following example is acceptable: string[] {"Additional", "Subtraction", "Division"} There are means by which "dynamic" attributes are achievable.  I leave this topic to the reader to noodle over.

A final note to those not steeped in C# is variable naming constraints.  You probably noticed that version 1.0.0 is _1_0_0 because the following rules apply:

  1. The first character of a variable name must be either a letter, an underscore character (_), or the at symbol (@)
  2. Subsequent characters may be letters, underscore characters, or numbers
  3. The use of C# keywords as variable names is prohibited, although the variable name may contain the key word subject to rule #1 and #2.
Variables and Expressions - https://msdn.microsoft.com/en-us/library/gg615485(v=vs.88).aspx

VersionAdditionalLinksAttribute

public static partial class DotNetVersions
{
    [AttributeUsage(AttributeTargets.ClassAllowMultiple = true)]
    public class VersionAdditionalLinksAttribute : Attribute
    {
        public VersionAdditionalLinksAttribute(string majorVersionGroup, string additionalInformationLink)
        {
            MajorVersionGroup = majorVersionGroup;
            AdditionalInformationLink = additionalInformationLink;
        }
 
        public string MajorVersionGroup { getset; }
        public string AdditionalInformationLink { getset; }
    }
}

After the detailed description of the first attribute, I think what's going on with this attribute is straightforward.  One thing to take note of is the AttributeUsage attribute is applicable only to classes and more than one may be applied to the class.  As mentioned earlier, I want to provide detailed information about the two major groups of .NET versions: those that run on the original CLR and those that run on the second version of the CLR.   Here is an application of the attribute:

[VersionAdditionalLinks(DotNetVersionDescriptions.CLRDotNetVersionDescriptions.CLR_LINK)]
[VersionAdditionalLinks(DotNetVersionDescriptions.CLR2DotNetVersionDescriptions.CLR2_LINK)]
public static partial class DotNetVersions

Putting It All Together

Let's start with the end result and walk backwards--you may figure out how it was done given what I have shown you thus far.   The following is output from a unit test that exercises all of the major pieces of the solution: provide additional information links for the two groups of .NET versions, provide a brief description of each version, provide a sane output from all of the _X_X_X variables for use in user interfaces (such as binding the version list to a combo box).

The OutputOutput


Major Release Collection = v1.1 - v3.x
Major Release Link = https://msdn.microsoft.com/library/ms171868(v=vs.90).aspx
Major Release Collection = v4.0 - v4.x
Major Release Link = https://docs.microsoft.com/en-us/dotnet/framework/whats-new/index

Version = 1.0.0.0
Additional Information =
- First version of the.NET Framework.
Version = 1.1.0.0
Additional Information =
- ASP.NET and ADO.NET updates
- Side-by-side execution
Version = 2.0.0.0
Additional Information =
- Generics
- ASP.NET additions
Version = 3.0.0.0
Additional Information =
- WPF, WCF, WF, CardSpace
Version = 3.5.0.0
Additional Information =
- AJAX-enabled websites
- LINQ
- Dynamic data
Version = 4.0.0.0
Additional Information =
- Expanded base class libraries
- Cross-platform development with Portable Class Library
- MEF, DLR, code contracts
Version = 4.5.0.0
Additional Information =
- Support for Windows Store apps
- WPF, WCF, WF, ASP.NET updates
Version = 4.5.1.0
Additional Information =
- Support for Windows Phone Store apps
- Automatic binding redirection
- Performance and debugging improvements
Version = 4.5.2.0
Additional Information =
- New APIs for transactional systems and ASP.NET
- System DPI resizing in Windows Forms controls
- Profiling improvements
- ETW and stress logging improvements
Version = 4.6.0.0
Additional Information =
- Compilation using .NET Native
- ASP.NET Core 5
- Event tracing improvements
- Support for page encodings
Version = 4.6.1.0
Additional Information =
- Support for X509 certificates containing ECDSA
- Always Encrypted support for hardware protected keys in ADO.NET
- Spell checking improvements in WPF
Version = 4.6.2.0
Additional Information =
- Cryptography enhancements, including support for X509 certificates containing FIS 186-3 DSA, support for persisted-key symmetric encryption, SignedXml
support for SHA-2 hashing, and increased clarity for inputs to Elliptic Curve Diffie–Hellman key derivation routines.
- For Windows Presentation Foundation (WPF) apps, soft keyboard support and per-monitor DPI.
- ClickOnce support for the TLS 1.1 and TLS 1.2 protocols.
- Support for converting Windows Forms and WPF apps to UWP apps.
Version = 4.7.0.0
Additional Information =
- Support for the level of TLS support provided by the operating system.
- Ability to configure default message security settings for TLS1.1 or TLS1.2.
- Improved reliability of the DataContractJsonSerializer.
- Improved +reliability of serialization and deserialization with WCF applications.
- Ability to extend the ASP.NET object cache.
- Support for a touch/stylus stack based on WM_POINTER Windows messages instead of the Windows Ink Services Platform (WISP) for WPF applications.
- Use of Window's Print Document Package API for printing in WPF applications.
- Enhanced high DPI and multi-monitor support for Windows Forms applications running on Windows 10 Creators Update.

Version = 1.0.0.0
Version = 1.1.0.0
Version = 2.0.0.0
Version = 3.0.0.0
Version = 3.5.0.0
Version = 4.0.0.0
Version = 4.5.0.0
Version = 4.5.1.0
Version = 4.5.2.0
Version = 4.6.0.0
Version = 4.6.1.0
Version = 4.6.2.0
Version = 4.7.0.0

The Unit Test


[TestMethod]
public void VersionsClassTests()
{
    DotNetVersions.VersionInfo versionInfo = new DotNetVersions.VersionInfo();
 
    foreach (KeyValuePair<stringstringkvp in versionInfo.MajorReleaseDictionary)
    {
        Debug.WriteLine("Major Release Collection = " +  kvp.Key);
        Debug.WriteLine("Major Release Link = " + kvp.Value);
    }
 
    Debug.WriteLine(Environment.NewLine);
 
    foreach (KeyValuePair<Versionstringkvp in versionInfo.VersionDictionary)
    {
        Debug.WriteLine("Version = " + kvp.Key);
        Debug.WriteLine("Additional Information = " + kvp.Value);
        Debug.WriteLine(Environment.NewLine);
    }
 
    Debug.WriteLine(Environment.NewLine);
 
    foreach (Version version in versionInfo.Versions)
    {
        Debug.WriteLine("Version = " + version);
    }
}

The Implementation


[VersionAdditionalLinks(DotNetVersionDescriptions.CLRDotNetVersionDescriptions.CLR_LINK)]
[VersionAdditionalLinks(DotNetVersionDescriptions.CLR2DotNetVersionDescriptions.CLR2_LINK)]
public static partial class DotNetVersions
{
static DotNetVersions()
{
    _4_7_0 = new Version(4, 7, 0, 0);
    _4_6_2 = new Version(4, 6, 2, 0);
    _4_6_1 = new Version(4, 6, 1, 0);
    _4_6_0 = new Version(4, 6, 0, 0);
    _4_5_2 = new Version(4, 5, 2, 0);
    _4_5_1 = new Version(4, 5, 1, 0);
    _4_5_0 = new Version(4, 5, 0, 0);
    _4_0_0 = new Version(4, 0, 0, 0);
    _3_5_0 = new Version(3, 5, 0, 0);
    _3_0_0 = new Version(3, 0, 0, 0);
    _2_0_0 = new Version(2, 0, 0, 0);
    _1_1_0 = new Version(1, 1, 0, 0);
    _1_0_0 = new Version(1, 0, 0, 0);
}
 
[VersionDescription(DotNetVersionDescriptions._1_0_0)]
public static Version _1_0_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._1_1_0)]
public static Version _1_1_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._2_0_0)]
public static Version _2_0_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._3_0_0)]
public static Version _3_0_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._3_5_0)]
public static Version _3_5_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_0_0)]
public static Version _4_0_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_5_0)]
public static Version _4_5_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_5_1)]
public static Version _4_5_1 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_5_2)]
public static Version _4_5_2 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_6_0)]
public static Version _4_6_0 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_6_1)]
public static Version _4_6_1 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_6_2)]
public static Version _4_6_2 { getset; }
 
[VersionDescription(DotNetVersionDescriptions._4_7_0)]
public static Version _4_7_0 { getset; }
 
public class VersionInfo
{
    public VersionInfo()
    {
        MajorReleaseDictionary = new Dictionary<stringstring>();
        VersionDictionary = new Dictionary<Versionstring>();
        Versions = new List<Version>();
 
        Type parentType = GetType().DeclaringType;
 
        Debug.Assert(parentType != null,
                        DotNetVersionDescriptionErrors.ASSERT_PARENT_TYPE_NOT_NULL);
 
        List<VersionAdditionalLinksAttribute> linksAttributes = parentType.GetCustomAttributes()?
                                                                            .OfType<VersionAdditionalLinksAttribute>()
                                                                            .ToList();
 
        if (linksAttributes == null)
        {
            throw new InvalidOperationException(DotNetVersionDescriptionErrors.ERROR_MALFORMED_VERSION_INFO);
        }
 
        foreach (VersionAdditionalLinksAttribute linksAttribute in linksAttributes)
        {
            MajorReleaseDictionary.Add(linksAttribute.MajorVersionGroup, linksAttribute.AdditionalInformationLink);
        }
 
        PropertyInfo[] properties = parentType.GetProperties();
 
        foreach (PropertyInfo propertyInfo in properties)
        {
            object versionInfoObject = propertyInfo.GetValue(nullnull);
 
            if (versionInfoObject == null)
            {
                continue;
            }
 
            Version version = new Version(versionInfoObject.ToString());
            Versions.Add(version);
 
            VersionDescriptionAttribute versionDescriptionAttribute =
                (VersionDescriptionAttribute) propertyInfo.GetCustomAttributes(typeof(VersionDescriptionAttribute)).FirstOrDefault();
 
            if (versionDescriptionAttribute == null)
            {
                throw new InvalidOperationException(DotNetVersionDescriptionErrors.ERROR_MALFORMED_VERSION_INFO);
            }
 
            VersionDictionary.Add(version, versionDescriptionAttribute.Description);
        }
    }
 
    public Dictionary<stringstringMajorReleaseDictionary { getset; }
    public Dictionary<VersionstringVersionDictionary { getset; }
    public List<VersionVersions { getset; }
}

Wrap-Up

I hope you enjoyed a little meandering down memory lane with our look back on the Internet in 1995, plus some of my own personal experiences. I hope to have taught a few something about custom attributes and, in the end, I hope the DotNetVersions classes are useful to others.

A link to a ZIP archive with everything you need to add the components to your solution follows. You may use this as you see fit. If you have questions, leave a comment.


Colby-Tait

Thursday, June 08, 2017

Automatic Updates - Tips & Tricks

Product Update
(I Received feedback about "Moving On" and decided to
just syndicate my posts to both blogs" 
 

Introduction


Almost every modern Windows and phone app has some method to update itself.  This increases productivity, security, and an automatic update channel, allows the vendor to put more features into the user's hands faster. You might build applications for fun, donations, or maybe it's your full-time job, and a mechanism for automatically updating can be a fantastic time-saver.

There are challenges.  Many corporate IT policies prevent users from installing applications or applying updates.  This does make sense in many cases ranging from maintenance issues to avoiding increasing the threat surface area of information systems.  There are ways around this obstacle such as not attempting to install applications the traditional way through a setup prog, am.  Usually these setup programs will install into Programs or Programs (x86), make registry entries in HKEY_CURRENT_MACHINE or any number of actions that require administrative or elevated rights.

If all the application does are things a regular user can do, its hard to something you aren't allowed to do.  As Raymond Chen refers to It(s) rather involved being on the other side of this airtight hatchway

On the other hand, unless the IT group has the user desktop locked down super tight, you can copy files into directories "owned" by the users and, if necessary, make changes to HKEY_CURRENT_USER key (depending on what and where) if your app requires registry access.  I am not necessarily advocating this, but that's the way my hobby projects ship.

The process of updating is seemingly trivial. House a catalog of updates with a manifest somewhere on the internet. Every time the app starts or, on the user's behest, go grab the manifest, and compare the catalog version with the currently installed version. If the catalog's version is higher "download and apply the update".

Notice the quotations, which could signal sarcasm from the author.

High-Level Update Flow - Reference for Further Discussion

Product High Level
The diagram on the right points to items we will cover later in the post.  I put it out there now because you can see some of the nuts and bolts, and you can noodle on what embedded assemblies and applications are, and why we might want them.

A couple things to just forget about trying to do with app updates.  Forget about trying to secure your catalog.  You certainly can't do it by obscuring its location.  Anyone with Fiddler or WireShark will figure out where your catalog is in about 8 seconds.  Second, don't try to encrypt your catalog because the key distribution is impossibly insecure unless you use asymmetric algorithms, but who wants to ship their public key with their app and then establish a handshake that allows the user to decrypt the downloaded catalog?  It just needs to be a web page or if it's more convenient, open up a public blob store in Azure, which is what I am going to do at some point.

My catalog is actually just a Blogger page, with a text area that contains a simple XML document.  A user is free to go look at the catalog if they want.

Set a baseline version of .NET for your update framework, not necessarily the version that your apps are written in so that you can at least get the user started.  As an example, my installer is written in .NET 2.0 but most of my apps require .NET 4.0.  Most computers will have .NET 2.0 installed already, but there is no guarantee.  Actually, I think you would have to go back to Windows XP to find .NET 1.1 running and I don't focus on that class of users anyway, but you should provide a decent user experience of the required version of the framework is not present.

The update system has three layers, as you can see in the diagram below.  The first is a bootstrap executable written in C/C++, which knows how to find if .NET 2.0 is installed.  If it is installed, the bootstrap kicks off the System Check executable, which is fully contained--meaning that all assemblies that are not part of the .NET 2.0 Base Class Libraries (BCL) are embedded resources and are loaded at run-time. If the bootstrap detects that .NET 2.0 is NOT installed, it will take the user to a Microsoft-provided web page with instructions on how to install the framework.  Note that it also indicates that the user must have administrative privileges to do the installation.  More than likely, the user will end up installing a version of .NET must more current than 2.0 if they get that far.

I will discuss how to load create and load embedded assembly resources later in the post.

Layers

Boot Strap

Welcome back to C/C++ for those who have been away a while.  As you might imagine, aside from great tooling and the usual Microsoft-specific extensions to the languages, not much has changed given that the current state of the art in straight C is C11 and for C++ there is the C++14 standard, and the forthcoming C++17 standard, of which Microsoft has various levels of conformance.

The bootstrap executable is incredibly simple.  It only does three things: checks to see if .NET 2.0 is installed; if not, present a web page explaining how to install it; and finally, if .NET 2.0 is installed, spawns the System Check application.

I am not sure how well my HTML-Copy-Code Visual Studio extension will work for C, but let's give it a whirl, shall we?  Okay, I tried that.  It does NOT understand C/C++.  I found one online that did the trick nicely!  The palette is different but beggars can't be choosers.

You are welcome to skip over the listing.

BootStrapper.exe Listing

include "stdafx.h"

// Global variables
HINSTANCE _Instance;

// Constants
const int ERROR_UNEXPECTED = 3;
const int ERROR_FRAMEWORK_NOT_INSTALLED = 4;
const int ERROR_INVALID_INSTALLER = 5;
const int ERROR_INSTALLER_LAUNCH_FAILED = 6;

const int MAX_BUFFER_SIZE = 2000;

const wchar_t CONSTANT_FRAMEWORK_REGISTRY_KEY[] = L"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v2.0.50727";
const wchar_t CONSTANT_FRAMEWORK_LINK[] = L"https://msdn.microsoft.com/en-us/library/aa480243.aspx#ndp2_ref_topic2";

// Defaults
wchar_t CONSTANT_DEFAULT_PATCHER[] = L"SW.Apps.SystemCheck.exe";

// Prototypes
void LoadFrameworkGuidance();
bool IsFrameworkInstalled();
bool GetInstallerPath(LPTSTR commandLine, LPWSTR installerPath);
bool IsValidInstaller(LPWSTR filePath);
int LaunchInstaller(LPWSTR installerFile, LPTSTR lpCmdLine);
int ShowMessageBox(UINT stringId, UINT type);

//  Entry point
int APIENTRY _tWinMain(__in HINSTANCE hInstance,
                       __in_opt HINSTANCE hPrevInstance,
                       __in LPTSTR lpCmdLine,
                       __in int nCmdShow)
{
    _Instance = hInstance;

    int returnValue = ERROR_SUCCESS;

    // Is the 2.0 Framework installed?
    if (!IsFrameworkInstalled())
    {
        // No, load guidance
        LoadFrameworkGuidance();

        returnValue = ERROR_FRAMEWORK_NOT_INSTALLED;
    }

    if (returnValue == ERROR_SUCCESS)
    {
        // Yes, load the exe off the command line or run the default exe name defined above
        wchar_t installerFile[MAX_PATH * sizeof(wchar_t)];

        if (!GetInstallerPath(CONSTANT_DEFAULT_PATCHER, (LPWSTR)&installerFile) || !IsValidInstaller(installerFile))
        {
            returnValue = ERROR_INVALID_INSTALLER;
        }

        if (returnValue == ERROR_SUCCESS)
        {
            // Got a good file path, launch it
            returnValue = LaunchInstaller(installerFile, lpCmdLine);
        }
    }

    switch (returnValue)
    {
    case ERROR_FRAMEWORK_NOT_INSTALLED:
        // Bail
        break;
    case ERROR_INSTALLER_LAUNCH_FAILED:
        // Error message
        ShowMessageBox(IDS_INSTALLER_LAUNCH_FAILURE, MB_OK | MB_ICONERROR);
        break;
    case ERROR_INVALID_INSTALLER:
        // Error message
        ShowMessageBox(IDS_INVALID_INSTALLER, MB_OK | MB_ICONERROR);
        break;
    default:
        break;
    }

    return returnValue; // Just in case a caller wants to know what the result was
}

void LoadFrameworkGuidance()
{
    if (ShowMessageBox(IDS_PROMPT_LEARN_ABOUT_FRAMEWORK, MB_YESNO | MB_ICONQUESTION) == IDNO)
    {
        return;
    }

    ShellExecute(NULL, 0, (LPCWSTR)&CONSTANT_FRAMEWORK_LINK, 0, 0, SW_MAXIMIZE);
}

bool IsFrameworkInstalled()
{
    HKEY keyHandle;
    LSTATUS returnValue = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
        CONSTANT_FRAMEWORK_REGISTRY_KEY,
        0,
        KEY_READ,
        &keyHandle);

    if (keyHandle != NULL)
    {
        RegCloseKey(keyHandle);
        return true;
    }

    return false;
}

bool GetInstallerPath(LPTSTR commandLine, LPWSTR installerPath)
{
    DWORD len = GetModuleFileName(NULL, (LPWCH)installerPath, MAX_PATH * sizeof(wchar_t));

    if (len == 0)
    {
        return false;
    }

    while (len--)
    {
        if (installerPath[len] == '\\')
        {
            installerPath[len + 1] = -'\0';
            break;
        }
    }

    if (len > 0)
    {
        int bufferSize = MAX_PATH * sizeof(wchar_t);
        wchar_t buffer[MAX_PATH * sizeof(wchar_t)];

        if (wcscpy_s(buffer, bufferSize, installerPath) == ERROR_SUCCESS)
        {
            if (wcscat_s(buffer, bufferSize, commandLine) == ERROR_SUCCESS)
            {
                return wcscpy_s(installerPath, bufferSize, buffer) == ERROR_SUCCESS;
            }
        }
    }

    return false;
}

bool IsValidInstaller(LPWSTR filePath)
{
    DWORD attributes = GetFileAttributes(filePath);

    return attributes != 0xFFFFFFFF;
}

int LaunchInstaller(LPWSTR installerFile, LPTSTR lpCmdLine)
{
    STARTUPINFO startupInfo;
    PROCESS_INFORMATION process;

    ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
    ZeroMemory(&process, sizeof(PROCESS_INFORMATION));

    if (CreateProcess(installerFile, lpCmdLine, NULL, NULL, true, 0, NULL, NULL, &startupInfo, &process))
    {
        if (WaitForSingleObject(process.hProcess, INFINITE) == ERROR_SUCCESS)
        {
            return ERROR_SUCCESS;
        }

        CloseHandle(process.hThread);
    }

    return ERROR_INSTALLER_LAUNCH_FAILED;
}

int ShowMessageBox(UINT stringId, UINT type)
{
    wchar_t prompt[MAX_BUFFER_SIZE];
    wchar_t caption[MAX_BUFFER_SIZE];

    int len = LoadString(_Instance, stringId, (LPWSTR)&prompt, MAX_BUFFER_SIZE);

    if (len == 0)
    {
        return ERROR_UNEXPECTED;
    }

    len = LoadString(_Instance, IDS_APP_TITLE, (LPWSTR)&caption, MAX_BUFFER_SIZE);

    if (len == 0)
    {
        return ERROR_UNEXPECTED;
    }

    return MessageBox(NULL, (LPCWSTR)&prompt, (LPCWSTR)&caption, type); 

}       
Boot Strapper remains almost constant.  Aside from changing the registry key path and the link to the installation web page, this code hasn't changed in 10 years or so (except for the inclusion of the annotations on _tWinMain like "__in" and "__in_opt", which are part of Microsoft's SAL ( Source code Annotation Language ).

System Check

System Check performs a number of much more involved operations than Boot Strapper.  First, it is responsible for retrieving the catalog data from the web, building a set of objects from the catalog to be used later. Secondly, it scans the local machine for installed apps, which uses the same objects as the online catalog source but with additional information about the app's local context. Third, it presents a "gallery" of updates, which is a user interface by which the user can pick and choose which updates to apply.  Furthermore, the gallery shows all available products, so if they find something new, the can install the app immediately.  System Check contains all of the code required to download an update or install an app.

The very first thing it does, however, is check to see if there is a new version of itself--the update the updater problem.

Challenge I: Updating the Updater

System Check is just another app of sorts, right?  What makes updating it different than updating any other app?  The main difference is that it is running already.  If you are updating another application, and it happens to be running, simply prompt the user to save their work and close the app.  System Check waits for this to occur or the user chooses to abort the update, and then System Check carries on or not.

But, with System Check you can hardly ask it to close itself and somehow magically update itself.  That takes work.

System Check Update Flow
It's easier to view if you zoom your browser to about 130%.   There are four lanes: System Check, Data (Web & Local), File Replacement, and System Check once again.  The second System Check lane represents the latest running version.  Let's summarize:

  1. System Check v1.0.0.0 starts and downloads the catalog XML.  Regardless of whether a self-update occurs, this is the first action taken by System Check
  2. System Check compares its version with the version in the catalog.  If it is the same, the flow drops down to the 4th lane and regular update actions continue
  3. If the catalog version is higher, System Check downloads the newest version from the web
  4. System Check unpacks the zip file into a temporary folder and deletes the downloaded zip file
  5. Embedded in System Check is an executable called SW.Apps.FileReplace.exe (File Replace) which is compiled into System Check as an embedded binary file.  System Check unpacks File Replace into the temporary folder created above
  6. Not on the flow chart, but important, is System Check generates an "install.info" file in the temporary folder.  This file contains information about how File Replace behaves and what actions it performs. Specifically, it contains the path to the executable that called it, the full path to the setup it will execute, the flags contained in the catalog XML (flags drive file operations such as specifying a wildcard used to determine what types of files should be deleted so if there are user data files, those can be excluded), and the installer type.  For System Check, the type is always a ZIP archive because it contains just one file
  7. System Check executes File Replace with System Check's process ID and the location of the temporary folder on the command line
  8. File Replace starts, waits for System Check to close (the reason the process ID is passed on the command line), and then looks for "install.info" in the folder indicated on the command line
  9. File Replace performs the actions required to replace System Check (delete files and optionally subdirectories and files, copies the new version into the original folder and cleans up as well as it can
  10. Keep in mind that now we have another problem in that File Replace plus the temporary directory and install.info are on the drive and we don't want to leave these files laying around
  11. File Replace executes System Check, passings its process ID and location on the command line, and begins to close down
  12. A piece of code common to all my apps, not just System Check, is PerformCallerActions.  When System Check is started up, it passes the command line to PerformCallerActions, which knows how to do various things
  13. PerformCallerActions waits for File Replace to quit and then deletes all of the temporary files on the disk related to the update process
  14. System Check v1.0.0.1 continues with its normal update actions
  15. <The End/>

Challenge II: Self-Contained Executables (No External File References)

This topic is less of a challenge and more about knowing how to do it.  The idea is simple.  Create an executable that takes external references outside of the Base Class Libraries (BCL-those libraries that ship with .NET) and fashion a mechanism whereby your executable totes its references around with it as embedded resources instead of external files.  Why would you want to do this besides "its cool"?  When it comes to setup, in particular, I want my setup and update executables to rely on nothing on the machine except for the BCL.  I don't want to ship 5 files so I can setup an app that has 7 files.

AppDomain.CurrentDomain.AssemblyResolve to the rescue!  This event is fired each time the App Domain attempts to load an assembly using the assembly load sequence and fails.  Keep in mind it is important you create your reference with Specific Version = True otherwise the load sequence may find an assembly of another version, load it, and potentially cause all sorts of problems.  So, after .NET performs its duties attempting to locate the assembly by its fully qualified name (i.e. Ionic.Zip, Version=1.9.1.5, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c) and fails, it fires the AssemblyResolve event.

The handler will always look something like this:

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    if (args.Name.Equals(InstallerConstants.ZIP))
    {
        return Assembly.Load(Resources.Zip);
    }
 
    return null; (or do something else like throw up an error message but assuming because you implemented the handler, you intend to do something else :-)
}
internal const string ZIP = @"Ionic.Zip, Version=1.9.1.5, Culture=neutral, PublicKeyToken=edbe51ad942a3f5c";        
I always use a constant instead of a setting or something that can be changed in this sort of situation because if I change the version, it will demand recompilation to be successful.  Testing the assembly resolve handler will uncover the issue easier if you make this a const.

The purpose of the event is for you to take action when the assembly load sequence fails.  Sometimes the action is to throw your hands up and tell that user it is missing a required file.

Embedded File
However, you can be creative with it.  The inbound "args" includes the fully qualified name of the assembly it was unable to locate and its return value is an Assembly.  Assembly.Load has many overloads.  Of interest to us is the overload that accepts a byte array (byte[]) that contains the full bytes of a file image of the assembly.   Simply add a resource (.resx) file to your project and add the assembly as a file.  As you can see in the screenshot, it is stored as a byte array.

To ensure you are using the event and not the normal assembly binding mechanism, set Copy Local = False in the reference. The file will no longer be in the assembly's load sequence (unless it finds it somewhere else--beware of the GAC!) and the event will fire. If the embedded file is not a DLL provided through NuGet or some other mechanism but an executable you created, the easiest thing to do is add the executable to your Resources folder as a link to the build output of said executable. The compilation process will take care of grabbing it from it build location and embedding it in your executable.

As an interesting side note: System Check uses this mechanism for Ionic's ZIP library, HTML Agility Pack, and SW.Apps.FileReplacement.  File Replacement also has an embedded reference to Ionic ZIP, so we are embedding an executable with an embedded reference.  Not bad for a few hours work.

Product Update

Building the Update System

Up until now, we have talked about under-the-covers tips and tricks.  Let's take a look at a real-world example (mine).  Note that all of the code up to this point has been hanging around in my stack in various forms and levels of maturity.  Maybe 5-7 years ago, I built an update system called QuickPatch (and here).  It was too complex, trying to do too many things.  These days I use off-the-shelve installers like WIX instead of rolling my own (with one exception, which I will discuss in a follow-on post).  The only thing "invented" here is the approach to checking for new versions, kicking off the setup applications, and creating a user interface that I am satisfied with.  And, of course, updating the updater, which was fun.

Here is what mine in its earlier state looks like (with test data).



Gallery




I may follow up with any lesson's learned.


Hope you enjoyed this!






Colby-Tait

Disclaimer

Content on this site is provided "AS IS" with no warranties and confers no rights. Additionally, all content on this site is my own personal opinion and does not represent my employer's view in any way.