Christmas vibe in your WPF application

Retro Christmas vibes in your WPF application

Just call MakeIt.Snow(true); in your application startup and get the Christmas spirit in your WPF application. The routine will check for your MainWindow to be loaded and add an overlay grid that will display the snow on top of your window.

Let is snow, let it snow, let it snow!

MakeIt.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;

namespace Snow
{
    /// <summary>
    /// Make It Snow!
    ///
    /// based upon:
    /// - https://github.com/tsasioglu/Wpf-Snow/blob/master/WpfSnow/SnowFlake.xaml
    /// - https://blog.jerrynixon.com/2013/12/you-can-make-it-snow-in-xaml-here-ill.html
    /// </summary>
    public static class MakeIt
    {
        private static bool s_showAlways = false;
        private static readonly Random s_random = new Random((int)DateTime.Now.Ticks);
        private static Grid s_gridOverlay;
        private static readonly int s_maxSnowflakes = 100;

        public static void Snow() => Snow(false);

        public static void Snow(bool showAlways)
        {
            s_showAlways = showAlways;
            StartTimer();
        }

        private static void StartTimer()
        {
            // either show always, or at random
            if (s_showAlways || s_random.Next(0, 5) == 0)
            {
                DispatcherTimer t = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(100) };
                t.Tick += (_, _) => MakeItSnow();
                t.Start();
            }
        }

        private static void CreateGrid()
        {
            if (Application.Current.MainWindow?.Content is Grid grid && s_gridOverlay == null)
            {
                s_gridOverlay = new Grid
                {
                    Background = new SolidColorBrush(Colors.Transparent) {Opacity = 0},
                    IsHitTestVisible = false,
                    HorizontalAlignment = HorizontalAlignment.Stretch,
                    VerticalAlignment = VerticalAlignment.Stretch
                };
                Panel.SetZIndex(s_gridOverlay, 1000);

                grid.Children.Insert(0, s_gridOverlay);
            }
        }

        private static void MakeItSnow()
        {
            if (s_gridOverlay == null)
            {
                CreateGrid();
                return;
            }

            if (s_gridOverlay.Children.Count > s_maxSnowflakes)
            {
                return;
            }

            double actualHeight = Application.Current.MainWindow != null ? Application.Current.MainWindow.ActualHeight : s_gridOverlay.ActualHeight;
            double actualWidth = Application.Current.MainWindow != null ? Application.Current.MainWindow.ActualWidth : s_gridOverlay.ActualWidth;

            int x = s_random.Next(-500, (int) (actualWidth - 100));
            int y = 0;
            double s = s_random.Next(5, 15) * 0.1;
            int r = s_random.Next(0, 270);

            Flake flake = new Flake()
            {
                RenderTransform = new TransformGroup() {Children = new TransformCollection() {new RotateTransform(r), new TranslateTransform(x, y), new ScaleTransform(s, s),}},
                HorizontalAlignment = HorizontalAlignment.Left,
                HorizontalContentAlignment = HorizontalAlignment.Left,
                VerticalContentAlignment = VerticalAlignment.Top,
                VerticalAlignment = VerticalAlignment.Top,
                IsHitTestVisible = false,
            };

            s_gridOverlay.Children.Add(flake);
            TimeSpan d = TimeSpan.FromSeconds(s_random.Next(1, 4));

            x += s_random.Next(100, 500);
            DoubleAnimation ax = new DoubleAnimation() {To = x, Duration = d};

            Storyboard.SetTarget(ax, flake);
            Storyboard.SetTargetProperty(ax, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.X)"));

            y += (int) (actualHeight + 100 + 100);
            DoubleAnimation ay = new DoubleAnimation() {To = y, Duration = d};

            Storyboard.SetTarget(ay, flake);
            Storyboard.SetTargetProperty(ay, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)"));

            r += s_random.Next(90, 360);
            DoubleAnimation ar = new DoubleAnimation() {To = r, Duration = d};

            Storyboard.SetTarget(ar, flake);
            Storyboard.SetTargetProperty(ar, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)"));

            Storyboard story = new Storyboard();
            story.Completed += (sender, args) => { s_gridOverlay.Children.Remove(flake); };

            story.Children.Add(ax);
            story.Children.Add(ay);
            story.Children.Add(ar);

            story.Begin();
        }
    }
}

Flake.xaml

