Issues Creating In-House BoxStarter Packages with Dependencies and Reboots

Aug 27, 2014 at 9:41 PM
I’ve been working with BoxStarter for a few months now and have built up a library of in-house BoxStarter packages which essentially wrap known-good installs (both third party and proprietary). Until now I haven’t had to leverage BoxStarter’s reboot handling capabilities but now that it’s required to configure our VM’s I’m running into problems. It may be a complete misunderstanding of how BoxStarter manages reboots (and thus how to design packages and their dependencies). Here’s what I’m trying to do.

Assume the following:

• I have in-house packages A1, A2, A3, B1, C1, and C2.
• A packages should be installed in order followed by a reboot, then B packages, reboot, then C packages.
• Package An has a dependency on An-1 where n > 1.
• Package C2 has a dependency on C1.
• C packages have dependencies on B packages and B packages depend on A packages.

So by calling:
Install-BoxStarterPackage A3
I’d expect A1, A2, then A3 to be installed.
Install-BoxStarterPackage C1
Should result in A1, A2, and A3 being installed, followed by a reboot, then B1, reboot, and C1
Install-BoxStarterPackage C1
Everything should be installed: A1, A2, A3, reboot, B1, reboot, C1, C2.

Early in my experimentation I was using Restart-Computer in packages A3 and B1 to force the reboots but the result was (probably obvious to most gurus here) that the computer would indeed reboot but BoxStarter wouldn’t automatically log in and get things ramped back up from where it had left off. After much gnashing of the teeth I discovered the Invoke-Reboot script and while this at first seemed promising, all it did was write “writing restart file” to the console/log, write out the appropriate information to that file, then immediately exit the current package then go on to install the next package without rebooting (i.e. A1, A2, A3, B1, C1, C2, without reboots in between—but then at the end BoxStarter forced the reboot!). Dug into the Invoke-Reboot script and see that it calls Restart():
function Restart {
    exit
}
In other words (and unless I’m missing something obvious) while Invoke-Reboot seems to do some important things required by BoxStarter to gracefully handle reboots, in the end it simply exits and returns execution to the calling script/process which then decides to continue installing packages without a reboot having occurred.

What am I missing?
Coordinator
Aug 28, 2014 at 8:24 AM
Hi there!

You are definitely correct that Restart-Computer is not the correct path and I apologize that the docs have not done a good job of steering you better.

Most are not using Boxstarter in such a sophisticated manner and I realize the path is not at all inuitive but here is what I'd suggest:
  1. Only use the Install-BoxstarterPackage for the initial Boxstarter invocation. Do not use it from inside a package to invoke another. Instead, use cinst from inside a package to install another. Install-BoxstarterPackage sets up a bunch of overhead to make Invoke-reboot work. As you noticed, Invoke-Reboot sets up more stuff and then simply calls exit. Yeah...not too intuitive. What is supposed to happen next is the finally block of Invoke-Boxstarter, an internal command, actually performs the restart but my guess is that it is not handling this correctly when nested. That is something that needs to be addressed but in the meantime I think you can get around this by just using cinst from inside the packages.
  2. Ideally, you should not even need to call Invoke-reboot. I say "Ideally" but it really depends on what you are doing. If you are just trying to avoid failures that would be caused by pending reboots. For example, installing the .net 3.5 framework which is required by a sql server install but often will fail the sql server install becuse it will complain that a reboot is necessary -- those types of scenarios. Boxstarter is able to automatically detect that and should reboot for you. However, if you have special logic that requires a reboot for some other reason, that is when you want to call invoke-reboot. A good example here is after joining a domain. A reboot is required but windows is not technically in a pending reboot state.
I hope this makes sense. Please lket me know if this is not clear or just does not work.

Thanks!!
Aug 28, 2014 at 10:37 PM
Hey MWRock,

Thanks for your quick response. Not sure we're using BoxStarter in a “sophisticated manner”, I think I may have simply been trying to leverage Chocolatey's dependency handling capabilities a wee more than BoxStarter is happy with.

In regard to your suggestions...

I am not using Install-BoxstarterPackage for invocations within packages, only to call the initial package.

Referring to my previous example, I call:
Install-BoxStarterPackage C2
None of the packages have calls to CINST or Install-BoxStarterPackage. Instead they use Chocolatey’s package dependency feature defined in the *.nuspec files. For example, the C2.nuspec file would look like this:
<?xml version="1.0"?>
<package>
  <metadata>
    <id>C2</id>
    <version>1.0</version>
    <authors>Some Guy</authors>
    <owners>Some Team</owners>
    <description>Installs package C v2.0 and all it's dependencies.</description>
    <tags>Boxstarter</tags>
    <dependencies>
      <dependency id="C" version="1.0" />
    </dependencies>
  </metadata>
</package>
Based on my experience with the technologies so far seems like Chocolatey is handling these dependencies exactly as I’d expect, installing A1, A2, A3, B1, then C1, before getting to C2 (assuming the *.nuspec files for the other packages have their dependencies defined correctly). If I understand what you’re saying BoxStarter may not play well with Chocolatey if and when Chocolatey packages have dependencies defined in this way and causes reboots within the package chain.

