Down the “Exit Code” rabbit hole
Over the last few weeks I have been neck deep in reworking the way Px Tools handles Exit Codes, which has included extensive notes on the vagaries of Exit Code usage by Autodesk and others, to ensure that Px Tools 4.0 handles this better and more flexibly/extensibly than 3.X.
Error Codes are, of course, numeric codes that are mapped to human readable messages. When you install manually the installer will provide you with a dialog when needed, with the meaningful message, like the above example when installing Revit 2020.0.1 on a machine that doesn’t have Revit. But when you are automating installs you will only get the error code, which you can then look up using resources from Microsoft. You’ll notice that each Error Code actually has three parts, the code, a name, and a message. So from here on out I’ll be talking about the code and the name, since the name usually provides enough information for our purposes here. We’ll talk again about the messages towards the end.
Now, the first thing to get out of the way is the nomenclature. You will almost always see the term Error Code, but they also code for success, and success with caveats. So, conceptually I think of them as “Exit Codes” rather than “Error Codes”. But Microsoft insists on the term Error Code, and then offers codes like 0 ERROR_SUCCESS with a straight face. Programmers! 🙂
In any case, 0 is the code you most want to see when automating an install. It means everything worked fine, no caveats, no warnings, no further action required. But, due to the way Microsoft handles shared libraries another code you will see often is 1641 ERROR_SUCCESS_REBOOT_INITIATED. This occurs when an installer updates a library like .NET or C++ which is currently memory resident, and so any program needing that library would use the memory resident older version without a reboot to clear memory. So the installer initiates that reboot, and you get an Exit Code explaining the reboot.
But, when you are automating installs you normally want to gang your installs together, so you use a command line flag on the installer such as /norestart so that multiple installs can complete. This suppresses the automatic reboot, and changes the Exit Code to 3010 ERROR_SUCCESS_REBOOT_REQUIRED. The key difference being that YOU now have to handle that reboot. This one can really bite you, as you could easily create a script that installs updates to something like Revit, and run that script using SCCM “behind” a logged in user. That user could have already opened something like email that uses an updated library, so that library is already loaded in memory. Without a reboot that user can launch the updated Revit or AutoCAD, but the library in use will be the old one, and a crash or other issues can result. It’s one of the subtleties to be aware of when trying to automate updates AND minimize impacts on staff. My best practice here is to only do installs as a dedicated install user on a machine in which no one else is logged in, and automatically reboot at completion. Now the user can log in and be sure of using the newest libraries. But I digress a bit. Back to Exit Codes!
Another Exit Code you may see is 1642 ERROR_PATCH_TARGET_NOT_FOUND, where the update can’t run because the “target”, i.e. “version” isn’t found, either because it isn’t installed, or what is installed is either a newer or older version, but specifically not a version that can be updated with the installer in question. This used to be really common when one Autodesk update would require a specific earlier version in order to install, say a point release that required that the last security fix be installed. You also often see it when trying to install an update on a machine that already has an even newer update. But not always, sometimes you get a 0 back there, because whoever created the update package built it to consider this a success. I suspect this is the case with “rollup” updates. Since Revit 2019.2 INCLUDES 2019.1, an attempt to install 2019.1 on a machine currently at 2019.2 would logically be a success, in the sense that 2019.1 is already there, as part of the rollup. And indeed, installing 2019.1 on a machine that has 2019.1 will give you a 0.
You may also run into 1638 ERROR_PRODUCT_VERSION. This happens a lot when you “unbundle” the C4R installers because you want to manage them separately from the Revit update itself. Basically the “update” isn’t an update, it’s a fresh install, and it doesn’t handle the uninstall, which would be handled by the wrapper EXE if you weren’t unbundling. That may be a good topic for another post, but again I digress.
Thus far everything has been pretty easy, and probably familiar to most people. And if that was all there was to it, there would be no reason for this post. But, over the last few years especially you may have started seeing a lot more Exit Codes that take the form of a 10 digit negative number rather than a four or fewer digit positive number. And these numbers aren’t in the usual documentation references like the one above from Microsoft. So what’s going on here?
Well, first we need to understand that the Exit Codes we have been talking about are Win32 Error Codes, and more specifically the subset of Win32 that applies to installs (and uninstalls) done using MSIEXEC.EXE. This Microsoft doc provides a MUCH larger list of errors, with the now familiar ones sprinkled in, but with different numbers.
These are actually the same numbers, just represented as 32 bit “hex” numbers, as in “hexidecimal”. Which just means there are 16 possible digits rather than 10, so you count 0,1,2,3…7,8,9,A,B,C,D,E,F. And being a 32 bit number, there are 8 digits available to build the number. Also the 0x at the front also tells you this is a hex number. So 0 is there, in the form 0x00000000, and the rest like this.
0x00000000 (0) ERROR_SUCCESS 0x00000666 (1638) ERROR_PRODUCT_VERSION 0x00000669 (1641) ERROR_SUCCESS_REBOOT_INITIATED 0x0000066A (1642) ERROR_PATCH_TARGET_NOT_FOUND 0x00000BC2 (3010) ERROR_SUCCESS_REBOOT_REQUIRED
As you look around that document you will see lots of codes for things that aren’t install specific, like 0x00000008 ERROR_NOT_ENOUGH_MEMORY. Windows programs use these Error Codes internally for all sorts of things. Thus far, still nothing that exciting beyond a new way to represent the numbers. The key here is that Microsoft obviously considers Win32 to be the correct numbering scheme for install errors, and positive integers the correct representation, since that’s the ONLY way they document their own Error Codes for MSIEXEC. And this makes a lot of sense. Error Codes in general are something programmers see, so hex representation makes sense, but Install Error Codes are something that IT folks and even regular users might see, and that audience is better served with short, sweet four digit positive integers.
However, there is another world of Error Codes, HRESULT. This is an actual Data Type, most commonly used for COM programming. And it was invented for the IBM/Microsoft OS/2 Operating System along with the shared COM Object Model, which is also supported (somewhat) in Apple’s Mac OS and other OSs. And one of the key features is that individual digits in the hex form of the code specify certain information, such as the source of the error. It’s a much more complex system, that allows for Error Codes on all sorts of machines and all sorts of capabilities, all at the expense of human readability in exchange for machine speed amongst other things. So, what does this all have to do with us you ask? And if you are asking that, thanks for sticking with me on this!
So, in HRESULT there is an ERROR_SUCCESS_REBOOT_REQUIRED as well, 0xFFFFFFFF80070BC2. This is a 64 bit hex number, so 16 total digits, but only 32 bits are used for the code and HRESULT puts F in all the unused digits. The next four digits provide information about the condition, including the source of the condition. The 8 means an error, and the 7 means the source of the error is Win32. The last four digits are the Error Code itself. And, you might have just realized that 0BC2 is the last four digits in 0x00000BC2. Because they are the same condition. And those first four 0’s in the Win32 version? Just unused digits that Win32 fills with 0’s rather than the F’s that HRESULT uses, because REASONS. 😉
So, ultimately 3010/0BC2 is the 16 bit number we really care about, and in 32-bit Win32 it’s represented as 0x00000BC2 with the unused digits taking a 0, and in 64-bit HRESULT as 0xFFFFFFFF80070BC2 with Fs filling the unused digits, and 8007 being information that we aren’t really concerned about. But all the same pertinent information: 0BC2.
But here’s where things get interesting. Those hex numbers we have been looking at are unsigned hex, but Windows uses a signed integer data type in the tools we use for automation. So, when the installer uses Win32 Error Codes, it hands 0x00000BC2 back to your automation, which converts it to a signed decimal (technically called “casting” to a specific data type), and 0x00000BC2 as a signed decimal is 3010. 0x0BC2 is 3010 as well, thanks to those 0’s for padding. It’s a bit like 0010 and 10 being the same in decimal, we just don’t usually bother filling out the unused digits. But HRESULT is messier. Because HRESULT fills unused digits with F, and an F in the first digit represents a negative number when casting to signed integer, and the rest of those Fs make for a BIG number, and our computers use what’s called Two’s Complement at the binary level to handle this which is a Dragon Lair not a Rabbit Hole, so we won’t go down there. Suffice it to say that 0xFFFFFFFF80070BC2 = -2147021886 as a signed integer. And THAT is what you may see as an Exit Code when installing some Autodesk software. And to make matters even worse, while a Win32 0x00000000 gives us a nice 0 in decimal, the HRESULT version is 0xFFFFFFFF00000000, because remember that HRESULT pads out the unused digits with Fs. And 0xFFFFFFFF00000000 cast to a signed decimal is… -4294967296!
(yes, I think the fact that the first two digits are 42 is the universe trying to tell us something)
In any case, this use of HRESULT values in some installers is what causes these gigantic negative numbers to sometimes be returned.
Now, I think the root issue is that sometimes Autodesk has former COM programmers doing stuff they aren’t familiar with like installers, and no guidelines or best practices when it comes to installers, so while someone familiar with Windows Installs is going to choose Win32 for their errors instead HRESULT is sometimes chosen, and so in some cases we get -2147021886 instead of 3010, or -4294967296 instead of 0.
Note that this is all conjecture, and if anyone from Autodesk wants to chime in with an explanation here I would love to learn. But in any case, it’s the reality we have to deal with, and now you have a better understanding of what is going on. But that’s not very useful without also being able to use the knowledge.
To start with, when seeing these negative numbers, you can start with a good Decimal to Hex converter, where you can put in something like -2147021886 and get back 80070BC2 (without the 0x, that’s not part of the actual number, just a shorthand in Windows to let you know it’s hex). From there, you know it’s only the last four digits that matter, so 0BC2 into a Hex to Decimal converter will give you 3010, and you are off to the races looking up your error message with Microsoft.
But, if you are a PowerShell nerd, you can skip all that and the manual lookup. This bit of code takes your nasty negative number, takes it back to hex to pull off the F filler, then casts the resulting hex number to the Win32Error data type, and returns the actual human readable message! And of course it also works for Win32 values like 3010 as well.
$errCode = -2147021886
[int]$intCode = $errCode -band 0xFFFF
[string]$message = ([ComponentModel.Win32Exception]$intCode).message
It’s been a long, long slog, both this post and my own learning curve over the last few weeks. It’s worth noting here how this all came about, and why it became necessary. You see, in Px Tools before 3.4.0, I was naive/ignorant and I was hard coding just the Win32 “SuccessCodes” (0, 1641, 3010) and was only logging the codes, without any meaningful message. In Px Tools 3.4.0 I started doing a lookup hashtable of some exit codes, but that also was hard coded and limited to what I knew about and could thus add. Px Tools 4.0 moves the success codes into an XML file so it’s easier to add if Autodesk starts using something I haven’t seen yet. And Px Tools 4.0 will expand on the code above so any error message Windows knows about will be logged in human comprehendible form. The combination should make for a much better Px Tools!
Hopefully this was helpful, or at least interesting, for a few people. Now back to work on Px Tools 4.0!