<UserControl x:Class="Snow.Flake"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d">
        <Path Width="27.0183"
              StrokeThickness="0.2"
              Height="19.1079"
              Stretch="Fill"
              StrokeLineJoin="Round"
              Stroke="#FF000000"
              Fill="#FFFFFFFF"
              Data="M 77.6738,73.6884C 77.7136,73.4433 77.6448,73.1851 77.5577,72.9526C 77.4839,72.7558 77.5355,72.5209 77.4415,72.3329C 77.4126,72.2752 77.3595,72.2328 77.3253,72.178C 77.2947,72.1291 77.2618,72.0792 77.2478,72.0231C 77.2353,71.973 77.2478,71.9199 77.2478,71.8682C 77.2478,71.8166 77.266,71.7617 77.2478,71.7133C 77.2252,71.6529 77.1773,71.6041 77.1317,71.5584C 77.086,71.5128 77.0188,71.4912 76.9768,71.4422C 76.9392,71.3984 76.9228,71.3401 76.8993,71.2873C 76.8711,71.2238 76.871,71.1428 76.8218,71.0937C 76.7842,71.056 76.7095,71.0869 76.6669,71.0549C 76.165,70.6785 75.6217,70.1672 75.5439,69.5446C 75.5166,69.3268 75.5439,69.1057 75.5439,68.8862C 75.5439,68.8346 75.5439,68.783 75.5439,68.7313C 75.5439,68.6797 75.5182,68.6213 75.5439,68.5764C 75.6481,68.394 75.9533,68.4603 76.1635,68.4603C 76.4088,68.4603 76.6614,68.4008 76.8993,68.4603C 77.2365,68.5446 77.1817,69.1201 77.4415,69.351C 77.8393,69.7046 78.4922,69.9251 78.9906,69.7382C 79.2667,69.6347 79.3098,69.2184 79.3391,68.925C 79.3623,68.6934 79.5268,68.4067 79.3778,68.2278C 79.3333,68.1745 79.2334,68.1996 79.1842,68.1505C 79.1466,68.1128 79.1389,68.0483 79.1455,67.9955C 79.225,67.3594 80.2636,67.0381 80.8882,67.1823C 81.1222,67.2363 81.3848,67.3834 81.4691,67.6082C 81.5828,67.9114 81.4619,68.2604 81.3916,68.5764C 81.3097,68.9453 81.1899,69.3245 81.2367,69.6995C 81.2768,70.02 81.3424,70.3788 81.198,70.6677C 81.1114,70.8409 80.8944,70.9088 80.7333,71.0162C 80.6725,71.0567 80.6232,71.1135 80.5784,71.1711C 80.5322,71.2305 80.5188,71.3152 80.4622,71.3648C 80.4099,71.4106 80.3293,71.4084 80.2686,71.4422C 80.2121,71.4736 80.1593,71.5128 80.1136,71.5584C 79.9116,71.7604 79.8741,72.0876 79.8426,72.3716C 79.8221,72.5554 79.7493,72.7304 79.7264,72.9139C 79.7071,73.0681 79.9204,73.2065 80.0749,73.2237C 80.6176,73.284 81.2242,73.2952 81.7015,73.03C 81.7737,72.9899 81.8254,72.9195 81.8951,72.8751C 81.9681,72.8286 82.0471,72.7911 82.1274,72.759C 82.6746,72.5401 83.2361,72.2985 83.6765,71.9069C 83.9629,71.6524 84.3953,71.5782 84.6447,71.2873C 84.7687,71.1426 84.845,70.9608 84.9158,70.7839C 85.0074,70.5549 85.0475,70.1788 84.8383,70.048C 84.5544,69.8705 84.1656,69.8376 83.9476,69.5833C 83.8349,69.4518 83.7761,69.2808 83.7153,69.1186C 83.6427,68.9252 83.5862,68.6602 83.7153,68.499C 83.9103,68.2552 84.3399,68.3151 84.6447,68.3828C 84.8871,68.4366 85.0925,68.5997 85.3031,68.7313C 85.5667,68.8961 85.9277,68.8698 86.2325,68.8088C 86.8392,68.6874 87.3439,68.2507 87.9365,68.0729C 88.1135,68.0198 88.3344,67.8413 88.4787,67.9568C 88.5202,67.9901 88.5115,68.0588 88.5174,68.1117C 88.5245,68.1758 88.5174,68.2408 88.5174,68.3054C 88.5174,68.6197 88.4299,68.9738 88.2076,69.196C 87.9336,69.47 87.3367,69.4141 87.2007,69.777C 87.1826,69.8253 87.2007,69.8802 87.2007,69.9319C 87.2007,69.9835 87.1751,70.0419 87.2007,70.0868C 87.2327,70.1428 87.2989,70.1721 87.3556,70.2029C 87.4419,70.2501 87.5413,70.2703 87.6267,70.3192C 87.7231,70.3742 87.8152,70.4385 87.8978,70.5128C 87.9457,70.556 87.9877,70.6087 88.014,70.6677C 88.0407,70.7278 88.0384,70.7971 88.0527,70.8613C 88.0642,70.9133 88.0676,70.9686 88.0914,71.0162C 88.2242,71.2818 88.357,71.7742 88.0914,71.9069C 87.7174,72.0939 87.267,71.7652 86.8521,71.7133C 86.6374,71.6865 86.3855,72.06 86.2325,71.9069C 86.196,71.8704 86.2815,71.7357 86.2325,71.752C 85.9313,71.8525 86.0605,72.4135 85.8065,72.6041C 85.5321,72.8098 85.1382,72.786 84.8383,72.9526C 84.5984,73.0859 84.4353,73.3262 84.2187,73.4948C 83.9415,73.7104 83.6525,73.9103 83.3667,74.1144C 83.3234,74.1453 83.2583,74.1273 83.2118,74.1531C 83.1395,74.1932 83.0731,74.2463 83.0182,74.308C 82.833,74.5163 82.8245,74.8426 82.8245,75.1213C 82.8245,75.2896 82.7555,75.4954 82.8633,75.6248C 83.0481,75.8465 83.4266,75.7797 83.7153,75.7797C 84.0767,75.7797 84.4382,75.7797 84.7996,75.7797C 85.3046,75.7797 85.8228,76.0287 86.31,75.8958C 86.8146,75.7582 87.3525,75.7038 87.8203,75.4698C 88.0957,75.3321 88.2856,75.0009 88.3238,74.6953C 88.3767,74.2718 88.1452,73.7587 88.4012,73.4173C 88.4399,73.3657 88.4966,73.3259 88.5561,73.3011C 88.6544,73.2602 88.7609,73.2411 88.866,73.2237C 89.1285,73.1799 89.5317,73.2767 89.6018,73.5335C 89.6903,73.8582 89.6421,74.2058 89.6792,74.5403C 89.7049,74.7717 89.6997,75.1025 89.9116,75.1987C 90.2532,75.354 90.7052,75.301 91.0347,75.1213C 91.343,74.9531 91.5797,74.6722 91.8867,74.5016C 92.2478,74.301 92.8338,74.2095 93.1259,74.5016C 93.3805,74.7563 93.397,75.1872 93.397,75.5473C 93.397,75.7667 93.4952,76.0093 93.397,76.2056C 93.3644,76.2709 93.2902,76.3056 93.2421,76.3605C 93.1996,76.4091 93.1601,76.4608 93.1259,76.5154C 92.9122,76.8575 92.729,77.3033 92.3514,77.4449C 92.303,77.4631 92.2481,77.4449 92.1965,77.4449C 92.119,77.4449 92.0413,77.4385 91.9641,77.4449C 91.7065,77.4664 91.3724,77.6664 91.1896,77.4836C 90.9959,77.29 91.3112,76.8062 91.0734,76.6703C 90.8378,76.5357 90.5315,76.6317 90.2601,76.6317C 90.0923,76.6317 89.8856,76.5242 89.7567,76.6317C 89.5771,76.7813 89.5553,77.1305 89.6792,77.3287C 89.7179,77.3907 89.7903,77.4252 89.8341,77.4836C 89.8688,77.5298 89.8913,77.5845 89.9116,77.6385C 90.066,78.0502 90.2329,78.5894 89.989,78.9553C 89.8257,79.2002 89.4274,79.236 89.137,79.1876C 88.9431,79.1553 88.7339,79.0943 88.5949,78.9553C 88.3743,78.7347 88.4106,78.3401 88.2076,78.1033C 88.0421,77.9102 87.7429,77.8967 87.5105,77.7935C 87.3613,77.7271 87.2091,77.6385 87.0458,77.6385C 86.2969,77.6385 85.5261,77.4182 84.7996,77.5998C 84.5072,77.6729 84.2088,77.7247 83.9089,77.7547C 83.6905,77.7766 83.456,77.6777 83.2505,77.7547C 83.1901,77.7774 83.1579,77.8539 83.0956,77.8709C 82.7875,77.9549 82.309,77.8176 82.1662,78.1033C 82.1424,78.1509 82.134,78.2054 82.1274,78.2582C 82.121,78.3094 82.0944,78.3734 82.1274,78.4131C 82.172,78.4665 82.2603,78.4568 82.3211,78.4905C 82.3775,78.5219 82.434,78.5577 82.476,78.6068C 82.5136,78.6506 82.5332,78.7076 82.5534,78.7617C 82.5721,78.8115 82.5581,78.8757 82.5922,78.9166C 82.7575,79.1149 83.0837,79.1571 83.2118,79.3813C 83.2374,79.4261 83.1808,79.4949 83.2118,79.5362C 83.2677,79.6107 83.3667,79.6395 83.4442,79.6911C 83.5216,79.7427 83.6107,79.7802 83.6765,79.846C 83.8591,80.0286 84.0772,80.1739 84.2962,80.3107C 84.5216,80.4516 84.7326,80.6458 84.9932,80.698C 85.2855,80.7564 85.6174,80.7538 85.884,80.6205C 86.2662,80.4294 86.6902,79.8413 87.0458,80.0784C 87.3071,80.2526 87.2769,80.7031 87.2007,81.0078C 87.1121,81.3622 86.6651,81.5747 86.6198,81.9373C 86.5941,82.1426 86.5222,82.4011 86.6585,82.5569C 86.7916,82.7089 87.0473,82.6965 87.2007,82.8279C 87.3744,82.9769 87.4667,83.2693 87.3943,83.4863C 87.2609,83.8867 86.6329,83.8563 86.2325,83.9898C 85.9016,84.1 85.5289,84.0969 85.1869,84.0285C 85.1223,84.0156 85.0575,84.0041 84.9932,83.9898C 84.9413,83.9782 84.8792,83.9851 84.8383,83.951C 84.7157,83.8488 84.742,83.6448 84.7222,83.4863C 84.7021,83.3259 84.8493,83.1832 84.9545,83.0604C 85.0637,82.9329 85.2457,82.7322 85.1482,82.5956C 85.1044,82.5344 85.0244,82.5074 84.9545,82.4794C 84.8934,82.455 84.8264,82.4467 84.7609,82.4407C 84.6838,82.4337 84.6045,82.4559 84.5285,82.4407C 84.2676,82.3885 84.019,82.2712 83.754,82.2471C 83.4323,82.2178 83.0486,82.0206 82.7858,82.2083C 82.5884,82.3493 82.5845,82.6498 82.476,82.8667C 82.3431,83.1325 81.758,83.1473 81.5853,82.9055C 81.348,82.5732 81.6626,82.0093 81.9725,81.7436C 82.0164,81.7061 82.0836,81.7037 82.1274,81.6661C 82.1765,81.6241 82.221,81.5717 82.2436,81.5112C 82.3077,81.3403 82.3029,81.1507 82.3211,80.9691C 82.3405,80.7747 82.4955,80.5578 82.3985,80.3882C 82.3699,80.3381 82.2953,80.3365 82.2436,80.3107C 82.192,80.2849 82.1326,80.2708 82.0887,80.2333C 82.0397,80.1912 82.0182,80.124 81.9725,80.0784C 81.9141,80.0199 81.8333,79.9857 81.7789,79.9234C 81.6222,79.7443 81.5371,79.4648 81.3142,79.3813C 81.0601,79.2859 80.7723,79.3425 80.5009,79.3425C 80.2398,79.3425 79.9352,79.3021 79.7264,79.4587C 79.5691,79.5767 79.563,79.8168 79.494,80.0009C 79.4737,80.0549 79.4446,80.1053 79.4166,80.1558C 79.38,80.2216 79.3242,80.278 79.3004,80.3494C 79.2228,80.5821 79.3226,80.841 79.3004,81.0853C 79.2686,81.4345 78.9897,81.8391 79.1842,82.1309C 79.3066,82.3144 79.6363,82.2197 79.8038,82.3632C 80.1896,82.6939 80.6006,83.2205 80.5009,83.7187C 80.4685,83.8808 80.3515,84.0659 80.1911,84.106C 79.9531,84.1655 79.7006,84.106 79.4553,84.106C 78.9204,84.106 78.4024,83.8736 77.8675,83.8736C 77.8158,83.8736 77.7642,83.8736 77.7126,83.8736C 77.648,83.8736 77.5754,83.8422 77.5189,83.8736C 77.1932,84.0545 77.3862,84.612 77.2478,84.9579C 77.1417,85.2233 76.7203,85.2291 76.4346,85.2291C 76.1055,85.2291 75.4992,85.3629 75.4664,85.0354C 75.4595,84.9662 75.5128,84.904 75.5439,84.8417C 75.5775,84.7745 75.6029,84.6971 75.66,84.6481C 75.7004,84.6135 75.764,84.6247 75.8149,84.6094C 75.8931,84.5859 75.982,84.5809 76.0473,84.5319C 76.2176,84.4042 76.2022,84.1252 76.2022,83.9123C 76.2022,83.7445 76.2855,83.5546 76.2022,83.4089C 76.056,83.153 75.6775,83.139 75.4277,82.9828C 75.2171,82.8513 74.9578,82.796 74.7693,82.6343C 74.6001,82.4893 74.5749,82.1846 74.6531,81.976C 74.8785,81.3749 75.9254,81.7332 76.512,81.4725C 76.7771,81.3547 77.0591,81.2281 77.2478,81.0078C 77.3946,80.8366 77.3481,80.5682 77.4027,80.3494C 77.4697,80.0817 77.5272,79.8105 77.5577,79.5362C 77.5649,79.4708 77.5891,79.408 77.5964,79.3425C 77.6021,79.2912 77.5677,79.2306 77.5964,79.1876C 77.6284,79.1396 77.7105,79.151 77.7513,79.1102C 77.7921,79.0693 77.8546,79.0069 77.8288,78.9553C 77.8057,78.9091 77.7245,78.9654 77.6738,78.9553C 77.5938,78.9393 77.522,78.8912 77.4415,78.8778C 77.3651,78.8651 77.2826,78.9023 77.2091,78.8778C 76.8849,78.7697 76.4617,78.4617 76.2022,78.6841C 76.0619,78.8044 76.1388,79.1125 75.9699,79.1876C 75.6575,79.3265 75.3047,79.3813 74.963,79.3813C 74.7019,79.3813 74.373,79.3128 74.1884,79.4974C 73.9676,79.7182 74.2659,80.1171 74.3046,80.4269C 74.3573,80.8482 74.2241,81.3806 73.8786,81.6274C 73.6461,81.7935 73.3511,81.8985 73.0653,81.8985C 73.0137,81.8985 72.9621,81.8985 72.9104,81.8985C 72.8588,81.8985 72.7975,81.9285 72.7555,81.8985C 72.5952,81.784 72.585,81.5344 72.4457,81.3951C 72.4001,81.3494 72.325,81.3336 72.2908,81.2789C 72.2559,81.2231 72.2194,81.1424 72.2521,81.0853C 72.2866,81.0249 72.3934,81.0536 72.4457,81.0078C 72.5735,80.896 72.5157,80.5886 72.3682,80.5043C 71.7742,80.1649 70.9999,80.5043 70.3157,80.5043C 70.0546,80.5043 69.686,80.6054 69.5412,80.3882C 69.4456,80.2449 69.5861,80.0415 69.6573,79.8847C 69.7819,79.6106 69.83,79.1832 70.1221,79.1102C 70.4861,79.0192 70.8788,78.9514 71.2452,79.0327C 71.5145,79.0926 71.8116,79.311 72.0584,79.1876C 72.5923,78.9207 72.1505,77.7111 72.7168,77.5224C 72.7658,77.506 72.8227,77.506 72.8717,77.5224C 72.9431,77.5462 72.9995,77.602 73.0653,77.6385C 73.3034,77.7708 73.5699,77.872 73.7624,78.0645C 73.9908,78.2929 74.4264,78.2119 74.7306,78.1033C 74.9666,78.019 75.217,77.9733 75.4664,77.9484C 75.7284,77.9222 75.9912,77.8767 76.2409,77.7935C 76.2914,77.7766 76.372,77.8023 76.3959,77.7547C 76.4253,77.6959 76.3644,77.6265 76.3571,77.5611C 76.3514,77.5098 76.3507,77.4575 76.3571,77.4062C 76.3637,77.3533 76.3829,77.3029 76.3959,77.2513C 76.4088,77.1996 76.4555,77.1453 76.4346,77.0964C 76.4091,77.037 76.3401,77.0029 76.2797,76.9802C 76.2313,76.962 76.1578,77.0198 76.1248,76.9802C 76.0745,76.9199 76.1371,76.8074 76.086,76.7479C 76.0432,76.6978 75.9573,76.7199 75.8924,76.7091C 75.8024,76.6941 75.7085,76.6972 75.6213,76.6703C 75.3051,76.5731 75.0028,76.4349 74.6919,76.3218C 74.0969,76.1055 73.5048,75.7797 72.8717,75.7797C 72.6651,75.7797 72.4239,75.6651 72.2521,75.7797C 71.9861,75.957 71.826,76.2686 71.555,76.438C 71.3875,76.5427 71.1703,76.5324 70.9741,76.5542C 70.6745,76.5875 70.3789,76.7682 70.0833,76.7091C 69.7606,76.6446 70.0369,76.0353 69.8897,75.7409C 69.8045,75.5704 69.5083,75.6163 69.3863,75.4698C 69.2857,75.3492 69.3959,75.1358 69.3088,75.0051C 69.2093,74.8559 68.9875,74.8416 68.8441,74.734C 68.6084,74.5572 68.3388,74.4277 68.0695,74.308C 68.0008,74.2775 67.9432,74.2255 67.8759,74.1918C 67.8137,74.1608 67.7351,74.1596 67.6823,74.1144C 67.5583,74.0081 67.5594,73.8098 67.5274,73.6497C 67.4918,73.472 67.335,73.2036 67.4886,73.1075C 67.566,73.0591 67.6687,73.0753 67.7597,73.0688C 67.8499,73.0623 67.9408,73.077 68.0308,73.0688C 68.9039,72.9894 69.7363,73.901 70.5868,73.6884C 70.8879,73.6131 71.073,73.2883 71.2452,73.03C 71.438,72.7408 71.8657,72.6428 72.2133,72.6428C 72.3945,72.6428 72.6395,72.5423 72.7555,72.6815C 73.0959,73.09 72.5913,73.8439 72.9104,74.2693C 73.087,74.5048 73.4728,74.449 73.7624,74.5016C 74.1846,74.5784 74.6113,74.6566 75.0404,74.6566C 75.3889,74.6566 75.7392,74.6912 76.086,74.6566C 76.3055,74.6346 76.4917,74.4777 76.7057,74.4242C 77.0989,74.3259 77.6089,74.0885 77.6738,73.6884 Z M 79.3004,74.8115C 79.0268,74.8115 78.7303,74.9008 78.5258,75.0826C 78.3358,75.2515 78.2891,75.5361 78.216,75.7797C 78.1373,76.0419 78.0211,76.3306 78.0998,76.593C 78.1861,76.8805 78.6169,76.9308 78.9131,76.9802C 79.2953,77.0439 79.699,77.1129 80.0749,77.0189C 80.3219,76.9572 80.5676,76.825 80.7333,76.6317C 80.8741,76.4673 80.8509,76.2147 80.9269,76.012C 81.0007,75.8152 81.1692,75.5605 81.0431,75.3924C 80.6388,74.8533 79.7806,74.8115 79.1067,74.8115M 71.4775,74.5404C 71.3779,74.5102 71.2715,74.5091 71.1677,74.5016C 71.0904,74.4962 71.0061,74.4702 70.9353,74.5016C 70.6395,74.6331 71.0066,75.3221 71.3226,75.3924C 71.5624,75.4457 71.8143,75.4582 72.0584,75.4311C 72.2435,75.4106 72.4999,75.098 72.3682,74.9664C 72.1355,74.7336 71.7925,74.6357 71.4775,74.5404 Z M 73.0266,78.9553C 72.9704,78.9686 72.975,79.0585 72.9491,79.1102C 72.8561,79.2963 72.691,79.6265 72.8717,79.7298C 72.9277,79.7618 73.001,79.7244 73.0653,79.7298C 73.1563,79.7374 73.2454,79.7609 73.3364,79.7685C 73.4007,79.7739 73.4659,79.7757 73.5301,79.7685C 73.5829,79.7626 73.6473,79.7675 73.685,79.7298C 73.7945,79.6203 73.7945,79.3746 73.685,79.2651C 73.5578,79.1379 73.3423,79.1519 73.1815,79.0715C 73.1238,79.0425 73.0894,78.9404 73.0266,78.9553 Z M 77.7126,82.2083C 77.4931,82.2083 77.2256,82.0712 77.0542,82.2083C 76.8929,82.3373 76.9447,82.6528 77.0542,82.8279C 77.2338,83.1153 77.6861,83.1798 78.0224,83.1378C 78.2494,83.1094 78.3625,82.6552 78.216,82.4794C 78.094,82.333 77.9032,82.2083 77.7126,82.2083 Z M 85.032,81.3563C 84.9721,81.4071 84.8766,81.333 84.7996,81.3176C 84.7474,81.3072 84.6976,81.2847 84.6447,81.2789C 84.4266,81.2546 84.2058,81.2789 83.9863,81.2789C 83.9089,81.2789 83.8145,81.2305 83.754,81.2789C 83.6226,81.384 84.0144,81.5198 84.18,81.55C 84.4978,81.6077 84.8252,81.5887 85.1482,81.5887C 85.1998,81.5887 85.28,81.6349 85.3031,81.5887C 85.3563,81.4822 85.1228,81.2794 85.032,81.3563 Z M 91.8867,75.586C 91.8224,75.5923 91.7559,75.5715 91.693,75.586C 91.5855,75.6108 91.4763,75.643 91.3832,75.7022C 91.3288,75.7369 91.2897,75.7967 91.267,75.8571C 91.2489,75.9055 91.2795,75.9619 91.267,76.012C 91.253,76.0681 91.2036,76.1109 91.1896,76.1669C 91.1771,76.217 91.1565,76.2822 91.1896,76.3218C 91.2341,76.3752 91.3153,76.3842 91.3832,76.3993C 91.4336,76.4105 91.4865,76.3993 91.5381,76.3993C 91.5898,76.3993 91.6414,76.3993 91.693,76.3993C 91.7447,76.3993 91.7978,76.4118 91.8479,76.3993C 91.9879,76.3643 92.0237,76.1552 92.0416,76.012C 92.048,75.9608 92.0416,75.9088 92.0416,75.8571C 92.0416,75.8055 92.0352,75.7534 92.0416,75.7022C 92.0482,75.6494 92.1229,75.5792 92.0803,75.5473C 92.0276,75.5078 91.9522,75.5795 91.8867,75.586 Z M 88.2076,76.1669C 88.156,76.1669 88.0989,76.1439 88.0527,76.1669C 87.9874,76.1996 87.9304,76.2565 87.8978,76.3218C 87.8747,76.368 87.8853,76.4266 87.8978,76.4767C 87.9118,76.5328 87.9344,76.5908 87.9752,76.6317C 88.1723,76.8287 88.5566,76.9799 88.7885,76.8253C 88.9604,76.7107 88.9345,76.3517 88.7885,76.2056C 88.6023,76.0195 88.2773,76.0507 88.014,76.0507M 86.1551,70.048C 86.0797,70.0303 85.9935,70.0166 85.9227,70.048C 85.6982,70.1478 85.654,70.6976 85.884,70.7839C 86.0295,70.8384 86.2293,70.8447 86.3487,70.7451C 86.4693,70.6446 86.4456,70.4362 86.4261,70.2804C 86.4114,70.1623 86.2709,70.0754 86.1551,70.048 Z M 80.1911,69.6221C 80.1395,69.6221 80.0878,69.6221 80.0362,69.6221C 79.9846,69.6221 79.9296,69.6039 79.8813,69.6221C 79.7298,69.6788 79.669,69.8875 79.6489,70.048C 79.6316,70.1866 79.8589,70.2977 79.9975,70.2804C 80.1517,70.2612 80.3073,70.0873 80.3073,69.9319C 80.3073,69.7352 80.2715,69.3897 80.0749,69.3897M 77.7513,70.2417C 77.5975,70.2606 77.4355,70.1992 77.2866,70.2417C 77.133,70.2856 77.0858,70.571 77.1704,70.7064C 77.3198,70.9455 77.7039,70.9738 77.9837,70.9388C 78.2029,70.9114 78.1837,70.4643 78.0611,70.2804C 78.0291,70.2324 77.9635,70.2101 77.9062,70.2029C 77.8534,70.1964 77.8041,70.2352 77.7513,70.2417 Z M 77.3253,74.8889C 77.2846,74.8571 77.2218,74.8842 77.1704,74.8889C 77.0795,74.8972 76.9875,74.9041 76.8993,74.9277C 76.6862,74.9845 76.4356,75.004 76.2797,75.16C 76.1668,75.2729 76.1833,75.4663 76.1635,75.6248C 76.1569,75.6776 76.1314,75.7269 76.1248,75.7797C 76.1184,75.8309 76.0925,75.8942 76.1248,75.9346C 76.278,76.1261 76.6412,76.0443 76.8606,75.9346C 77.0687,75.8305 77.1245,75.551 77.2478,75.3536C 77.3311,75.2205 77.4491,74.9856 77.3253,74.8889 Z M 79.4166,77.9484C 79.1842,77.9484 78.898,77.7996 78.7195,77.9484C 78.4882,78.1411 78.8854,78.6855 79.1842,78.7229C 79.4507,78.7562 79.6156,78.3311 79.6489,78.0645C 79.6555,78.0117 79.7141,77.9559 79.6876,77.9096C 79.6503,77.8443 79.5586,77.8322 79.494,77.7935M 82.7084,76.3218C 82.3483,76.3218 81.7662,76.248 81.6627,76.593C 81.6041,76.7884 81.6571,77.1296 81.8564,77.1738C 82.0081,77.2076 82.1703,77.1728 82.3211,77.1351C 82.5426,77.0797 82.7575,76.8583 82.7858,76.6317C 82.8094,76.4425 82.6782,76.2263 82.5147,76.1282M 81.9338,73.7271C 81.7117,73.7271 81.5711,74.0485 81.5465,74.2693C 81.5294,74.4238 81.4107,74.6126 81.5078,74.734C 81.6459,74.9066 81.9823,74.934 82.1662,74.8115C 82.4737,74.6064 82.3035,73.7271 81.9338,73.7271 Z " />
