Skip to content
OlegBoulanov edited this page Sep 9, 2021 · 28 revisions

Motivation and Operational model

So we have a park of hundreds of Windows computers, operating 24x7x365, hosting important user services, provided by our great software. Redundant, geographically distributed, etc. We designed the whole system in a way allowing different versions of the software to run on different hosts simultaneously and flawlessly.

Here is the question - how do we perform upgrades with minimum human intervention? Obviously, hosts should be able check for updates during periods of inactivity (maybe self initiated), and upgrade themselves as needed. Doing that on regular scheduled reboot (once a week, or a day) seems to be a natural first step.

s3i allows us to configure each host only once, marking its group identity with configuration file URI. After that, at each s3i service restart (computer reboot), it's software configuration will be synchronized with remote config file.

Software amy be produced by a separate CI/CD system and uploaded to version specific folders and/or AWS S3 buckets automatically. Upgarding a group of hosts to newer version would require changing a single line in group configuration file. s3i, ran manually or automatically, would compare already installed product semantic version and properties with requested one and take proper actions, like install, reinstall, or uninstall. A newer version would be installed over existing one in one pass, reinstall (to downgrade or change of properties) would require uninstall, followed by install.

s3i allows to install/upgrade/downgrade software packaged for Microsoft Installer, downloading configuration files and installers from local or remote location (http/file Uri not requiring user credentials supported, or AWS S3 with profile credentials)

Setup Example

           command line to run on host          S3/http
/------\                                    /--------------\
| Host | -- s3i http://../products.ini--->  | products.ini |   host (group of hosts) 
\------/                                    \--------------/     configuration file
  /|\                                             | |
   |            S3/GitHub/http                    | |  links to products
   |              /-------\                       | |   to be downloaded
   \------------  | *.msi |---\  <----------------/ |   and installed
  download        \-------/   |  <------------------/
 and install          \-------/  
                        /|\
                         |        /--------------\
              upload     \--------| CI/CD system | 
     to version-named subfolders  \--------------/

s3i reads configuration files, specified in command line, downloads and caches product installers and properties in the staging area, and performs required uninstalls, installations, upgrades, or downgrades by invoking Windows msiexec.exe with proper arguments.

AWS S3 prerequisites

By default, s3i uses current user's [default] AWS profile. Profile name can be changed using --profile command line option. Profile credentials should allow read access to all necessary S3 buckets and prefixes.

Product Installer Reqirements

Product Installer (.msi) is expected to be able to run in unattended mode, being configured with use of public properties, passed as msiexec command line arguments (s3i_setup project is a simple example of such product)

Configuration file format

Configuration file contains one or several product specifications:

  • Product name, for example, SomethingUseless
  • Product installer URL, like https://deployment.s3.amazonaws.com/useless.product/develop/1.2.3.4-beta2+test/installer.msi
  • Optional set of product public properties (key/value pairs) to be passed to unattended MSI installation

Here is an example of products.ini file:

[$define$]
$deploymentBase$ = https://deployment.s3.amazonaws.com
$ssm_param$ = ${ssm:/local/params/something}
$env_var$ = ${env:VARNAME}

; List of product name = installer URL
[$products$]
SomethingUseless = $deploymentBase$/useless.product/develop/1.2.3-beta2+test/installer.msi
EvenMoreUseless = $deploymentBase$/other.product/release/3.7.5/setup.msi

; Sections specify optional product properties
[SomethingUseless]
ImportantProperty = just an example
NotSoImportant = but we pass it anyway, just for fun

[EvenMoreUseless]
HelloWorld = You welcome!

$define$ section keys get replaced in all entry values

${ssm:} and ${env:} entries get replaces with AWS Simple Systems Management Parameter values, and environment variables. These entries get expanded recursively, as other variables, and default values can be specified for missing SSM or ENV variables.

Variables

Variable reference is provided as ${type:name} or ${type:name?default}.

Consider these examples:

Assert.AreEqual(3, vars.Count);
Assert.AreEqual("== 1+222", vars["three"]);	            
Assert.AreEqual("1", vars["one"]);
Assert.AreEqual("== 1+222", vars.Expand("${three}"));	            
Assert.AreEqual("222", vars["two"]);
Assert.AreEqual("== 1+222 ==", vars["three"]);
Assert.AreEqual("== 1+222 ==", vars.Expand("${nope?${three}}"));
Assert.AreEqual("== 1+222 ==", vars.Expand("${three?nope}"));
Assert.AreEqual("== 1+222 ==", vars.Expand("${three?${two}}"));
Assert.AreEqual("== 1+222 ==", vars.Expand("${three?${nope}}"));

List of variables can also be supplied in a separate, yaml-like format file, using -z (--vars) <path.yaml> command line option

Running from Command Line

Printing s3i help info

