Doing an unattended install of Windows 8 Pro on the HP MediaSmart Server (Ex470)

September 23, 2012

Recently I’ve wanted to experiment with whether Windows 8 would make a good home server (since there appears to be no future for WHS now, unfortunately).

Rather than buy some nice NAS form factor hardware to test things out, I thought maybe I would try to get Windows 8 to install on my aging Ex470. Its also super cute hardware, so I wanted a reason to run it some more.

Note: My Ex470 has the stock processor, but I have upgraded the RAM. The stock processor meets Win8’s minimum requirements but the stock RAM does not. If you want to run Windows8 you will need to at least upgrade your memory to a sufficient amount for Windows 8 (1GB). There are plenty of guides for how to accomplish this out there.

The problem, though, is that it is a completely headless machine. People out there have built various VGA adapters that you can buy to hook it up to a display, but one of my goals for this experiment was not to spend any excess money on it (I’ve just spent some cash on computer upgrades, and need to give it a rest for a while).

The solution? I decided to try and do a completely unattended install of Windows 8. There are a few challenges to doing this on the Ex470, though:

  1. You can’t see the bios screen to get it to boot off USB.
  2. It does not have a VGA hookup, so Remote Desktop MUST be enabled from the get go, along with the associated user accounts to allow for login.

Point number 1 is not a big deal. Other Ex47x enthusiasts out there have figured out the correct sequence of key presses to get the unit to boot off usb.

Point number 2 is also surmountable by application of some wizardry in the Windows System Image Manager.

So, how do we do it?

Note: What I’m going to describe below involves wiping the system disk in the Media Smart Server. Please make sure anything on that disk is backed up and its best to make sure no other disks are inserted into your server when doing any of the steps to follow. You can reinsert them after Windows 8 is installed.

First, we follow the first part of these directions to get the Windows System Image Manager set up to create our answer file, but there are a lot of settings from the latter part of the document that we do not want, because the purpose of that document is to get the machine to boot into audit mode, so you can mess around and test before putting the machine into OOBE (out of box experience?) mode. We don’t want any of that. We want the machine to boot into a fully usable state, complete with enabled Remote Desktop, A computer name, and an account to log onto.