</UserControl>
Snowflake

OpenMSX and RetroPie

This post outlines how I got OpenMSX running on RetroPie, basically it is a collection of links and tips and tricks that i gathered from other sites to make it work.

Quick overview:
– OpenMSX 0.14.0
– RetroPie 4.3
– Raspberry Pi 3 Model B

UPDATE: for Raspberry Pi 3 Model B+ you can use the retro 4.4 image together with an updated sources.list which you can download at http://bjorn.kuiper.nu/upload/retropie/sources.list.

UPDATE 2: The original configuration only accepted ROMS, ignoring DSK and CAS files. By replacing the direct call to openmsx with a loader we can handle these files as well. For that to work i have updated the es_systems.cfg file and create a loader.sh file within the openmsx directory with the correct permissions.

download both files to your pi and copy them, as root:

# cp es_systems.cfg /etc/emulationstation/.
# cp loader.sh /opt/retropie/emulators/openmsx/bin/.
# chmod 755 /opt/retropie/emulators/openmsx/bin/loader.sh

Happy gaming!

UPDATE 3: This blogpost is outdated. You can easily add OpenMSX of BlueMSX as additional components to RetroPie nowadays.

I bought my Raspberry Pi 3 Model B Essentials kit (element14.com) from a Dutch online-store (1).