PS C:\Users\rdp> s3i --help
s3i: msi package batch installer v1.0.386
 Usage:
  s3i [<option> ...] <products> ...
 Options:
  -h, --help                        Print this help info [True]
  -p, --profile <profile-name>      AWS user profile name [default]
  -r, --region <region-name>        AWS default region name [us-east-1]
  -x, --prefixes <list>             List of comma separated allowed version prefixes [v,V,ver,Ver]
  -e, --envvar <var-name>           Environment variable name (default command line) [s3i_args]
  -z, --vars <path>                 Path to variables file (yaml map format) []
  -s, --stage <path>                Path to staging folder [C:\Users\rdp\AppData\Local\Temp\2\s3i]
  -c, --clean                       Clear staging folder at startup [False]
  -m, --msiexec <path>              MsiExec command [msiexec.exe]
  -a, --msiargs <args>              MsiExec extra args [/passive]
  -t, --timeout <timespan>          Installation timeout [00:03:00]
  -d, --dryrun                      Dry run [False]
  -v, --verbose                     Print full log info [False]

PS C:\Users\rdp>

Dry run (running with no actual installation)

C:\Users\current-user>s3i https://install.company.com.s3.amazonaws.com/Test/Group/products.ini --verbose --dryrun
Products [2]:
  SomethingUseless: https://deployment.s3.amazonaws.com/useless.product/develop/1.2.3-beta2+test/installer.msii
      => C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\useless.product/develop/1.2.3-beta2+test/installer.msi
    ImportantProperty = just an example
    NotSoImportant = but we pass it anyway, just for fun
  EvenMoreUseless = https://deployment.s3.amazonaws.com/other.product/release/3.7.5/setup.msi
      => C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\other.product\release\3.7.5\setup.msi
    HelloWorld = You welcome!
Install [2]:
...
(DryRun) Download ...
(DryRun) Install https://deployment.s3.amazonaws.com/useless.product/develop/1.2.3-beta2+test/installer.msi
(DryRun) Install https://deployment.s3.amazonaws.com/other.product/release/3.7.5/setup.msi
Save C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\useless.product/develop/1.2.3-beta2+test/installer.json
Save C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\other.product\release\3.7.5\setup.json

Similar results can be achieved by setting msiexec command to echo msiexec:

C:\Users\current-user>s3i https://install.company.com.s3.amazonaws.com/Test/Group/products.ini --verbose --msiexec "echo msiexec"
Products [2]:
...
Install [2]:
...
(Execute) Download ...
(Execute) Install https://deployment.s3.amazonaws.com/useless.product/develop/1.2.3-beta2+test/installer.msi
msiexec /i C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\useless.product/develop/1.2.3-beta2+test/installer.msi /passive
...

Installing products from configuration file on AWS S3

C:\Users\current-user>s3i https://install.company.com.s3.amazonaws.com/Test/Group/products.ini --verbose
Products [2]:
...
Install [2]:
...
(Execute) Download ...
(Execute) Install https://deployment.s3.amazonaws.com/useless.product/develop/1.2.3-beta2+test/installer.msii
(Execute) Install https://deployment.s3.amazonaws.com/other.product/release/3.7.5/setup.msi
Save C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\useless.product/develop/1.2.3-beta2+test/installer.json
Save C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\other.product\release\3.7.5\setup.json

Upgrading one product

After changing products.ini file: develop/1.2.3-beta2+test release/1.2.4, run the same s3i command again:

C:\Users\current-user>s3i https://install.company.com.s3.amazonaws.com/Test/Group/products.ini --verbose
Products [2]:
...
Install [1]:
...
(Execute) Download ...
(Execute) Install https://deployment.s3.amazonaws.com/useless.product/release/1.2.4/installer.msi
Save C:\Users\current-user\AppData\Local\Temp\s3i\deployment.s3.amazonaws.com\useless.product/release/1/2/4/installer.json

Downgrading or change of product properties

Can be done the same way, as upgrading, but the version in the URL should be earlier than the one already installed, and newer (or same) version of the product will be uninstalled first, and then the earlier version will be installed back.

Uninstalling product:

To uninstall a product, delete (or comment out with semicolon) product name = URL entry from [$products$] section of the config file, and run s3i again.

Simple automation

Default command line arguments

Default command line arguments can be set in s3i_args environment variable:

set s3i_args= https://install.company.s3.amazonaws.com/Test/Group/products.ini --verbose

Running s3i with no arguments (or at s3i service startup) will result in using the arguments specified in this variable. To see help info, run it with --help key:

s3i --help

To be available to s3i service, system environment variable with the same name should be set, otherwise service would do essentially nothing.

s3i Windows Service

s3i service runs s3i command line tool with arguments specified in s3i_args environment variable.

CMDLINEARGS s3i.msi installer property sets s3i service parameters at service installation time, for example:

msiexec /i s3i.msi CMDLINEARGS="-d 00:00:30 -t 00:10:00 -l Warning"

Service Parameters:

 -p, --path <path-to-exe>       Process executable path [installed service folder\s3i.exe]
 -d, --delay <timespan>         Process start delay [00:00:08]
 -t, --timeout <timespan>       Process timeout [00:05:00]
 -l, --level <log-level>        Logging level [Information]