So, add these components to the configuration passes (the way described in the document). For the ex47X you mostly want components to be prefixed with AMD64. I’m not sure about the newer MSS hardware, you may need to adjust some things to accommodate.

  • Pass: WindowsPE (when you right click the components to add, as described in the document, select this as the pass)
    • amd64_Microsoft-Windows-International-Core
      • Fill out InputLocale, SystemLocale, UILanguage, UserLocale (I set them all to en-US)
    • amd64_Microsoft-Windows-International-Core/SetupUILanguage
      • Fill out UILanguage (Again, I picked en-US, for my case)
      • WillShowUI -> OnError
    • amd64_Microsoft-Windows-Setup/DiskConfiguration/Disk
      • DiskId -> 0
      • WillWipeDisk -> true (Note! This means we are going to wipe the first disk! Make sure nothing important is on it, and best off just have the one disk in the MSS when running this)
    • amd64_Microsoft-Windows-Setup/DiskConfiguration/Disk/CreatePartitions/CreatePartition
      • Extend -> true
      • Order -> 1
      • Type -> Primary
    • amd64_Microsoft-Windows-Setup/DiskConfiguration/Disk/ModifyPartitions/ModifyPartition
      • Active -> true
      • Format -> NTFS
      • Label -> Windows (But you can call it whichever ;-) )
      • Letter -> C
      • Order -> 1
      • PartitionId -> 1
    • amd64_Microsoft-Windows-Setup/ImageInstall/OSImage
      • WillShowUI -> OnError
    • amd64_Microsoft-Windows-Setup/ImageInstall/OSImage/InstallTo
      • DiskID -> 0
      • PartitionID -> 1
    • amd64_Microsoft-Windows-Setup/UserData
      • AcceptEula -> true
    • amd64_Microsoft-Windows-Setup/UserData/ProductKey
      • Key -> The windows product key you want to use..
      • WillShowUI -> OnError
  • Pass: Specialize
    • amd64_Microsoft-Windows-Shell-Setup
      • ComputerName -> Whatever you want to call the server (I chose Server8)
      • ProductKey -> The windows product key you want to use.
      • Registered Owner -> Your Name
    • amd64_Microsoft-Windows-Shell-Setup/OEMInformation
      • HelpCustomized -> false
    • amd64_Microsoft-Windows-TerminalServices-LocalSessionManager
      • fDenyTSConnections -> false  (this enables RDP)
    • amd64_Microsoft-Windows-TerminalServices-RDP-WinStationExtensions
      • SecurityLayer -> 1
      • UserAuthentication -> 0 (from my understanding, these allow you to Remote in from older versions of Remote Desktop. Check the online doc for more options here).
    • amd64_Networking-MPSSVC-Svc/FirewallGroups/FirewallGroup
      • Active -> true
      • Group -> Remote Desktop
      • Key -> rd1
      • Profile -> all (these settings add an exception to the firewall for Remote Desktop)
  • Pass: auditSystem
    • amd64_Microsoft-Windows-Shell-Setup/UserAccounts/AdministratorPassword
      • Value -> The Administrator password you want.
  • Pass: oobeSystem
    • amd64_Microsoft-Windows-Deployment/Reseal
      • forceShutdownNow -> false
      • Mode -> OOBE
    • amd64_Microsoft-Windows-International-Core
      • InputLocale -> Your input locale (for me, en-US)
      • SystemLocale -> Your system local (for me, en-US)
      • UILanguage -> Your UI language (for me, en-US)
      • UserLocale -> Your User locale (for me. en-US)
    • amd64_Microsoft-Windows-Shell-Setup
      • TimeZone -> your time zone (for me Eastern Standard Time). Search for this setting online for the different options you can put.
    • amd64_Microsoft-Windows-Shell-Setup/OOBE
      • HideEulaPage -> true
      • HideOEMRegistrationScreen -> true
      • HideOnlineAccountScreens -> true
      • HideWirelessSetupInOOBE -> true
      • NetworkLocation -> home
      • ProtectYourPC -> 1
    • amd64_Microsoft-Windows-Shell-Setup/UserAccounts/AdministratorPassword
      • Value -> The administrator password you want to use (note: I think the admin account is still disabled by default though)
    • amd64_Microsoft-Windows-Shell-Setup/LocalAccounts/LocalAccount
      • DisplayName -> your display name
      • Group -> Administrators (for ease of remoting, you can change this later)
      • Name -> the user id
    • amd64_Microsoft-Windows-Shell-Setup/LocalAccounts/LocalAccount/Password
      • Value -> The password for the user account.

You can now validate (Tools -> Validate Answer File) and then save the answer file as Autounattend.xml. Then, make a bootable usb of the windows 8 install and put that xml file in the root of the key. Then:

  1. Make sure you backup anything useful on the system drive of the server, because its going to be wiped!
  2. Hook up that system drive to another machine and delete any partitions on it (the answer file we created above doesn’t seem to accomplish this, although you could probably add the relevant setting in there somewhere)
  3. You will have to repeat number 2 if something goes wrong and the install stops halfway.
  4. Put JUST the system drive into the Media Smart Server.
  5. Start with the server off.
  6. Insert the usb key with the windows 8 install and the Autounattend.xml file into the bottom usb port on the back of the server.
  7. Plug a usb keyboard into a different port on the server.
  8. Turn the server on and immediately start jamming the f12 key. Continue this until the rightmost led is a steady blue.
  9. Hit the down arrow 7 times and then hit enter.
  10. Windows 8 should now be installing. It will take a little while.
  11. If you can log onto your router, do so and keep refreshing the dhcp clients list, if you have a network cable hooked up to the MSS, eventually you should see the ComputerName that you recorded above show up. At this point you should be able to Remote Desktop into the machine using the credentials for the User Account that you created in the answer file above.

If your USB has an activity indicator light, you can make your job a bit easier by watching the indicator light to see that it starts buzzing away after you hit enter in step 9. This should confirm that the install has indeed started.

Good luck! Hopefully I haven’t missed a setting in what I have described above. Let me know if it works for you.

-Graham


Microsoft removes beating heart from WHS, crushes it.

November 23, 2010