Step 1: Assemble Raspberry Pi
Follow the instructions provided with your purchase and assemble your Raspberry Pi

PRO-TIP: You might need or want to re-partition your SD card if it has been used before. Under Windows you can use “Disk Management” to do this.

Step 2: Install RetroPie on the SD card for your Raspberry Pi
For this I followed the “First Installation” instructions on the RetroPie homepage (2). So I downloaded the latest RetroPie image for the Raspberry Pi 3 (download | mirror) and because I’m using Windows I used Win32DiskImager to write the image to the SD card. Make sure your SD card is set to “read/write” and not just “read-only”. The configuration of the controllers is not that important as OpenMSX uses their own settings. Just make sure you set “A” to match with “A” on your keyboard, etc. so you can easily navigate the EmulationStation menu (started when booting RetroPie). From “Installing Additional Emulators” on you can discard the “First Installation” instructions as we will be handling that here.

Step 3: Enable SSH for easy configuration
NOTE: Make sure you have enabled Wi-Fi (through the main Settings page in Emulationstation).
Detailed instructions for enabling SSH can be found HERE.
Press “Start” in the RetroPie Configuration screen. Choose “Quit” and “Quit Emulationstation”. You are now returned to a prompt (command line).
$ sudo raspi-config

And enable SSH through the menu “interfacing options >> SSH >> Enable”. Close the menu.