The benefit of using Chocolatey’s dependency mechanism, in my view, is the ability to only have to specify the top level dependencies for a given package without worrying about any dependencies on down the line; the child packages are aware of their own dependencies, obfuscating them from the package one’s requesting (e.g. C2). Calling CINST, instead, would require a package to know all of it’s dependencies, child dependencies, etc., up front; not a big deal, but maintenance could become a nightmare, especially as our package library grows.

If that’s the way to go I’ll give it a shot, but I’m running into another issue. As mentioned previously we have our own set of internal package libraries which we point BoxStarter to with:
Set-BoxstarterConfig -LocalRepo \\<Some Machine>\<Our BoxStarter Package Library Root Folder>
But when I call CINST for one of our internal packages, say sw-stuff, I get:
C:\Windows\system32> cinst sw-stuff
Chocolatey (v0.9.8.23) is installing 'sw-stuff' and dependencies. By installing you accept the license for 'sw-stuff' and each dependency you are installing.
Unable to find package 'sw-stuff'.

Command 'install' failed (sometimes this indicates a partial failure). Additional info/packages: sw-stuff
Reading environment variables from registry. Please wait... Done.
If I can get CINST working for our local packages then your solution should (hopefully) do the trick.

In terms of your second point, what I’ve been doing to get us by for now is tell the installs encapsulated by my packages to run silently and ignore reboots then, once the install is done, call Invoke-Reboot (previously Restart-Computer). I was under the assumption that if I allowed the install to cause the reboot this would prevent BoxStarter from regaining control again until after the machine came back up. If I understand you correctly I should allow the install to start the reboot, then BoxStarter will automagically detect this while it’s in progress?
Aug 29, 2014 at 12:35 AM
BTW - I found the following which I assume is the equivalent of using CINST:
choco install <some package> -source \\<Some Machine>\<Our BoxStarter Package Library Root Folder>
Let me know if my assumption is correct.
Aug 29, 2014 at 12:37 AM
Better yet:
cinst <some package> -source \\<Some Machine>\<Our BoxStarter Package Library Root Folder> -Force
Coordinator
Aug 30, 2014 at 8:13 AM
Hi Ryan,

A couple things here.

Regarding the way to declare dependencies. I agree that declaring dependencies in the nuspec file is the most elegant approach. There are issues here with Boxstarter that prevent it from hooking into install process and therefore may not automatically detect pending reboot states. However, my assumption would be that at least invoke-reboot would still force a reboot however it sounds like you are saying that is not working. I'll investigate that.

Regarding chnging the LocalRepo path, that will only affect the search path of Install-BoxstarterPackage but you correctly observe that calling cinst (or its aliases) using -source and -force have the same effect.
Sep 2, 2014 at 10:48 PM
Perfect! I am now becoming a full BoxStarter Jedi! Thanks mate.
Sep 23, 2014 at 7:17 PM
Hey MWRock,

Per your instructions I've refactored how my BoxStarter packages are organized and I'm 99% of the way there. I'm running into a couple of cases where I need to force a reboot and am not having success with either Invoke-reboot or forcing a reboot with Restart-Computer or ShutDown (the latter two of which are not, to my understanding, the way to go--and yes, these will force a reboot, but when the machine logs back in BoxStarter doesn't start back up where it left off).

Example: Suppose I have package A and package B. In order for package B to install successfully package A must install then the machine must be rebooted. Here's the truncated version of my attempt with Invoke-Reboot:

Initial Call:
Install-BoxStarterPackage MyPackage
MyPackage:
cinst PackageA
cinst PackageB
PackageA:
# Code to launch the installer for Package A
Invoke-Reboot
PackageB:
# Code to launch the installer for Package B which fails because a reboot didn't occur after A
Coordinator
Sep 24, 2014 at 10:10 AM
I'll investigate. That is not the intended behavior.
Coordinator
Sep 24, 2014 at 4:25 PM
I am able to reproduce. The nested exit does not propperly end the run. I think I have a fix and hope to release today.
Coordinator
Sep 24, 2014 at 5:22 PM
pushed 2.4.123 with what should be a fix. Your script should be fine. Let me know if this fixes things for you. Thanks for your patience and reporting this!!
Sep 26, 2014 at 9:13 PM
Looking much better. If I have invoke-reboot in PackageA it works as expected. Now I'm curious, what if I want to force the reboot in the calling package (MyPackage, as shown above)? That doesn't seem to work. As designed?
Coordinator
Sep 29, 2014 at 6:36 AM
Hi Ryan,

Possibly I'm missing what you are expecting. I created 3 dummy packages: A, B and C, Package A is called directly and it calls packages B and C. I have edited package a to invoke a reboot in between the calls to packages B and C, So my expectation is that package B should run fine but package C would never be called because the explicit Invoke-Reboot interrupts the run before it gets to Package C and performs the reboot., Then everything starts over and repeats.

Is this different from what you are seeing?

Matt