Extremely irritating news today. Microsoft is taking a product I love and killing it. First the news came that the new version of Drive Extender would be neutered of its ability to have its disks read without the need of WHS (an important disaster recovery option, IMO), and now it seems that the Drive Extender “feature” will be removed entirely. I say “feature” because I (and most other WHS users, I would assume) consider Drive Extender to be the whole thing that WHS is, not some feature to be discarded. This simply amounts to Microsoft tactfully killing the product.

Years ago I examined all the options and came to the conclusion that RAID was just too much trouble in terms of a way to achieve redundancy in file storage. I was considering software raid options, but then lo and behold, I stumbled upon Windows Home Server, which provided a dead simple solution for extending and creating redundant storage without the need to match disk sizes and without the worry of needing to replace a controller with an identical version should it fail. I was completely sold.

Now I just feel left in the cold. Microsoft, if you plan to kill this product, please at least just have the decency to look me in the eye and tell me.

If you feel passionately about this please visit this connect link also.


Silverlight/WPF cross-compile gotcha

September 22, 2010

Here’s an interesting gotcha if you are trying to cross-compile between Silverlight and WPF. The default content alignment of a ContentControl’s content in Silverlight is left/top while in WPF it’s stretch/stretch! The Silverlight ContentControl respects the VerticalContentAlignment/HorizontalContentAlignment properties (which default to left/top) while the WPF one does not. Check out the default templates of ContentControl in Silverlight and WPF for some other “cute” differences.


Binding to Anonymous types in Silverlight

May 30, 2010

If you’ve ever tried to bind to anonymous type in Silverlight (or a WPF non full-trust XBAP) you will have wound up running into a pretty nasty gotcha. It seems like a natural enough thing to do, for example:

public partial class MainPage : UserControl
    {
        IList types;

        public MainPage()
        {
            InitializeComponent();

            types = (from type in
                            Assembly
                            .GetExecutingAssembly()
                            .GetTypes()
                        select new
                        {
                            Name = type.Name,
                            BaseTypeName = type.BaseType.Name
                        }).ToList();

            DataContext = types;
        }
    }

It seems like this should work, right? If you try it, though, you will see a whole lot of nothing and a bunch of MethodAccessExceptions in your output log.

Why does this happen?

This happens because the way Anonymous types are implemented is that, at compile time, the compiler will determine what type needs to be generated in the assembly based on the property names and inferred types and will do the necessary generation. The problem is, though, that this type is marked internal!

This would not be so bad except for the fact that the Silverlight and WPF binding system need to reflect against the type in order to set up a binding, which would be fine if you were doing that from inside you own assembly, but it is the binding system that is going to be doing the reflection, from System.Windows.

The immediate answer then, that should occur to you, is that you can make your assemblies internals visible to System.Windows.

