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

Tuesday, May 30, 2017

Moving On

Hello, friends and long-time readers. I started this blog in 2007 as venue for discussing my adventures in Project Server development.  Since then, it has survived several job changes, including a job at my alma mater, Microsoft. During that period, much of my blogging can be found here.

A lot has transpired since 2007, both on the professional front and personally.  About a month ago, I began a new blog to start fresh.  I will continue to talk Project Server development as the I to learn new things. That said, my new company, DeltaBahn, is taking me on new adventures and onto new technologies, specifically Microsoft Dynamics Project Service Automation, Field Service Automation, and Azure technologies related to machine learning such as predictive analysis, among other topics.  Plus, I have decided that the new blog will serve a broader community with articles going beyond my traditional specialty of project management. 

These changes are the beginning of an exciting time for me and I hope you will follow me to the new blog. The new blog can be found here.

I will, from time to time, come back to this blog with fresh content when I know the topic is hard-core Project Server development but you can expect limited content and I ask that you move on to the new blog with me.

Thanks for the many years of great dialog, shared learning, and the delight of serving a group of devout readers.

May the Force be with you.

Colby-Tait Africa

Tuesday, May 23, 2017

Random Code Snippets from Work and Play Volume I

Introduction

I have my career and I have my hobby, which blissfully happen to be the same.   I do different software design for work than for play
(typically).  I like to share, so while you won't see the heavy, important IP from work anywhere in this blog,  I will share code where
I see fit, so here is Snippets from Work and Play Volume I, which includes code from work and play intermixed to because it comes to
me when it comes to me.

 Enjoy!





Tired of all Those Try/Catch Blocks in your Code?

Particularly in a WinForm's app interacting with primarily the base class libraries and maybe one or two other 3rd party APIs, you end up wrapping your
code
in a try/catch block that accounts for the most likely types of exceptions thrown, backstopped by catching the general case. I have used snippet manager
and snippets for this for a long while until it struck me that an extension method is perfect
for this:


public static bool TryCatchMethod(this Action action, IWin32Window parentWindow)
{
    try
    {
        action();
        return true;
 
    }
    catch (ShibumiWareException exception)
    {
        MessageBoxFx.Warning(parentWindow, exception.CreateMessage(false), MessageBoxButtons.OK);
    }
    catch (SoapException exception)
    {
        MessageBoxFx.Warning(parentWindow, exception.CreateMessage(false), MessageBoxButtons.OK);
    }
    catch (WebException exception)
    {
        MessageBoxFx.Warning(parentWindow, string.Format(Resources.ERROR_SERVER_RETURNED_0_1, exception.Response, exception.CreateMessage(false)), MessageBoxButtons.OK);
    }
    catch (Exception exception)
    {
        MessageBoxFx.Warning(parentWindow, exception.CreateMessage(false), MessageBoxButtons.OK);
    }
 
    return false;
}
You may have methods that take parameters, etc, and you can do what you need to do to extend or overload this to make that work, but in the
app I am working has both a menu and tool bar item for every action, so I wrap the core of the action into a Perform method: PerformLoad, PerformLogin,
and so far forth.  So this works great, plus it then becomes an expression body with a Try/Catch blog :-)


private void MainForm_Load(object sender, EventArgs e) => ((Action)PerformLoad).TryCatchMethod(this)

Here is a Simple One - String to Stream

So you want a string turned into a stream?  We do it all the time. Here you go:

public static Stream ToStream(this string stringData)
{
    MemoryStream memoryStream = new MemoryStream();
    StreamWriter writer = new StreamWriter(memoryStream);
 
    writer.Write(stringData);
    writer.Flush();
    memoryStream.Position = 0;
 
    return memoryStream;
}
Use it like this:

using (Stream stream = _Template.ToStream())
{
    _ActiveDocument.Load(stream, Encoding.UTF8);
}
The astute may ask "what about disposing of the underlying stream?"   Looking at the implement in dotPeak, you can see .Close() is called on the inner stream, which in turn calls .Dispose() so don't fret:



Stream Dispose

Using NextPageToken with Blogger API .NET

public List<PostLoadPosts(string id)
{
    if (string.IsNullOrEmpty(id)) throw new ArgumentException(Resources.ERROR_VALUE_NULL_OR_EMPTYnameof(id));
 
    List<Post> posts = new List<Post>();
 
    PostsResource.ListRequest listRequest = _PostService.List(id);
 
    listRequest.MaxResults = 10;
            
    PostList pageList = listRequest.Execute();
 
    do
    {
        posts.AddRange(pageList.Items);
 
        listRequest.PageToken = pageList.NextPageToken;
        pageList = listRequest.Execute();
 
    } while (!string.IsNullOrEmpty(pageList.NextPageToken));
 
 
    return posts;
}



Registry Extensions (Long--I will Explain)

public static class RegistryExtensions
{
    public static List<NodeBuildTree(this string path, RegistryHive hive, bool unpackMultiStrings = falsebool unpackByteArrays = false)
    {
        if (string.IsNullOrEmpty(path))
        {
            throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
        }
 
        if (!Enum.IsDefined(typeof(RegistryHive), hive))
        {
            throw new InvalidEnumArgumentException(nameof(hive), (int)hive, typeof(RegistryHive));
        }
 
        RegistryKey registryKey = GetKeyFromHive(hive);
 
        List<Nodetree;
 
        if (path.Equals(registryKey.ToString(), StringComparison.OrdinalIgnoreCase))
        {
            tree = new List<Node>();
 
            BuildTree(registryKey, null, unpackMultiStrings, unpackByteArrays, ref tree);
            return tree;
        }
 
        using (RegistryKey subKey = registryKey.OpenSubKey(path, RegistryKeyPermissionCheck.ReadSubTree))
        {
            if (subKey == null)
            {
                throw new ArgumentException(string.Format(Resources.ERROR_0_IS_INVALID_PATH, path), nameof(path));
            }
 
            tree = new List<Node>();
 
            BuildTree(subKey, null, unpackMultiStrings, unpackByteArrays, ref tree);
        }
 
        return tree;
    }
 
