#begin
On March 16 this year (2021) an old colleague of mine contacted me in regard to some questions he had concerning a build and deployment pipeline I once helped set up. He had some specific questions that had to do with a stage in the pipeline where we create windows installers based on the build output. Each time a new version was installed an unins<number>.exe would remain in the directory. So after installing 3 versions of an application there would be unins0.exe, unins1.exe and unins2.exe and so forth. The rest of the files would be removed or overwritten as they should by the installer setup. I could not really predict why this would be happening, other than the software used to create these installers must be updated. I think, in the end, that’s what they did to solve this particular problem.
However, this sparked an idea. I checked the Unity3D asset store if there were any packages on the store that would allow users to create installers for windows. And funny enough, there weren’t any assets in the store for creating installers. So I though, let’s write a simple Unity3D plugin that uses InnoSetup to create nice Windows installers for all your Unity3D applications. I was really surprised by the fact that there wasn’t any editor plugin available for this, but yeah; a neat little project I could work on next. You can find it here.
InnoSetup
So, the first thing I needed to do was to read the docs on InnoSetup. It’d been a long time since I checked out these docs because there wasn’t a reason for me to read them. But I needed to re-check if I could run InnoSetup from the command-line. And of course, you can run Inno from the command line with specific commands to compile new setup wizards. Next I checked out which data is required for any setup to compile. InnoSetup simply uses a .iss source file with a bunch of settings and configurations for compilation. These settings are categorized in things they call “Sections”. So for example, you have a Setup Section where al general/global variables are declared, and you have a Files Section where all directories and files are listed to copy to the user’s system upon installation.
Thus, InnoSetup is a piece of software to aggregate and create custom made setup wizards for windows software. I’ve been using it for a long time now and it always works great. It also supports custom script files, written in… Pascal Script…yuck! What a goofy choice for a language to choose. It’s probably related to the fact that InnoSetup was introduced back in 1997!
Once I installed InnoSetup on my pc I was prompted to create a new setup file. Inno guides you through some simple questions about the software you want to create an installer for like; the name of your application, what languages do you support, and, do you want the user to be able to toggle a checkbox to create a desktop shortcut. This was really nice and it allowed me to create a nice source file as a reference for what I needed to generate. I also contacted an old colleague to ask if he could send me an obfuscated/declassified (specifics were not important) .iss source file they use in the CI pileline. I asked him because I knew we had made some special settings to change the naming in source .exe files etc. for optimal customization. He send me a file and now I had 2 pieces of reference material to use for my Unity3D plugin. More than enough to get started!
Wizard
The next thing I needed to do was to create a new project. I first called it “UnityNstaller’ but there wasn’t enough magic there, so I later renamed to to “Wizard“. Which sounds way better of course. But, really, the first thing I did was to start a new Unit-Test to write the output of a line in the .iss file that should be the output of Wizard. I always like to start with some nice, independent Unit-Testable part of the software first. I wrote a simple “Option” class and made it implement an interface called “ITranspilable” which forces you to implement a method to transpile the option data to a single line of the .iss file. Next I added some more option types like boolean which transpiles to <name>=<yes>|<no> and an option that allowed for a selection from multiple predefined values, and much more.
When I got some nice Unit-Tests running and all options I needed to re-create the reference .iss files were done I needed to start thinking about persistence. In small projects like these I don’t like dependencies to (large) external libraries. For persistence in Unity3D you often use JSON but I have always used Newtonsoft JSON since it is simply the best for .Net. But for this project, I did not want this dependency to I chose to dabble around with Unity’s build in JSON serializer.
I’ve done some small minor things with Unity’s serializer but never something complex. I was quickly reminded that Unity’s serializer is really limited compared to Newtonsoft. This is related to the fact that it is “high performant” to comply with Unity’s standards. To be able to serialize and deserialize my options class (which implement nice generic class parameters), I ended up writing my own serializer and deserializer. Not really using Unity’s JSON, but simply by creating another class called “SerializableOption” which stores the source type, key and values. These values are all strings and can be serialized in a simple ScriptableObject (SO) called the config. This is something Unity’s serializer supports out of the box, so I did not need to do any custom JSON parsing anymore. My deserializer simply takes the type, and creates a class from that with the value array as arguments to the constructor on the class of that type. Then, it stores the class in section based on it’s key.
The UI
My next problem was to create some UI for my config. At this point, I had my nice generic typed option classes which I use as entity classes in Wizard and serializable options as which they are persisted in the SO. I tried many different implementations for my UI without creating too much dependencies. I settled for a nice Model-View-Presenter kind of pattern. I created Views and Presenters for each of my settings and made them all operate on the serializable option class. Which still isn’t optimal since my UI now essentially knows about the data layer, but the config SO knows about them anyway so I though it was good enough.
To couple the right View and Presenter to the options I also added presenters per section. These presenters are responsible for creating the correct presenters for each option. A simple example is the SetupPresenter. A nice bonus here was that now my UI was completely decoupled from my data, and thus, I can Unit-Test all this stuff again. And that’s exactly what I did. However, one stupid thing to note here, is that when you call any of the Editor UI functions in a Unit-Test, without showing an instantiated window or target UI element, Unity will throw exceptions. This makes you write your tests without calling any UI logic, which seems reasonable, but still should not throw exceptions if you ask me, since these editor API’s are all static, and not bound to any instanced object.
CLI
Another important feature I wanted to add is that Wizard must be accessible by custom startup parameters. You can start Unity3D with additional startup arguments for all sorts of scenario’s. For example, you can start Unity with commands to build your project, run Unit-Tests, activate your license file and much more. Check them out right here if you did not know about them.
Then, I also wanted to support two flavors of startup commands. The first; Editor startup commands. With these commands you can start the Unity3D editor and call a specific function with specific parameters if you parse them correctly. Second, startup parameters to start the actual compiled build. I wanted to support both to make sure you can always access Wizard even if you do not have a Unity3D editor available. So this means, you can start your compiled build and it will make an installer wizard for itself. Cool, right?
In order to support this I needed to add a nice CLI class with a couple of static methods which are invoked when the startup commands are detected and properly formatted. This is where all the CLI magic happens and it also runs a function when a scene with buildindex 0 starts for the first time. Then it will read the startup arguments from the environment and if they exist wizard will show some magic.
Running InnoSetup
The last thing left to do was to feed my generated .iss file into InnoSetup for compilation. Setting this up was rather easy since InnoSetup supports a nice CLI itself. The more difficult part was to make sure InnoSetup is installed and updated. And if it was not the latest version or not even installed it needs to be downloaded and installed before any wizardry can happen.
To fix this I implemented a simple adapter class (not really an adapter pattern but I think the name supports my thoughts). This adapter class is forms the connection from Wizard to InnoSetup. It can be used to check whether InnoSetup is installed, updated, run commands to InnoSetup CLI and download a new version. It is a rather primitive API since the webpage where you can download InnoSetup does not have a nicely layed out API. For example; I needed to write a simple scraper to check what the latest version of InnoSetup was, based on some information page about versions. This scraper is really, really primitive, I’m not really proud of it but it works for now so I’ll leave it as is until I start to get feature requests or bug reports.
Conclusion
Well I’ve had some real fun writing this thing. I really did not expect that there wasn’t a single similar asset in the Unity3D asset store. Everything I implemented up till now is enough for simple Unity3D projects. I’m well aware that my implementation is far from complete compared to the total number of configurations InnoSetup supports. But, for now, it does it’s thing.
Also, the UI is till rather primitive. I would like to implement a nice Editor UI based on the new UIElements API that Unity supports now. But first I hope to see some feature requests in the future so I can make it more useful to other people who want to deploy installer wizards to their customers. Forget those clumsy .zip archives!
Use Wizard and deliver some magic instead!
#end
01010010 01110101 01100010 01100101 01101110
Recent Comments