[assembly: InternalsVisibleTo("System.Windows,
PublicKey=00240000048000009400000006020000002400
005253413100040000010001008D56C76F9E8649383049
F383C44BE0EC204181822A6C31CF5EB7EF486944D0321
88EA1D3920763712CCB12D75FB77E9811149E6148E5D
32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC
3D115810C3D9069638FE4BE215DBF795861920E5AB6F
7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1
C785B4305C123B37AB", AllInternalsVisible=false)]

And indeed this will work (line breaks in the public key above added for clarity). This was gleaned from reflectoring some of the system Silverlight assemblies that need to make their internals visible to System.Windows (where the binding engine sits).

Ok, so this is all well and good, if you want to display read only information from a linq query, as Anonymous types are immutable! But what if we want to harness the easy definition of these anonymous types as a template for mutable types, and what if we want these types to be public so we don’t need to bother with exposing our internals? Well, I was curious as to how hard it would be to adapt the code I wrote recently for generating dynamic types to implement INotifyPropertyChanged to generate types that had the same properties as an anonymous type except read-write and public.

Well, luckily enough for you, if you are interested in this stuff, I did it! I won’t go into a lot of detaills for how this is done as it follows the general pattern of my previous post on type generation where I explained in more detail, but the general gist is that it will reflect against the anonymous type (which it can do since it is in the same assembly, if you put this code in a different assembly you will need to InternalsVisibleTo it) and for each property there found, make a writable version on the type it is generating. This writable property will also be augmented to raise a PropertyChanged event for INotifyPropertyChanged (bonus!!), allowing for some pretty interesting scenarios.

Here’s how the usage would look:

    public partial class MainPage : UserControl
    {
        IList types;

        public MainPage()
        {
            InitializeComponent();

            types = (from type in
                            Assembly
                            .GetExecutingAssembly().GetTypes()
                        select new
                        {
                            Name = type.Name,
                            BaseTypeName = type.BaseType.Name
                        }.MakePublicAndMutable()).ToList();

            DataContext = types;
        }
    }

And here’s the code that does the dynamic type generation:

    public static class Extensions
    {
        public static object MakePublicAndMutable(this object obj)
        {
            return TypeGenerator.MakePublicAndMutable(obj);
        }
    }

    public class TypeGenerator
    {
        //note, not threadsafe currently ;-)
        private static AssemblyBuilder _ab;
        private static ModuleBuilder _mb;
        private static Dictionary<Type, Type> types =
            new Dictionary<Type, Type>();

        private static IEnumerable<PropertyInfo> GetProperties(
            Type type)
        {
            return from p in
                       type.GetProperties(
                           BindingFlags.Public |
                           BindingFlags.Instance |
                           BindingFlags.FlattenHierarchy)
                   select p;
        }

        public static object MakePublicAndMutable(object item)
        {
            List<object> parms = new List<object>();

            foreach (PropertyInfo prop in GetProperties(item.GetType()))
            {
                parms.Add(prop.GetValue(item, null));
            }

            Type type = GetAnonTypeProxyType(item.GetType());
            ConstructorInfo mi = type.GetConstructor(
                (from parm in parms select parm.GetType()).ToArray());

            return Activator.CreateInstance(type, parms.ToArray());
        }

        protected static Type GetAnonTypeProxyType(Type type)
        {
            Type ret;
            if (types.TryGetValue(type, out ret))
            {
                return ret;
            }

            if (_ab == null)
            {
                AssemblyName assmName = new AssemblyName("DynamicAssembly");
                _ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
                    assmName, AssemblyBuilderAccess.Run);
                _mb = _ab.DefineDynamicModule(assmName.Name);
            }

            TypeBuilder typeBuilder = _mb.DefineType(
            Guid.NewGuid().ToString() + "__proxy", TypeAttributes.Public);
            typeBuilder.AddInterfaceImplementation(
                typeof(INotifyPropertyChanged));

            FieldBuilder eventField =
                CreatePropertyChangedEvent(typeBuilder);

            MethodBuilder raisePropertyChanged =
                CreateRaisePropertyChanged(typeBuilder, eventField);

            var props = GetProperties(type);
            Dictionary<string, FieldBuilder> fields =
                new Dictionary<string, FieldBuilder>();

            CreateProperties(props, typeBuilder, fields,
                raisePropertyChanged);
            CreateDefaultConstructor(typeBuilder);
            CreateConstructor(props, typeBuilder, fields);

            ret = typeBuilder.CreateType();
            types.Add(type, ret);
            return ret;
        }

        private static MethodBuilder CreateRaisePropertyChanged(
            TypeBuilder typeBuilder, FieldBuilder eventField)
        {
            MethodBuilder raisePropertyChangedBuilder =
                typeBuilder.DefineMethod(
                "RaisePropertyChanged",
                MethodAttributes.Family | MethodAttributes.Virtual,
                null, new Type[] { typeof(string) });

            ILGenerator raisePropertyChangedIl =
            raisePropertyChangedBuilder.GetILGenerator();
            Label labelExit = raisePropertyChangedIl.DefineLabel();

            // if (PropertyChanged == null)
            // {
            //      return;
            // }
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_0);
            raisePropertyChangedIl.Emit(OpCodes.Ldfld, eventField);
            raisePropertyChangedIl.Emit(OpCodes.Ldnull);
            raisePropertyChangedIl.Emit(OpCodes.Ceq);
            raisePropertyChangedIl.Emit(OpCodes.Brtrue, labelExit);

            // this.PropertyChanged(this,
            // new PropertyChangedEventArgs(propertyName));
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_0);
            raisePropertyChangedIl.Emit(OpCodes.Ldfld, eventField);
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_0);
            raisePropertyChangedIl.Emit(OpCodes.Ldarg_1);
            raisePropertyChangedIl.Emit(OpCodes.Newobj,
                typeof(PropertyChangedEventArgs)
                .GetConstructor(new[] { typeof(string) }));
            raisePropertyChangedIl.EmitCall(OpCodes.Callvirt,
                typeof(PropertyChangedEventHandler)
                .GetMethod("Invoke"), null);

            // return;
            raisePropertyChangedIl.MarkLabel(labelExit);
            raisePropertyChangedIl.Emit(OpCodes.Ret);

            return raisePropertyChangedBuilder;
        }

        private static FieldBuilder CreatePropertyChangedEvent(
            TypeBuilder typeBuilder)
        {
            // public event PropertyChangedEventHandler PropertyChanged;
            FieldBuilder eventField =
                typeBuilder.DefineField("PropertyChanged",
                typeof(PropertyChangedEventHandler),
                FieldAttributes.Private);
            EventBuilder eventBuilder =
                typeBuilder.DefineEvent(
                "PropertyChanged",
                EventAttributes.None,
                typeof(PropertyChangedEventHandler));

            eventBuilder.SetAddOnMethod(
            CreateAddRemoveMethod(typeBuilder, eventField, true));
            eventBuilder.SetRemoveOnMethod(
            CreateAddRemoveMethod(typeBuilder, eventField, false));

            return eventField;
        }

        private static MethodBuilder CreateAddRemoveMethod(
            TypeBuilder typeBuilder,
            FieldBuilder eventField, bool isAdd)
        {
            string prefix = "remove_";
            string delegateAction = "Remove";
            if (isAdd)
            {
                prefix = "add_";
                delegateAction = "Combine";
            }
            MethodBuilder addremoveMethod =
            typeBuilder.DefineMethod(prefix + "PropertyChanged",
               MethodAttributes.Public |
               MethodAttributes.SpecialName |
               MethodAttributes.NewSlot |
               MethodAttributes.HideBySig |
               MethodAttributes.Virtual |
               MethodAttributes.Final,
               null,
               new[] { typeof(PropertyChangedEventHandler) });
            MethodImplAttributes eventMethodFlags =
                MethodImplAttributes.Managed |
                MethodImplAttributes.Synchronized;
            addremoveMethod.SetImplementationFlags(
                eventMethodFlags);

            ILGenerator ilGen = addremoveMethod.GetILGenerator();

            // PropertyChanged += value; // PropertyChanged -= value;
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldfld, eventField);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.EmitCall(OpCodes.Call,
                typeof(Delegate).GetMethod(
                delegateAction,
                new[] { typeof(Delegate), typeof(Delegate) }),
                null);
            ilGen.Emit(OpCodes.Castclass, typeof(
            PropertyChangedEventHandler));
            ilGen.Emit(OpCodes.Stfld, eventField);
            ilGen.Emit(OpCodes.Ret);

            MethodInfo intAddRemoveMethod =
            typeof(INotifyPropertyChanged).GetMethod(
            prefix + "PropertyChanged");
            typeBuilder.DefineMethodOverride(
            addremoveMethod, intAddRemoveMethod);

            return addremoveMethod;
        }

        private static void CreateDefaultConstructor(
           TypeBuilder typeBuilder)
        {
            ConstructorBuilder cons = typeBuilder.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                null);
            ILGenerator ilGen = cons.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call,
                typeof(object).GetConstructor(new Type[0]));
            ilGen.Emit(OpCodes.Ret);
        }

        private static void CreateConstructor(
            IEnumerable<PropertyInfo> props,
            TypeBuilder typeBuilder,
            Dictionary<string, FieldBuilder> fields)
        {
            var paramTypes = from p in props select p.PropertyType;
            ConstructorBuilder cons = typeBuilder.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                paramTypes.ToArray());
            ILGenerator ilGen = cons.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call,
                typeof(object).GetConstructor(new Type[0]));

            int count = 1;
            foreach (PropertyInfo info in props)
            {
                ilGen.Emit(OpCodes.Ldarg_0);
                ilGen.Emit(OpCodes.Ldarg, count);
                ilGen.Emit(OpCodes.Stfld, fields[info.Name]);
                count++;
            }

            ilGen.Emit(OpCodes.Ret);
        }

        private static void CreateProperties(
            IEnumerable<PropertyInfo> props,
            TypeBuilder typeBuilder,
            Dictionary<string, FieldBuilder> fields,
            MethodBuilder raisePropertyChanged)
        {
            foreach (PropertyInfo info in props)
            {
                PropertyBuilder pb = typeBuilder.DefineProperty(
                    info.Name, PropertyAttributes.None,
                    info.PropertyType,
                    (from p in info.GetIndexParameters()
                     select p.ParameterType).ToArray());

                FieldBuilder backingField = typeBuilder.DefineField(
                    "_" + info.Name,
                    info.PropertyType,
                    FieldAttributes.Private);
                fields.Add(info.Name, backingField);

                MethodInfo getMethod = info.GetGetMethod();
                if (getMethod != null)
                {
                    pb.SetGetMethod(
                        CreateGetMethod(info, getMethod,
                        backingField, typeBuilder));

                    pb.SetSetMethod(
                        CreateSetMethod(info, getMethod, backingField,
                        typeBuilder, raisePropertyChanged));
                }
            }
        }

        private static MethodBuilder CreateGetMethod(
            PropertyInfo info,
            MethodInfo toWrap,
            FieldBuilder backingField,
            TypeBuilder typeBuilder)
        {
            MethodInfo orig = toWrap;
            var paramTypes = (from p in orig.GetParameters()
                              select p.ParameterType).ToArray();
            MethodBuilder mb = typeBuilder.DefineMethod(
                orig.Name,
                MethodAttributes.Public |
                MethodAttributes.HideBySig,
                orig.ReturnType, paramTypes);

            ILGenerator ilGen = mb.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldfld, backingField);
            ilGen.Emit(OpCodes.Ret);

            return mb;
        }

        private static MethodBuilder CreateSetMethod(
            PropertyInfo info,
            MethodInfo toWrap,
            FieldBuilder backingField,
            TypeBuilder typeBuilder,
            MethodBuilder raisePropertyChanged)
        {
            MethodInfo orig = toWrap;
            var origParamTypes = (from p in orig.GetParameters()
                                  select p.ParameterType).ToArray();
            List<Type> paramTypes = new List<Type>();
            if (orig.ReturnType != typeof(void))
            {
                paramTypes.Add(orig.ReturnType);
            }
            paramTypes.AddRange(origParamTypes);

            MethodBuilder mb = typeBuilder.DefineMethod(
                "set_" + info.Name,
                MethodAttributes.Public |
                MethodAttributes.HideBySig,
                null, paramTypes.ToArray());

            ILGenerator ilGen = mb.GetILGenerator();
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.Emit(OpCodes.Stfld, backingField);

            // RaisePropertyChanged("[PropertyName]");
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Ldstr, info.Name);
            ilGen.EmitCall(
            OpCodes.Call, raisePropertyChanged, null);

            ilGen.Emit(OpCodes.Ret);

            return mb;
        }
    }