Get the IP address by typing:

$ ifconfig

and reboot your pi by typing

$ sudo reboot

Step 4: Download OpenMSX, compile and install it
Log-in remotely to your RetroPie using SSH or in the EmulationStation menu, choose “Quit”->”Quit EmulationStation” so you get a command prompt. You are logged in as user “pi”.

Download OpenMSX using wget (mirror):

$ wget https://github.com/openMSX/openMSX/releases/download/RELEASE_0_14_0/openmsx-0.14.0.tar.gz

unzip it:

$ tar -zxvf openmsx-0.14.0.tar.gz

And execute the following commands to be able to compile it and install it:

$ sudo su –
# cd /etc/apt/
# rm -f sources.list
# wget http://bjorn.kuiper.nu/upload/retropie/sources.list
# apt-get -y update
# apt-get -y build-dep openmsx
# exit
$ cd openmsx-0.14.0/build/
$ rm -f custom.mk
$ wget http://bjorn.kuiper.nu/upload/retropie/custom.mk
$ cd ..
$ ./configure

NOTE: `make` takes a while. Go do something else.

$ make
$ sudo make install
$ rm openmsx-0.14.0.tar.gz

Step 5: Install system roms
To be able to use different machines, instead of the default cbios, you need to download the systemroms.zip.

$ cd
$ mkdir -p .openMSX/share/systemroms
$ cd .openMSX/share/
$ wget http://bjorn.kuiper.nu/upload/retropie/systemroms.zip
$ unzip systemroms.zip
$ rm systemroms.zip
$ cd

Step 6: Add MSX to RetroPie / EmulationStation

$ mkdir -p /opt/retropie/configs/msx/
$ cd /opt/retropie/configs/msx/
$ wget http://bjorn.kuiper.nu/upload/retropie/emulators.cfg

$ sudo su –

# cd /etc/emulationstation/
# vi es_systems.cfg

add the following at the bottom of the file before the closing tag.

    • <system>
    • <name>msx</name>
    • <fullname>MSX</fullname>
    • <path>/home/pi/RetroPie/roms/msx</path>
    • <extension>.rom .ROM .mx2 .MX2 .mx1 .MX1</extension>
    • <command>/opt/retropie/emulators/openmsx/bin/openmsx -cart %ROM%</command>
    • <platform>msx</platform>
    • <theme>msx</theme>
    </system>

Save and quit VI.

Step 7: copy games onto the RetroPie machine

The MSX emulator is not shown in the Emulationstation screen until you actual add some games in the predefined roms folder.