    public static RegistryHive RegistryGetHive(this string path, out string parsedComponent)
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
 
        parsedComponent = string.Empty;
 
        int i = path.IndexOf(Path.DirectorySeparatorChar, 0);
 
        string hive;
 
        if (i == -1)
        {
            hive = path;
        }
        else
        {
            hive = path.Substring(0, i);
            parsedComponent = path.Substring(0, i + 1);
        }
 
        if (hive.Equals(@"HKEY_CLASSES_ROOT"StringComparison.OrdinalIgnoreCase))
        {
            return RegistryHive.ClassesRoot;
        }
 
        if (hive.Equals(@"HKEY_CURRENT_USER"StringComparison.OrdinalIgnoreCase))
        {
            return RegistryHive.CurrentUser;
        }
 
        if (hive.Equals(@"HKEY_LOCAL_MACHINE"StringComparison.OrdinalIgnoreCase))
        {
            return RegistryHive.LocalMachine;
        }
 
        if (hive.Equals(@"HKEY_USERS"StringComparison.OrdinalIgnoreCase))
        {
            return RegistryHive.Users;
        }
 
        if (hive.Equals(@"HKEY_CURRENT_CONFIG"StringComparison.OrdinalIgnoreCase))
        {
            return RegistryHive.CurrentConfig;
        }
 
        if (hive.Equals(@"HKEY_USERS"StringComparison.OrdinalIgnoreCase))
        {
            return RegistryHive.Users;
        }
 