Again see my previous post for some Reflection.Emit details if you don’t understand the above. And feel free to shoot me some questions!


Windows Home Server v2 (VAIL), my initial reaction

April 30, 2010
  1. Please bring back some way to read the files on a disk from a non-VAIL machine! This is an important disaster recovery technique and a major selling point of V1 (Connect Link – Please Vote)
  2. Please consider adding the option for a second authentication factor to the remote access site login. (Whether it be MS or 3rd party provided through add-ins) (Connect Link – Please Vote)

I’ll tally some more detailed feedback as I look more into the changes. As it stands though, I’m a bit too disappointed with the changes in Drive Extender to install the Preview in its current state. I hope Microsoft reconsiders its deprecation of major features that contributed to the platform being attractive in the first place.


Working with collection-typed Dependency Properties, part 2

April 27, 2010

In my last post on this subject, I was trying to track down a good way to avoid setting the default for a collection typed Dependency Property in the constructor. See my the other post as to why this makes the property harder to style. I’ve come up with some interesting ideas, but there are still some interesting quirks with how the Xaml design surfaces treat these collections and the associated styles.

The root problem I was trying to address is that when you have xaml such as this:

<local:Palette x:Name="thePalette">
    <local:Palette.Colors>
        <Color>Blue</Color>
        <Color>Orange</Color>
    </local:Palette.Colors>