Create the folder
$ mkdir -p /home/pi/RetroPie/roms/msx/

and copy the ROM files into that folder. You can create subfolders as well.

If you have SSH enabled you can use SFTP to copy the ROMs onto your RetroPie. On Windows you could use the FileZilla client to accomplish this.

You can just restart the Emulationstation to show the new games you have added. You don’t need to reboot the system.

Games can be found here:
http://www.msxabandonware.com/en/contentsby/b/1/1
http://www.msxarchive.nl/pub/msx/games/roms/
http://www.planetemu.net/roms/msx-various-rom?page=D

Nice to have: Classic boot screen
By default OpenMSX boots with CBIOS, an opensource MSX emulator, but the systemroms.zip we installed contains a lot of different machines.

For people with a windows keyboard, open the menu by pressing the “Menu-key” on your keyboard (it is between the Altr Gr and Ctrl key on the right). You can also navigate your mouse to the top left corner of the screen and click menu. go to “Hardware-Change Machine” and select a different machine, for example”: Philips NMS 8225. Make it your default by going back the menu and “Hardware-Set Current Machine as Default”.

resources:
(1) https://www.processorstore.nl/product/753520/raspberry-pi-3-model-b-essentials-kit.html
(2) https://retropie.org.uk/docs/First-Installation/

Standing on ye shoulders of Giants. Big thanks go to:
https://www.msxinfo.net/2017/01/20/install-openmsx-on-retropie-with-xbox360-or-picade-controller/
https://www.raspberrypi.org/forums/viewtopic.php?t=31277

DevSummit presentation: Lessons learned: Three years of ArcGIS Runtime for WPF

Thank you for attending my presentation. It should be available through the Esri Website sometime in the near future. In the meantime you can get the slides and pseudo code examples from here.

THE PRESENTATION (.pdf) (12MB)

Date: Wednesday March 12th
Time: 2:30pm – 3:00pm
Location: Mesquite B

Lessons learned: Three years of ArcGIS Runtime for WPF
This presentation will focus on lessons learned with regards to ArcGIS Runtime for WPF. We have been using ArcGIS Runtime for WPF in two different projects that span nearly three years, starting with ArcGIS Runtime for WPF BETA 2 release. During the presentation we will talk about: 1) the current benefits and limitations of ArcGIS Runtime for WPF; 2) the different tools we have developed to improve usage – including a layer manager, saving and retrieving layers from our own database instead of SDE, custom attributes pane and identify tool, setting rendering properties on layers; 3) tips and tricks when working with ArcGIS Runtime.

Pseudo code examples:

C#

using System;

namespace TipsAndTricks.RuntimeFolders
{
    public class Example
    {
        ArcGISRuntime.AppDataPath = Path.Combine(@"C:\Temp\", "AppDataPath");
        ArcGISRuntime.TempPath = Path.Combine(@"C:\Temp\", "TempPath");
    }
}

namespace TipsAndTricks.MEF
{
    public enum GISFramework
    {
        ArcObjects,
        GPK,
    }

    public interface IGIS
    {
        /* placeholder for GISFrameworkAttribute */
        GISFramework Framework { get; }
    }

    public interface IGeodatabase
    {
        void DoGISStuff();
    }

    public class Example
    {
        [ImportMany]
        private Lazy<IGeodatabase, IGIS>[] geodatabase = null;

        public void DoSomeGISStuff()
        {
            IGeodatabase Geo = geodatabase.Get(GISFramework.GPK);
            Geo.DoGISStuff();
        }
    }

    public static class ExtensionMethods
    {
        public static IGeodatabase Get(this Lazy<IGeodatabase, IGIS>[] items, GISFramework framework)
        {
            foreach (var item in items)
            {
                if (item.Metadata.Framework == framework)
                {
                    return item.Value;
                }
            }

            throw new ArgumentOutOfRangeException("No implementation found for: " + framework);
        }
    }
}

namespace TipsAndTricks.RuntimeServices {

    public class Example{

        /// 
        /// Open ArcGIS Runtime Webservice site in browser
        /// 
        /// command parameter
        private void ExecuteOpenArcGISRuntimeWebservice(object parameter)
        {
            if (LocalServer.IsRunning)
            {
                Thread t = new Thread(new ThreadStart(() =>
                {
                    Process.Start(LocalServer.Url);
                }));
                t.Start();
            }
        }

        /// 
        /// Open ArcGIS Runtime Webservice Admin site in browser
        /// 
        /// command parameter
        private void ExecuteOpenArcGISRuntimeWebserviceAdmin(object parameter)
        {
            if (LocalServer.IsRunning)
            {
                Thread t = new Thread(new ThreadStart(() =>
                {
                    Process.Start(LocalServer.Url.Replace("services", "admin"));
                }));
                t.Start();
            }
        }
    }
}

Python

# Tips and Tricks / Python exception handling

def handleExceptions(e):
    msgs = arcpy.GetMessages(2)
    arcpy.AddError(msgs)
    arcpy.AddError(e.message)
    arcpy.AddMessage(" ! Exception Message: " + e.message)
    print " ! Exception Message: " + e.message
    sys.exit()
    return

def main():
    fileToDelete = arcpy.GetParameterAsText(0)

    arcpy.AddMessage(" + fileToDelete" + str(fileToDelete))

    try:
        arcpy.Delete_management(fileToDelete)
    except Exception as e:
        handleExceptions(e)

    arcpy.AddMessage(" = Finished")

if __name__ == '__main__':