        throw new ArgumentOutOfRangeException(string.Format(Resources.ERROR_INVALID_HIVE_0, hive));
    }
 
    public static List<stringRegistryGetKeySubkeys(this string path, RegistryHive hive)
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
        if (!Enum.IsDefined(typeof(RegistryHive), hive)) throw new InvalidEnumArgumentException(nameof(hive), (int)hive, typeof(RegistryHive));
 
        RegistryKey registryKey = GetKeyFromHive(hive);
 
        using (RegistryKey subKey = registryKey.OpenSubKey(path, RegistryKeyPermissionCheck.ReadSubTree))
        {
            if (subKey == null)
            {
                throw new ArgumentException(string.Format(Resources.ERROR_0_IS_INVALID_PATH, path), nameof(path));
            }
 
            return subKey.GetSubKeyNames().ToList();
        }
    }
 
    public static Dictionary<stringRegistryValueKindRegistryGetKeyValueInfo(this string path, RegistryHive hive)
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
        if (!Enum.IsDefined(typeof(RegistryHive), hive)) throw new InvalidEnumArgumentException(nameof(hive), (int)hive, typeof(RegistryHive));
 
        RegistryKey registryKey = GetKeyFromHive(hive);
 
        using (RegistryKey subKey = registryKey.OpenSubKey(path, RegistryKeyPermissionCheck.ReadSubTree))
        {
            if (subKey == null)
            {
                throw new ArgumentException(string.Format(Resources.ERROR_0_IS_INVALID_PATH, path), nameof(path));
            }
 
            List<string> valueNames = subKey.GetValueNames().ToList();
 
            return valueNames.ToDictionary(valueName => valueName, valueName => subKey.GetValueKind(valueName));
        }
    }
 
    public static List<stringRegistryGetKeyValues(this string path, RegistryHive hive)
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
        if (!Enum.IsDefined(typeof(RegistryHive), hive)) throw new InvalidEnumArgumentException(nameof(hive), (int)hive, typeof(RegistryHive));
 
        RegistryKey registryKey = GetKeyFromHive(hive);
 
        using (RegistryKey subKey = registryKey.OpenSubKey(path, RegistryKeyPermissionCheck.ReadSubTree))
        {
            if (subKey == null)
            {
                throw new ArgumentException(string.Format(Resources.ERROR_0_IS_INVALID_PATH, path), nameof(path));
            }
 
            return subKey.GetValueNames().ToList();
        }
    }
 
    public static T RegistryGetValue<T>(this string path, RegistryHive hive, string value, T defaultValue)
    {
        if (!Enum.IsDefined(typeof(RegistryHive), hive)) throw new InvalidEnumArgumentException(nameof(hive), (int)hive, typeof(RegistryHive));
        if (string.IsNullOrEmpty(path)) throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
        if (string.IsNullOrEmpty(value)) throw new ArgumentException(@"Value cannot be null or empty."nameof(value));
 
        RegistryKey registryKey = GetKeyFromHive(hive);
 
        using (RegistryKey subKey = registryKey.OpenSubKey(path, RegistryKeyPermissionCheck.ReadSubTree))
        {
            if (subKey == null)
            {
                return defaultValue;
            }
 
            object valueObject = subKey.GetValue(value);
 
            if (valueObject == null)
            {
                return defaultValue;
            }
 
            return (T)Convert.ChangeType(valueObject, typeof(T));
        }
    }
 
    public static bool RegistryKeyExists(this string path, bool parseHive = trueRegistryHive registryHive = RegistryHive.LocalMachine)
    {
        if (string.IsNullOrEmpty(path)) throw new ArgumentException(@"Value cannot be null or empty."nameof(path));
 
        if (parseHive)
        {
            string parsedComponent;
 
            registryHive = path.RegistryGetHive(out parsedComponent);
 
            if (!string.IsNullOrEmpty(parsedComponent))
            {
                path = path.Replace(parsedComponent, string.Empty);
            }
        }
        else
        {
            if (!Enum.IsDefined(typeof(RegistryHive), registryHive))
            {
                throw new InvalidEnumArgumentException(nameof(registryHive), (int)registryHive, typeof(RegistryHive));
            }
        }
 
        RegistryKey key = GetKeyFromHive(registryHive);
 
        if (key == null)
        {
            return false;
        }
 
        if (path.Equals(key.NameStringComparison.OrdinalIgnoreCase))
        {
            return true;
        }
 
        using (RegistryKey subKey = key.OpenSubKey(path, false))
        {
            if (subKey == null)
            {
                return false;
            }
        }
 
        return true;
    }
 
    public static bool RegistrySetBool(this bool b, RegistryHive hive, string path, string value)
    {
        RegistryKey registryKey = GetKeyFromHive(hive);
 
        RegistryKey subKey = null;
 
        try
        {
            subKey = registryKey.OpenSubKey(path, RegistryKeyPermissionCheck.ReadWriteSubTree) ?? 
                registryKey.CreateSubKey(path, RegistryKeyPermissionCheck.ReadWriteSubTree);
 
            if (subKey == null)
            {
                return false;
            }
 
            subKey.SetValue(value, b);
 
            return true;
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
            return false;
        }
        finally
        {
            if (subKey != null)
            {
                subKey.Close();
                subKey.Dispose();
            }
        }
    }
 
    public static Type Translate(this RegistryValueKind value)
    {
        switch (value)
        {
            case RegistryValueKind.MultiString:
            case RegistryValueKind.ExpandString:
            case RegistryValueKind.String:
                return typeof(string);
 
            case RegistryValueKind.Binary:
                return typeof(byte[]);
 
            case RegistryValueKind.DWord:
                return typeof(int);
 
            case RegistryValueKind.QWord:
                return typeof(long);
 
            case RegistryValueKind.Unknown:
            case RegistryValueKind.None:
                return typeof(object);
 
            default:
                throw new ArgumentOutOfRangeException(nameof(value), value, null);
        }
    }
 
    private static void BuildTree(RegistryKey parentKey, 
        Node parent, 
        bool unpackMultiStrings, 
        bool unpackByteArrays, 
        ref List<Node> tree)
    {
        Node node = new Node(parent, parentKey.Name);
 
        tree.Add(node);
 
        List<string> valueNames = parentKey.GetValueNames().ToList();
 
        for (int index = 0; index < valueNames.Countindex++)
        {
            string valueName = valueNames[index];
 
            object value = parentKey.GetValue(valueName);
 
            if (value == null)
            {
                continue;
            }
 
            if (value.GetType() == typeof(string[]))
            {
                if (unpackMultiStrings)
                {
                    string[] strings = (string[])value;
 
                    value = strings.Length == 1 ? strings[0] : strings.Concatenate(strings.Length@",");
                }
                else
                {
                    value = @"string[]";
                }
            }
 
            if (value.GetType() == typeof(byte[]))
            {
                value = unpackByteArrays ? BitConverter.ToString((byte[])value) : @"byte[]";
            }
 
            if (value is string && (string)value == string.Empty)
            {
                value = @"[No Value]";
            }
 
            node.ValueCollection.Add(valueName, parentKey.GetValueKind(valueName).Translate(), value);
        }
 
        foreach (string childrenKeyName in parentKey.GetSubKeyNames().ToList())
        {
            RegistryKey childKey = null;
 
            try
            {
                childKey = parentKey.OpenSubKey(childrenKeyName, RegistryKeyPermissionCheck.ReadSubTreeRegistryRights.ReadKey | RegistryRights.QueryValues);
 
                if (childKey != null)
                {
                    Node childNode = new Node(node, childKey.Name);
 
                    node.Children.Add(childNode);
 
                    BuildTree(childKey, childNode, unpackMultiStrings, unpackByteArrays, ref tree);
                }
            }
            catch (SecurityException exception)
            {
                DebugFx.WriteLine(string.Format(@"Security Exception: {0}  Key ='{1}'.", exception.CreateMessage(false), childrenKeyName));
            }
            catch (IOException exception)
            {
                DebugFx.WriteLine(string.Format(@"IO Exception: {0}  Key ='{1}'.", exception.CreateMessage(false), childrenKeyName));
            }
            catch (Exception exception)
            {
                DebugFx.WriteLine(string.Format(@"General Exception: {0}  Key ='{1}'.", exception.CreateMessage(false), childrenKeyName));
            }
            finally
            {
                if (childKey != null)
                {
                    childKey.Close();
                    childKey.Dispose();
                }
            }
        }
    }
 
    private static RegistryKey GetKeyFromHive(RegistryHive hive)
    {
        RegistryKey registryKey;
 
        switch (hive)
        {
            case RegistryHive.ClassesRoot:
                registryKey = Registry.ClassesRoot;
                break;
 
            case RegistryHive.CurrentUser:
                registryKey = Registry.CurrentUser;
                break;
 
            case RegistryHive.LocalMachine:
                registryKey = Registry.LocalMachine;
                break;
 
            case RegistryHive.Users:
                registryKey = Registry.Users;
                break;
 
            case RegistryHive.CurrentConfig:
                registryKey = Registry.CurrentConfig;
                break;
 
            case RegistryHive.PerformanceData:
            case RegistryHive.DynData:
                throw new ArgumentOutOfRangeException(nameof(hive), hive, null);
            default:
                throw new ArgumentOutOfRangeException(nameof(hive), hive, null);
        }
 
        return registryKey;
    }
}
These extensions allow you to do things like the following:


public void RegExtensionsExamples()
{
    // First, the simple stuff ==>
 
    // Get the list of sub keys of a registry key
    List<string> subkeys = @"\SOFTWARE\ShibumiWare".RegistryGetKeySubkeys(RegistryHive.LocalMachine);
 
    // Of course most of the time your key data will be in a variable so let's get fancier:
           
    // Here is full key.
    string dellCommandUpdateKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\DELL\CommandUpdate";
    string parseResults;
 
    // Get the hive from the key, since we are too lazy to shorten it and provide it as a value
    RegistryHive hive = dellCommandUpdateKey.RegistryGetHive(out parseResults);
           
    // Get the version OR a default value 
    string version = dellCommandUpdateKey.RegistryGetValue<string>(hive, "ProductVersion"default(string));
 
    // Fancy, fancy==>
 
    // Builds a tree of the subgraph of ShibumiWare's software registry key
    List<Node> swTree = @"\SOFTWARE\ShibumiWare".BuildTree(RegistryHive.LocalMachine);
 
    // Builds a tree of the subgraph of Microsoft's software registry key 
    List<Node> softwareTree = @"\SOFTWARE\Microsoft".BuildTree(RegistryHive.LocalMachine);
 
    // The tree contains all sorts of goodies
    DisplayTree(softwareTree[0]);
 
    // Here are a complete list:
    dellCommandUpdateKey.RegistryGetValue();
    dellCommandUpdateKey.RegistryGetKeySubkeys();
    dellCommandUpdateKey.RegistryGetKeySubkeys();
    dellCommandUpdateKey.RegistryGetKeyValueInfo();
    dellCommandUpdateKey.RegistryGetHive();
    dellCommandUpdateKey.RegistryKeyExists();
}
 
private void DisplayTree(Node parentNode)
{
    foreach (KeyValuePair<stringTuple<Typeobject>> kvp in parentNode.ValueCollection)
    {
        Debug.WriteLine("Key {0} contains value {1} of type {2}", kvp.Key, kvp.Value.Item2, kvp.Value.Item1);
    }
 
    foreach (Node childNode in parentNode.Children)
    {
        DisplayTree(childNode);
    }
}
Now that I think about it, the reason I put most of these
extensions together was for a project called Registry Analyst, that gave you a better graphical UI into the Registry and also a command line tool for
exporting registry data for search purposes.  Searching text is much faster than searching through the registry via Win32 and many things don't change
plus you could easily do a diff between snapshots to see what changed--maybe between instances of running a particular program or even comparing the
registry from one machine to the next.  It is a lot more readable and faster than using RegEdit to do the export.   Anyway, maybe I
will get that project put together and posted.


 









Some Handy Extension Methods

Here are three, all extension methods. The first changes the dirty gray color of a Multiple Document
Interface client area into what ever color you like. The second will invoke events by name on any
object.    The third shows or hides the bevel (3D-sink) of the MDIClient area:

Pretty MDI Client

public static class ControlExtensions
{
    public static void MdiClientSetBackgroundColor(this Form form, Color color)
    {
        MdiClient control = form.Controls.OfType<MdiClient>().FirstOrDefault();
 
        if (control == null)
        {
            return;
        }
 
        control.BackColor = color;
 
    }
 
    public static void Raise<TEventArgs>(this object source, string eventName, TEventArgs eventArgs)
    {
        EventInfo eventInfo = source.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
 
        if (eventInfo == null)
        {
            return;
        }
 
        FieldInfo fieldInfo = source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
 
        if (fieldInfo == null)
        {
            return;
        }
 
        object eventDelegateObject = fieldInfo.GetValue(source);
 
        if (eventDelegateObject == null || eventDelegateObject.GetType() != typeof(MulticastDelegate))
        {
            return;
        }
 
        MulticastDelegate eventDelegate = (MulticastDelegate)eventDelegateObject;
 
        foreach (Delegate handler in eventDelegate.GetInvocationList())
        {
            if (handler?.Method != null) handler.Method.Invoke(handler.Targetnew[] { source, eventArgs });
        }
    }
}
public static bool SetBevel(this Form form, bool show)
{
    MdiClient control = form.Controls.OfType<MdiClient>().FirstOrDefault();
 
    if (control == null)
    {
        return false;
    }
 
    int windowLong = Native.GetWindowLong(control.HandleNative.GWL_EXSTYLE);
 
    if (show)
    {
        windowLong |= (int)Native.WindowStyleEx.WS_EX_CLIENTEDGE;
    }
    else
    {
        windowLong &= (int)~Native.WindowStyleEx.WS_EX_CLIENTEDGE;
    }
 
    Native.SetWindowLong(control.HandleNative.GWL_EXSTYLEwindowLong);
 
    Native.SetWindowPos(control.HandleIntPtr.Zero, 0, 0, 0, 0,
        (uint) (Native.SetWindowPosOptions.SWP_NOACTIVATE |
                Native.SetWindowPosOptions.SWP_NOMOVE |
                Native.SetWindowPosOptions.SWP_NOSIZE |
                Native.SetWindowPosOptions.SWP_NOZORDER |
                Native.SetWindowPosOptions.SWP_NOOWNERZORDER |
                Native.SetWindowPosOptions.SWP_FRAMECHANGED));
 
    return true;
}