</local:Palette>

And you have a Color dependency property that is not initialized in the default value or the constructor but is initialized in the style:

<Setter Property="Colors">
    <Setter.Value>
        <local:ColorCollection>
            <Color>Red</Color>
            <Color>Blue</Color>
        </local:ColorCollection>
    </Setter.Value>
</Setter>

Then, oddly, things will appear to work correctly in the designer. Meanwhile, at runtime, as expected, when the Xaml parser goes to add the colors to the Colors property it will find that the Colors property is null.

Silverlight 4 actually gives a good way to avoid this behavior in the form of the ISupportInitialize event. When we have our control implement this class ISupportInitialize.BeginInit will be called when the Xaml parser starts parsing the element, and ISupportInitialize.EndInit will get called when the Xaml parser finishes parsing the element, before the style is applied. Thus, if we want to ensure that there is an empty local collection for the parser to fill, but not leave the collection as a local value if the parse doesn’t find any local values (so that the style precedence properties can apply), we can do something like this:

        private ColorCollection _initialColors =
            new ColorCollection();

        public void BeginInit()
        {
            if (ReadLocalValue(ColorsProperty) ==
                DependencyProperty.UnsetValue)
            {
                _initialColors.Clear();
                Colors = _initialColors;
            }
        }

        public void EndInit()
        {
            if (GetValue(ColorsProperty) == _initialColors &&
                _initialColors.Count == 0)
            {
                ClearValue(ColorsProperty);
            }
            UpdateColors();
        }