    main()

Lego and Playmobil professional cycling sets

While browsing around the web I came across a great Tour de France Team Telekom Lego set released in 2000. It is called the “1199 Race Cyclists and Winners’ Podium” and was part of promotional package of Team Telekom.

After posting about this on Twitter I soon discovered that there are all kinds of Lego and Playmobil cycling sets.

First of all, José Been (@TourDeJose) pointed out that this was exactly part of a set of four. It seems that 1196, 1197, 1198 and 1199 are all part of a Team Telekom promotional package.

Then Ron Kleijnen (@RonKleijnen) pointed out that there is also a Playmobil collection. Ron pointed me to #4994 after which is quickly discovered that both #4994 and #4995 are part of a Team Telekom promotional set.

Mark Young (@miyoung9999) pointed me to a special Vuelta Playmobil set with number #3090.

There is also a couple of older sets from Playmobil with cyclists, #3846 has just a single cyclist, #3847 has a motor TV crew and #3849 has three cyclist with a podium.

Start collecting! 🙂

Update:
You can even order personalized Lego cycling figures, Chris Froome and Team Sky and/or the Paralympics GB squad on minifigs.me.

All the different Lego and Playmobil items are visible below:

Playmobil #4995

Playmobil #3090

Playmobil #3846

Playmobil #3847

Playmobil #3849

The origin of 0-day (zero-day) in hacking (etymology of zero-day)

2021-02-27 UPDATE: A new find sets back the date four more years. Finding a reference to zero-day exploits in a e-zine of May 1994.

This post is a result of a tweet [1] by Space Rogue.

It seems that only a few [2] have tried to capture the origin of the word 0-day in hacking and are wrong.

The term 0-day comes originally from the Warez scene [3]:

“0-day (pronounced as zero day) – This refers to any copyrighted work that has been released the same day as the original product, or sometimes even before.[6] It is considered a mark of skill among warez distro groups to crack and distribute a program on the same day of its commercial release.”

Somewhere around the late 90’s it was picked up by the hacking scene.

Wikipedia explains zero-day attacks as following [4]:

A zero-day (or zero-hour or day zero) attack or threat is an attack that exploits a previously unknown vulnerability in a computer application, meaning that the attack occurs on “day zero” of awareness of the vulnerability.[1] This means that the developers have had zero days to address and patch the vulnerability. Zero-day exploits (the software and/or strategies that use a security hole to carry out a successful attack) are used or shared by attackers before the developer of the target software knows about the vulnerability.

On the 27th of February of 2021 Robert Graham, using his Twitter account @ErrataRob, posted a new, earlier references to the use of the terminology zero day within hacking [8].

Published in the fifth edition of BRoTHeRHooD oF WaReZ e-zine, May 1994 [9]:

we do not wish to impede the progress of zero day rdist exploits, sendmail DEBUG exploits, or other eleet aych pee warez.

As stated by Robert: “One theory is that “zero-day” appeared in the hacking scene independently of the warez scene. This reference refutes that, clearly showing the warez scene using zero-day to also refer to an exploit. One caveat to this is that it’s a zero-day EXPLOIT, and only later does it become zero-day VULN.”

The full thread by Robert [10] on the subject can be found as part of this initial tweet:

This particular reference sets back our initial findings to zero-day references four more years: from 1998 to May 1994!

Other early references we found in the past included a reference found in an e-zine of 1998 called CRH [5], (thanks to @bill_e_ghote for finding this one):

Our member chameleon set us up with a domain in Argentina, the D-Lab.. It has some mad shit on it, but you have to know where to look, because www.d-lab.com.ar will take you nowhere, it has 0-day exploits on it, as well as other useful stuff and source code, check it out..

Another references to 0-day in 1998 include a post on BugTraq by Ken Williams [6] and the Line-noise section of Phrack 53 [7].

From Phrack 53 [7]:

They seem unwilling to read the code given to them to establish exactly what happens when the newest 0-day exploit runs.

Let me know if you have found a reference to 0-day (in hacking) before May 1994!

Thanks go to
Space Rogue, twitter: @spacerog website: http://www.spacerogue.net
Bill E. Ghote, Twitter: @bill_e_ghote website: http://scrapeghote.blogspot.com
Robert Graham, twitter @ErrateRob website: https://blog.erratasec.com/

References:
[1] https://twitter.com/spacerog/statuses/387677286385733632 by @spacerog on October 8, 2013.
[2] http://spiresecurity.com/?p=576 – “Zero Day” Terminology by Pete Lindstrom on July 27, 2005.
[3] http://en.wikipedia.org/wiki/Warez
[4] http://en.wikipedia.org/wiki/Zero-day_attack
[5] http://web.textfiles.com/ezines/CRH/crh007.txt – 7th edition of CRH E-zine published on January 31st, 1998
[6] http://www.shmoo.com/mail/bugtraq/oct98/msg00027.html – Bugtraq October 5th, 1998
[7] http://www.textfiles.com/magazines/PHRACK/PHRACK53 – Phrack 53 July 8th, 1998
[8] https://twitter.com/ErrataRob/status/1365444754004185089 by @ErrateRob
[9] http://www.textfiles.com/magazines/BOW/bow5.txt – BRoTHeRHooD oF WaReZ #5 May 1994.
[10] https://twitter.com/ErrataRob/status/1365444749767933955 – Research by Robert Graham on zero-day references in hacking and warez scene, captured in a twitter feed.

 

 

PictureMarkerSymbols for offline use

Hi,

With the help from Esri I was able to download the existing PictureMarkerSymbols that are available from their website, as shown here

http://developers.arcgis.com/en/javascript/samples/portal_symbols/index.html

You can download the ‘zip’ (tar.gz) with all symbols from this location

http://bjorn.kuiper.nu/upload/blog/symbols.tar.gz

Enjoy!

DevSummit Presentation on ArcGIS Runtime WPF SDK (BETA 2)

My DevSummit presentation is online!

Starting with ArcGIS Runtime SDK for WPF

ArcGIS Runtime is the new lightweight easy to deploy cross platform GIS framework to host your maps, Geoprocessing services and other services. As of this writing, the ArcGIS Runtime SDK for WPF is in BETA 2, but the future for ArcGIS Runtime is bright and it will probably play a big role in stand-alone client applications in the near future. We have been using the ArcGIS Runtime SDK for WPF since November 2011 (Beta 1), and are one of the first groups to actively use it in a project to move an existing Esri-based Win-Forms application to the new Runtime. In this presentation, you will learn how to get started with ArcGIS Runtime and see what we have learned so far. Topics covered include:

  • A (very) short introduction into the ArcGIS Runtime and how to obtain ArcGIS Runtime
  • How the ArcGIS Runtime works (behind the scenes)
  • Debugging approach
  • Demonstrate an application using ArcGIS Runtime
  • Executing a Geoprocessing Task (hosted within a GP package) and displaying the results
  • Current known limitations (due to BETA status)
  • Tips and Tricks

The presentation can be viewed on the Esri website.

Source-code

A big part of the presentation is still valid on the latest release of the ArcGIS Runtime SDK for WPF, which is currently Prerelease.

ESRI DevSummit presentation from Bjorn Kuiper on Vimeo.

WP7: Tweetsharp and AgFx

Hello world!

WP7: Northern Lights WP7 Toolkit v0.0.1

In the previous months I posted some code examples that I used in my own Windows Phone applications.

After discovering some minor bugs and speed improvements I decided to put them into a toolkit library so they are easier to maintain.

I added the following posts / code examples into the toolkit:

The toolkit is called Northern Lights and hosted on codeplex. The project page is available HERE.

I’m looking forward on feedback and I will try to keep on updating the toolkit with new features while I keep developing my Windows Phone apps.

UPDATE: Currently (12/6/2011) Northern Lights is already at version v0.0.8. Make sure you check the codeplex homepage occasionally or use NuGet to get the latest updates.

WP7: LittleWatson Extended; Error reporting to HTTP endpoint

UPDATE 7 NOV 2011: Have a look at this post: WP7: Northern Lights WP7 Toolkit v0.0.1 for the latest version of this code example.

In the previous post I already mentioned the great talk of Jeff Wilcox on TechEd Australia (video here).

In his talk he mentions LittleWatson, a supporting class that Andy Pennell wrote. This class enables you to catch unhandled exceptions and let the user report these application errors to you, the developer.

A big downside of this class, in my opinion, is that it needs the user to send the report. I therefore took it up me to extend the class and add the possibility to automatically send the error report to an HTTP endpoint.

Here are the classes.

The main class.

LittleWatsonManager.c

namespace MyApp.Utils
{
    using System;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Net;
    using System.Text;

    /// 
    /// LittleWatsonManager class.
    /// 
    /// 
    /// Text to user: Send application error reports automatically and anonymously to southernsun to help us improve the application.
    /// 
    public class LittleWatsonManager
    {
        #region Fields
        private static readonly LittleWatsonManager instance = new LittleWatsonManager();
        private const string Filename = "LittleWatson.txt";
        private const string SettingsFilename = "LittleWatsonSettings.txt";
        private bool allowAnonymousHttpReporting = true;
        #endregion

        #region Constructor
        /// 
        /// Initializes static members of the LittleWatsonManager class.
        /// 
        static LittleWatsonManager()
        {
        }