New Download - A Browser in A Process

Introduction

This application allows for setting up shortcuts to your favorite sites without launching a browser
directly.  The requirement to have such a thing sprung from my work on using Google's oAuth
and Blogger APIs inside a Windows app.  See here for more information.



As a side-effect, I ended up needing to design a "browser process" that's purpose was to
launch a web page and act as a go-between the client and server.


It is useful for creating shortcuts to your favorite websites and have them run outside the browser. 
For me, I am constantly generating more and more tabs in Chrome or IE, which I invariably get overwhelmed
by so I just close the browser, forgetting I was on YouTube listening to a song or a social site
in the middle of a conversation, so I have to start over.


This little app looks nothing like a browser, except for a single page
(you can still go to other sites, navigate, use the context menu, etc., but you won't inadvertently
shut it down with your other tab-bloated browsers


Help

It is pretty easy to use, but I must excuse the strange symbols used in the command switches. For various
technical resources related to the work I was doing on oAuth, I had to try to find characters that weren't
likely to be returned by an authentication request


You can drive this from the command line or, more likely, setup shortcuts and pin them to your Start
screen or menu


Basic Switches

To open the application and connect to a specific webpage (url address)

[InstallDrive]:\[InstallDirectory]\SW.BrowserProcess.exe ~url©http://wwww.google.com ~standalone



Don't forget the ~standalone switch!

Also, just to make it easy for you, the '©' symbol is easily achieved on your keyboard:

  1. Make sure Num Lock is on
  2. Press Alt + 0169 on the keypad, which if you go look here:  http://www.alt-codes.net/, you can find many
    more cool tricks

Advanced Switches

The following switches I would consider "experimental" for you because they were designed specifically
for my needs in oAuth, but here goes


[InstallDrive]:\[InstallDirectory]\SW.BrowserProcess.exe ~url©http://wwww.google.com ~attach©[WINDOW_HANDLE]



The WINDOW_HANDLE is either obtained via Win32 API or WinForms (if you are nodding off now, just quit).


This switch makes the browser window a child of the window represented by WINDOW_HANDLE.
This is tricky because its not really a good idea to have two message pumps in one process that
aren't setup
that way to begin with (lot's of WM_PAINT problems, MDI clients spawned this way don't respond to
parent
MDI directives like cascade or minimize all, because the parent really doesn't know it exists. BAD
Colby.


If you add the~mdi switch, the browser process will attempt to make itself
an MDI (multiple Document Interface) document of the parent process.


I figure since I included these experimental switches, I should show you an example of what this looks
like


Embedded Browser

I have a forthcoming post on this, so I will provide details there but what you are seeing on the right
is the browser process running as an MDI client inside another application, waiting for me to deny or
allow access to my blog.


Embedded Browser




Enjoy

Shibumi

Create Program Shortcut Through Reflection



If you have a totally self-contained, relies on ZERO references not in .NET's BCL or a COM component, here is how you create a program shortcut using reflection.  I will post later on how to resolve references to components that you do need (doesn't work with COM objects or the interop libraries created for it) so that you load your dependent references from embedded resources.





Shibumi

Google oAuth and Blogger API Deep Dive Part I



Overview

I am going to cover quite a bit in this post regarding using Google's Blogger v3.0 API and achieving specific results for a personal project of mine.  I will cover the internals of Google's oAuth
v2.0 implementation in some detail, discuss how to build a browser component that with browser isolation
used in a the project's WinForms app plus other commentary.   So far this is Part I in a series.

But first I am going to discuss the experience of blog authoring with Blogger given the type of writer I am, the tools I prefer, and what started this project I call "ShibumiWare Blog", which is the combination of my blog and a Windows App.

Blogger versus Wordpress and Other Platforms

I choose Blogger years and years ago.  I am not going to change because I like their APIs, their domain management facility is fantastic, and I have learned the system well over time.  At times I have almost quit the Blogger platform but frankly I just have too much invested in it.

I like the widgets, the simplicity of the templates I have worked with, and the deep level of customization you can perform to make it look just the way you like it.  In fact, and this is part of the problem and part of the fun, here is a screen shot of what it looks like when I write a post:




Click to pop-it out. It is
a perfect mess and I love it. Microsoft used to have a standards-based designer suite called Microsoft Expression that I really liked, but it has been discontinued. What you are looking at is Visual Studio 2017 with several extensions installed:




Anyway, it's kind of a hassle because at the end of editing, I cut and paste the contents of an inner portion of the page (the rest of the content essentially mocks-up what the blog looks like so I don't do something nuts that won't fit the style or dimensions of the blog template) into Blogger's
editor and post. 

I love side-projects and I have a ton of them so I thought I should
look at simply uploading the contents directly.  I don't want a full-blown
editor, because while complicated for many users, this combination of Visual Studio and extensions is powerful and suites me just fine.

Enter the problem: How to use Google's APIs to post the contents as a new blog post. 

 

Introducing Google Blogger API v3.0

I am not going to regurgitate the API documentation but I am going to highlight portions of the SDK you need to pay close attention to, including the use of code samples for purposes of clarity. 
The documentation will give you detailed information: Blogger API Doc.

The first task is to obtain an API key. Click here to open the credentials page.  The user interface is fairly intuitive so follow the flow until you are given the chance to "Create  credentials":

credentials

You have several options, but for my purposes, I needed an OAuth client ID, which would allow me to ask users if I can perform particular actions on their behalf within the application.  Once you have created it, you can download the associated JSON file with the details:

 {

  "installed": {

    "client_id""[FAKE] 1234456793-yadayadayadayadayadayada.apps.googleusercontent.com",

    "project_id""[FAKE] projectname",

    "auth_uri""https://accounts.google.com/o/oauth2/auth",

    "token_uri""https://accounts.google.com/o/oauth2/token",

    "auth_provider_x509_cert_url""https://www.googleapis.com/oauth2/v1/certs",

    "client_secret""[FAKE] tYc41IAIZO3SU75h/UFTSrir",

    "redirect_uris": [ "urn:ietf:wg:oauth:2.0:oob""http://localhost" ]

  }

In the API these are referred to as "Google Secrets" and you should treat them that way, although keep in mind that just having the secrets still requires that whatever you come up with to do with the secrets, the user still has to agree with.  See the
Crypto Available
post which contains a tool for encrypting and decrypting data, which is useful in just this sort of problem.

More on the API key and Google Secrets later. 

The next task is to configure your conformation screen, which will be displayed to the user when you app requests access to use the Blogger API (or any other Google API for that matter) on their behalf:


 

After you are done with these two tasks, you have the basics to get started.

 

Blogger API v2.0 & OAuth v3.0

First, let's talk briefly about what I started out trying to accomplish.  I wanted to use my Google Secrets to allow a user
(me) access to a blog (mine) and explore the Blogger API while achieving my primary objective, which is posting automatically.  This meant using Google's oAuth implementation as well.   My app is a Windows app.  Keep that mind and here is a early version of th
user interface:



App

Authentication

 

I am going to show you two flows, one that uses the standard Google oAuth behavior, and another flow I figured out that makes a lot more sense for a Windows app. 
The second flow appears in Part II of this series.

The first flow starts with a click of the Login button (button in upper left corner):

_Blogger = new Blogger(Resources.GoogleBloggerv3Secrets);
 
if (Settings.Default.UseEmbeddedCodeReceiever)
{
    string browserProcessPath = Path.Combine(Application.StartupPathProductConstants.SW_BROWSER);
 
    _Blogger.UseEmbeddedCodeReceiver = true;
    _Blogger.CodeReceiverPath = browserProcessPath;
    _Blogger.CodeReceiverArguments = @"~attach";
}
else
{
    _Blogger.UseEmbeddedCodeReceiver = false;
}
 
_Login = _Blogger.Login(Environment.UserName);

Notice the Blogger object is instantiated with my Google Secrets, which has been decrypted and is ready to go.  Based on settings, I choose not to use the "Embedded Code Receiver", which I will explain later.

NOTE:  When you use Google's oAuth implementation, it uses a FileDataStore object
to store the token it receives when the user accepts the application's request for access to the blog.  The samples that I found showed lot's of "login" examples that used either the user's gmail account to "login" with, but how would they know that if the user isn't
prompted for it and secondly, who cares?  The user has to accept the application, which then gives you access to this information anyway.  The most important thing is there is one token stored for each user on the device. 

In the case of Windows, the storage location I chose is [OS-Drive]:\Users\[user
name]\AppData\Roaming\ShibumiWare\GoogleBloggerv3.  You could use anything you want really and the examples are not clear on this.  Even the open source LiveWriter developers appeared confused about how this worked.  I cloned their GitHub thinking it would get me where I wanted to go.

It helped but it was weird and unclean.

Let's follow the regular, non-embedded code receiver flow and see what happens:

First the code goes through a function to see if the token exits and whether it is still valid (it has not expired): 

EnsureCredentials(new IdentityToken(identifier, string.Emptynull));

Inside the local store is examined to see if a valid token exists and whether it is valid:

GoogleAuthorizationCodeFlow flow = null;
 
try
{
    if (_SecretStream == null)
    {
        _SecretStream = GetClientSecretsStream();
    }
 
    GoogleAuthorizationCodeFlow.Initializer initializer = new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecretsStream = _SecretStream,
        DataStore = GetCredentialsDataStoreForIdentty(),
        Scopes = new List<string>() { BloggerService.Scope.Blogger }
    };
 
    flow = new GoogleAuthorizationCodeFlow(initializer);
 
    Task<TokenResponse> loadTokenTask = flow.LoadTokenAsync(identityToken.IdCancellationToken.None);
 
    loadTokenTask.Wait();
 
    if (loadTokenTask.IsCompleted)
    {
        // We were able re-create the user credentials from the cache.
        userCredential = new UserCredential(flow, identityToken.Id, loadTokenTask.Result);
        token = loadTokenTask.Result;
    }
}
finally
{
    flow?.Dispose();
}

The GoogleAuthorizationCodeFlow
object holds the various data that is required to perform this "flow" like the Google Secrets, the file data store and the "Scopes", which are API scopes--like Blogger, or Blogger-Read-only, or any scope available for the API you are working with.  Here I just use the
BloggerService
.Scope.Blogger scope.  These flows do everything async.  The call to flow.LoadTokenAsync is just a wrapper around
FileDataStore.GetAsync.

In this situation, I have removed the token for the purposes of demonstrating a complete path through the authorization flow.   What happens next is where it starts to get interesting:


if (!IsValidToken(token))
{
    Task<UserCredential> authorizationTask = GetOAuth2AuthorizationAsync(identityToken.IdCancellationToken.Nonenull);
 
    authorizationTask.Wait();
 
    if (authorizationTask.IsCompleted)
    {
        userCredential = authorizationTask.Result;
        token = userCredential?.Token;
    }
}

GetOAuth2AuthorizationAsync is the important call:

public Task<UserCredentialGetOAuth2AuthorizationAsync(string userId,
 CancellationToken taskCancellationToken, BrowserCoreCodeReceiver embeddedCodeReceiver)
{
    _SecretStream = GetClientSecretsStream();
 
    if (UseEmbeddedCodeReceiver)
    {
        if (CodeReceiver == null)
        {
            CodeReceiver = embeddedCodeReceiver;
        }
 
        return GoogleWebAuthorizationBroker.AuthorizeAsync(
            GoogleClientSecrets.Load(_SecretStream).Secrets,
            new List<string>() { BloggerService.Scope.Blogger },
            userId,
            taskCancellationToken,
            GetCredentialsDataStoreForIdentty(), CodeReceiver);
    }
 
    return GoogleWebAuthorizationBroker.AuthorizeAsync(
        GoogleClientSecrets.Load(_SecretStream).Secrets,
        new List<string>() { BloggerService.Scope.Blogger },
        userId,
        taskCancellationToken,
        GetCredentialsDataStoreForIdentty());
}

We do some flow control regarding the use of the embedded code receiver and choose the method that does not include a code receiver. 
Here is what I get (with a very cleaned up desktop--usually I have 20+ windows open, which made this worse because it was a randomly popping up an instance of Chrome)

Desktop

Okay, so this isn't that bad, except it got me wondering what was going on in the background because here is what we know about oAuth 2.0's flow:

oAuth 2. Flow



My blog tool is the client.  It first looks to the local store for
the token representing the authorization grant.  It doesn't find it so it goes to the authorization server (https://accounts.google.com/o/oauth2/v2/) and asks for authorization, which the server will give depending if you select Deny or Allow. 
That access token is persisted to the local FileDataStore.  Subsequent calls to the protected resource (this case, the blog) are granted by having obtained authorization and persisted the access token locally.

So let's accept and see what happens next.  While this browser page is open (or the browser instance is open) but the user hasn't accepted or denied, the authorization flow Wait:

Task<UserCredential> authorizationTask = GetOAuth2AuthorizationAsync(identityToken.Id
CancellationToken.Nonenull);
 
authorizationTask.Wait();
 
if (authorizationTask.IsCompleted)
{
    userCredential = authorizationTask.Result;
    token = userCredential?.Token;
}
 
if (!IsValidToken(token))
{
    throw new ShibumiWareException(@"Invalid token"); // TODO better error message
}
 
identityToken.Token = userCredential;

Meanwhile, over in the browser notice two things, first we have a very lame "close the window" message and, more importantly, that message is being served up by localhost:55804/authorize. I don't have IIS installed on this machine with port 55804
open so what's the deal?



Now I am really curious, plus, and here is where the new goal comes to mind: I want to know what is going on in the background so I can design the authorization system to NOT jump out of my Windows app.

Time for some serious spelunking, but first a disclaimer:


  1. What I am about to show you is probably not out in the documentation with fine-grained implementation details
  2. The techniques I used to get at the details are not illegal, you just have to know what you are doing
  3. Do not rely on this data, which are implementation details and may change over time
  4. If I am wrong about my interpretation, I take no responsibility for any code incorrectly written because you took advantage of this information.

Deep Dive into Google's oAuth 2.0 Implementation

One this is certain, the API launches a browser and accesses Google's oAuth service.  The API waits for a response, but itsn't waiting from the browser process to close because in experimentation, it opened it as a new tab in Chrome (that is still process isolation, but that's harder
to wait on,
I didn't think they did it that way, and it also led me to another experiment in building my own browser with process isolation, just because I endued up curious about that--I get distracted).

Remember when I said spelunking?  Here is cave number one:

As-Is Implementation

How does the API launch a browser, wait for a response, and then seemingly create a response to the user ("we have the verification code, you may close this window, yadayda") locally on some weird port?

This is what I found:

browserOpenedOk = this.OpenBrowser(url1);
private bool OpenBrowser(string url)
{
  Process.Start(url);
  return true;
}

Yes, that is correct.  It passes the authorization URL to Process.Start, which looks at the protocol or file extension to find the handler for the http protocol and launches the default  browser: in my case, Google Chrome.  The next important call is the following:

authorizationCodeResponseUrl = await this.GetResponseFromListener(listener).ConfigureAwait(false);

The "listener" in this case is an HTTPListener


private HttpListener StartListener()
{
  HttpListener httpListener = new HttpListener();
  httpListener.Prefixes.Add(this.RedirectUri);
  httpListener.Start();
  return httpListener;
}

Notice that one of the prefixes (which can be a full URL or a pattern) is RedirectUri.  Okay, one problem figured out.  What's happening based on looking at data from Fiddler is that a local TCP listener is opened on that weird port.  When the authentication server is sent the
authorization URL, it includes this redirect URL, which once the Allow button is selected, the browser will "open" that address, which is really just a stream that can be written to like Response.Write).  That is how the message "the code has been accepted" message gets written
to the browser.

The this.RedirectUri is actually a computed property, not a property backed by a field or an auto property:


public string RedirectUri
{
  get
  {
    if (!string.IsNullOrEmpty(this.redirectUri))
      return this.redirectUri;
    return this.redirectUri = string.Format("http://localhost:{0}/authorize/", (objectLocalServerCodeReceiver.GetRandomUnusedPort());
  }
}

One clue here I took note of is "LocalServerCodeReceiver.GetRandomUnusedPort()".  Hmm, local code receiver.  Does that mean there are other types of code receivers?

The property does much more than that.  The call to above starts a TCPListener at an unused port, which is attached to the authorization querystring on the url retrieved from the authorization server:

private static int GetRandomUnusedPort()
{
  TcpListener tcpListener = new TcpListener(IPAddress.Loopback, 0);
  try
  {
    tcpListener.Start();
    return ((IPEndPoint) tcpListener.LocalEndpoint).Port;
  }
  finally
  {
    tcpListener.Stop();
  }
}

I will cut to the chase so we can move on.  What happens next, without showing you even more code, is the request is fired off to Google's authorization server, which includes all of the information required to figure out what consent screen and API key to use (remember the Google Secrets?). 
The user is redirected to the consent screen where they hopefully accept, which processes the acceptance, generates a token, sticks it onto the query string and redirects the browser to the local address, where the query string is picked apart, the token is created (its a JSON string) and saved to
the local store.  Boom!

You have authorized my app to access your blog!

As you can see in that fancy FileDataStore there is a token from Google.


Open it open and this is what you see:

{
  "access_token""[FAKE] ya29.GlsoBPScQfThMgV7Vf8d9TbwKwAU2VAkcLlCbsEFu4pNNZtKCzrYGJahTvl",
  "token_type""Bearer",
  "expires_in": 3600,
  "refresh_token""[FAKE]GMLEOnd3suxfsdfser323425AhoYwK4SX5PBhZcJLgD-E",
  "Issued""2017-04-09T14:01:55.016-05:00",
  "IssuedUtc""2017-04-09T19:01:55.016Z"
}

What's next?   The first thing I wanted to see if I could accomplish is to keep the user inside the blog application without popping out into a browser window to give my app permission to work with the blog.

Remember I said to take not of the LocalCodeReceiver?  As it turns out, the GoogleWebAuthorizationBroker has an overload of AuthorizeAsync that takes an ICodeReceiver object, which at the time I surmised would replace the current OpenBrowser technique used so I could keep the user in the app
for authorization:

public static async Task<UserCredentialAuthorizeAsync(ClientSecrets clientSecrets,
    IEnumerable<string> scopes,
    string user,
    CancellationToken taskCancellationToken,
    IDataStore dataStore = null,
    ICodeReceiver codeReceiver = null)

This looks promising so I go off into cave # 2

Replacing the Local Code Receiver with My Own

This one took me a little while to track down but when I found it, it is an amazingly simple interface:

public interface ICodeReceiver
{
    string RedirectUri { get; }
 
    Task<AuthorizationCodeResponseUrlReceiveCodeAsync(AuthorizationCodeRequestUrl url, CancellationToken taskCancellationToken);
}

Because I NuGe'ted the Google APIs into my solution, I created a new class that implements ICodeReceiver, with a couple of additions .  My general desire was to implement the
code
receiver using .NET's WebBrowser class, but two things made doing that directly impractical and ugly.  The impractical item was that due to the threading model and the call sequences in async, and running on the UI thread, made it a mess to synchronize.  Second, the ugly part, is
that I am implementing a class library for extending and wrapping the Google APIs and I hate to include System.Windows.Forms--» its just ugly.

This Concludes Part I



Again, if you are seeing this as a post on Facebook, don't respond there.  I rarely pay attention.  If you have comments you can email me directory at shibumi-at-shibumiware-dot-com or leave a comment on the blog


For completeness, here is basically the implementation of FileDataStore, just so you can see there is nothing special going on here:

using System;
using System.IO;
using System.Threading.Tasks;
 
namespace Google.Apis.Util.Store
{
    public class FileDataStore
    {
        private readonly string folderPath;
 
        public FileDataStore(string folder, bool fullPath = false)
        {
            folderPath = fullPath ? folder : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), folder);
            if (Directory.Exists(folderPath))
                return;
 
            Directory.CreateDirectory(folderPath);
        }
 
        public string FolderPath
        {
            get
            {
                return folderPath;
            }
        }
 
        public static string GenerateStoredKey(string key, Type t)
        {
            return string.Format("{0}-{1}", t.FullName, key);
        }
 
        public Task ClearAsync()
        {
            if (Directory.Exists(folderPath))
            {
                Directory.Delete(folderPathtrue);
                Directory.CreateDirectory(folderPath);
            }
            return Task.Delay(0);
        }
 
        public Task DeleteAsync<T>(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException("Key MUST have a value");
 
            string path = Path.Combine(folderPathGenerateStoredKey(key, typeof(T)));
            if (File.Exists(path))
                File.Delete(path);
            return Task.Delay(0);
        }
 
        public Task<TGetAsync<T>(string key)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException("Key MUST have a value");
 
            TaskCompletionSource<T> completionSource = new TaskCompletionSource<T>();
            string path = Path.Combine(folderPathGenerateStoredKey(key, typeof(T)));
            if (File.Exists(path))
            {
                try
                {
                    string input = File.ReadAllText(path);
                    completionSource.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize<T>(input));
                }
                catch (Exception ex)
                {
                    completionSource.SetException(ex);
                }
            }
            else
                completionSource.SetResult(default(T));
            return completionSource.Task;
        }
 
        public Task StoreAsync<T>(string key, T value)
        {
            if (string.IsNullOrEmpty(key))
                throw new ArgumentException("Key MUST have a value");
 
            string contents = NewtonsoftJsonSerializer.Instance.Serialize((object)value);
            File.WriteAllText(Path.Combine(folderPathGenerateStoredKey(key, typeof(T))), contents);
            return Task.Delay(0);
        }
    }
}

 

Shibumi

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.