Which will basically set a local value as the Xaml parser begins to parse the element, but if no items are added to the collection, clear the value when done parsing, so that the style values can take precedence.

This seems to work for fixing the runtime problem. However, there seem to be other problems with creating style-able collection properties when it comes to the designer. If you, for example start typing out the Palette as above, the designer will apply its style before you even get to the Colors property, so, when you are adding the items to the Colors property collection, you will be modifying the collection from the default style of the Palette, so the Colors properties all Palette instances will be affected. Fun, huh? So I’m left wondering if its a good idea currently to try and create style-able collection-typed Dependency Properties with the current tool support, given their current behavior.

It would nice if we could somehow indicate to Blend/VS that this is the only correct way to specify the Colors collection in Xaml:

<local:Palette x:Name="thePalette">
    <local:Palette.Colors>
        <local:ColorCollection>
            <Color>Blue</Color>
            <Color>Orange</Color>
        </local:ColorCollection>
    </local:Palette.Colors>
</local:Palette>

But I haven’t come across a way to do this yet….


Dead PS3. A case for cloud storage for save games.

April 26, 2010

My PS3 unceremoniously died last night, making me sad. The fact that its out of warranty and I need to pay to fix it makes me sad. The fact that I’ll likely have to wait 6 weeks makes me even sadder. But what makes me most sad is that all my save games will not survive the process. There is an easy and useful mechanism for backing up save games for the PS3, but I cannot use this if I cannot get into the system’s OS (it’s that broken).

I cannot take the hdd out and extract the save games because the drive is heavily protected and encrypted. This is understandable because having user modifiable save games would introduce an attack vector that Sony would rather not have to worry about, but it does present some rather ugly usability issues. Above and beyond the fact that I’m now unable to extract my save games, even if I had had the foresight to do this, some games do not allow their saves to be transferred to a different system than which originally hosted them.

I believe this must be to prevent some sort of cheating or abuse of the trophy system. Apparently some games can be transitioned between systems, but then disable certain features, such as trophies, for that save file. So, this brings me to my point. Sony is researching ways to monetize PSN by all rumors, so here’s an easy one:

I would pay a small recurring sum to have my save games automatically synchronized with PSN and associated with my account so that I could use them from any PS3 associated with my account. This would be a really nice value add. It would certainly make sense especially for games that you can play on both the PSP and the PS3 (ps1 classics), imagine automatically sharing a save file for a game you were playing on both devices? So, a nice feature for disaster scenarios AND a value add to the platform. Sounds good? Now to wait 6 weeks before having to replay the games I was in progress of completing.


Follow

Get every new post delivered to your Inbox.