        /// 
        /// Prevents a default instance of the LittleWatsonManager class from being created.
        /// 
        private LittleWatsonManager()
        {
            this.allowAnonymousHttpReporting = this.GetSetting();
        }
        #endregion

        #region Properties
        /// 
        /// Gets DataManager instance.
        /// 
        public static LittleWatsonManager Instance
        {
            get
            {
                return LittleWatsonManager.instance;
            }
        }

        /// 
        /// Gets or sets a value indicating whether error reports are allowed to send anonymously to a http endpoint.
        /// 
        public bool AllowAnonymousHttpReporting
        {
            get
            {
                return this.allowAnonymousHttpReporting;
            }

            set
            {
                this.allowAnonymousHttpReporting = value;
                this.SetSetting(this.allowAnonymousHttpReporting);
            }
        }
        #endregion

        #region Public Methods
        /// 
        /// Report exception.
        /// 
        /// The exception to report.
        public static void SaveExceptionForReporting(Exception ex)
        {
            if (ex == null)
            {
                return;
            }

            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (TextWriter output = new StreamWriter(store.OpenFile(Filename, FileMode.OpenOrCreate & FileMode.Truncate)))
                    {
                        output.WriteLine(Serializer.WriteFromObject(new ExceptionContainer() { Message = ex.Message, StackTrace = ex.StackTrace }));
                    }
                }
            }
            catch
            {
            }
        }

        /// 
        /// Check for previous logged exception.
        /// 
        /// Return the exception if found.
        public static ExceptionContainer GetPreviousException()
        {
            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    if (store.FileExists(Filename))
                    {
                        using (TextReader reader = new StreamReader(store.OpenFile(Filename, FileMode.Open, FileAccess.Read, FileShare.None)))
                        {
                            string data = reader.ReadToEnd();

                            try
                            {
                                return Serializer.ReadToObject(data);
                            }
                            catch
                            {
                            }
                        }

                        store.DeleteFile(Filename);
                    }
                }
            }
            catch
            {
            }

            return null;
        }

        /// 
        /// Send error report (exception) to HTTP endpoint.
        /// 
        /// Exception to send.
        public void SendExceptionToHttpEndpoint(ExceptionContainer exception)
        {
            if (!this.AllowAnonymousHttpReporting)
            {
                return;
            }

            try
            {
                string uri = "http://www.yourwebsite.com/data/post.php";

                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
                webRequest.Method = "POST";
                webRequest.ContentType = "application/x-www-form-urlencoded";

                webRequest.BeginGetRequestStream(
                    r =>
                    {
                        try
                        {
                            HttpWebRequest request1 = (HttpWebRequest)r.AsyncState;
                            Stream postStream = request1.EndGetRequestStream(r);

                            string info = string.Format("{0}{1}{2}", exception.Message, Environment.NewLine, exception.StackTrace);

                            string postData = "&exception=" + HttpUtility.UrlEncode(info);
                            byte[] byteArray = Encoding.UTF8.GetBytes(postData);

                            postStream.Write(byteArray, 0, byteArray.Length);
                            postStream.Close();

                            request1.BeginGetResponse(
                                s =>
                                {
                                    try
                                    {
                                        HttpWebRequest request2 = (HttpWebRequest)s.AsyncState;
                                        HttpWebResponse response = (HttpWebResponse)request2.EndGetResponse(s);

                                        Stream streamResponse = response.GetResponseStream();
                                        StreamReader streamReader = new StreamReader(streamResponse);
                                        string response2 = streamReader.ReadToEnd();
                                        streamResponse.Close();
                                        streamReader.Close();
                                        response.Close();
                                    }
                                    catch
                                    {
                                    }
                                },
                            request1);
                        }
                        catch
                        {
                        }
                    },
                webRequest);
            }
            catch
            {
            }
        }
        #endregion

        #region Private Methods
        private bool GetSetting()
        {
            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (TextReader reader = new StreamReader(store.OpenFile(SettingsFilename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None)))
                    {
                        string content = reader.ReadToEnd();

                        if (!string.IsNullOrEmpty(content))
                        {
                            try
                            {
                                return Serializer.ReadToObject(content);
                            }
                            catch
                            {
                            }
                        }
                    }
                }
            }
            catch
            {
            }

            return true;
        }

        private void SetSetting(bool value)
        {
            try
            {
                using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    using (TextWriter output = new StreamWriter(store.OpenFile(SettingsFilename, FileMode.OpenOrCreate & FileMode.Truncate, FileAccess.Write, FileShare.None)))
                    {
                        try
                        {
                            output.WriteLine(Serializer.WriteFromObject(value));
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch
            {
            }
        }
        #endregion
    }
}

We make our own ExceptionContainer that can be serialized.

ExceptionContainer.c

namespace MyApp.Utils
{
    using System;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Net;
    using System.Text;

    /// 
    /// ExceptionContainer class.
    /// 
    public class ExceptionContainer
    {
        /// 
        /// Gets or sets the message.
        /// 
        public string Message { get; set; }

        /// 
        /// Gets or sets the stacktrace.
        /// 
        public string StackTrace { get; set; }
    }
}

The supporting Serializer class.

Serializer.c

namespace MyApp.Utils
{
    using System.IO;
    using System.Runtime.Serialization.Json;
    using System.Text;

    /// 
    /// Serializer class.
    /// 
    public class Serializer
    {
        /// 
        /// Serialize object.
        /// 
        /// The Object type.
        /// The object to serialize.
        /// The serialized object.
        public static string WriteFromObject(T obj)
        {
            MemoryStream ms = new MemoryStream();
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            ser.WriteObject(ms, obj);
            byte[] json = ms.ToArray();
            ms.Close();
            return Encoding.UTF8.GetString(json, 0, json.Length);
        }

        /// 
        /// Deserialize object.
        /// 
        /// The object type.
        /// The serialized object.
        /// The deserialized object.
        public static T ReadToObject(string json)
        {
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json));
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            T obj = (T)ser.ReadObject(ms);
            ms.Close();
            return obj;
        }
    }
}

The HTTP endpoint that processes the error report, a PHP file.


You hook the manager with the UnhandledExceptionHandler of your App.xaml.cs

LittleWatsonManager.SaveExceptionForReporting(e.ExceptionObject as Exception);

And then add some code in your MainPage to report previous errors to your website:

            ExceptionContainer exception = LittleWatsonManager.GetPreviousException();

            if (exception != null)
            {
                if (LittleWatsonManager.Instance.AllowAnonymousHttpReporting)
                {
                    LittleWatsonManager.Instance.SendExceptionToHttpEndpoint(exception);
                }
                else
                {
                    // show popup.
                    this.notification.Show("Unhandled exception found", new SolidColorBrush(Colors.Red), null);
                }
            }

To summary what happens. An unhanled exception happens and is caught by your apps unhandled exception handler which contains the SaveExceptionForReporting() method to save the exception. The next time the user starts your application the code in your MainPage.xaml.cs will check if there was any exception that needs to be reporting. Depending on the settings this exception will be pushed to a HTTP endpoint as a POST request with a variable ‘exception’ that contains the original exception message and stacktrace. In my example the message is then e-mailed to my personal e-mailaddress. If the user opt-outs on this, you can still show him a popup and ask him to send the error report. You should allow the user to change these settings. This can achieved by changing the AllowAnonymousHttpReporting boolean of the manager.

You should make a settings page that lets the user select the option to “Send application error reports automatically and anonymously to ‘companyname’ to help us improve the application”.

So that’s it. Hope you like it!

Let me know if you have any questions.