Windows: add script to build a .msi package

Also add a test, though there's no build rule to
wrap it yet.

The generated installer:
- is a standard .msi installer that users are
  familiar with
- installs Bazel at a short and safe path (no
  space in path)
- updates the PATH envvar
- handles uninstall, upgrading, parallel installs:
  backwards-compatible versions can be upgraded
  (but not downgraded), while incompatible
  versions may be installed side by side
  (see `guids.txt` comments)

Example use:

```
c:\src\bazel>powershell scripts\packages\msi\make_msi.ps1 ..\bazel-releases\0.26.1\bazel-0.26.1-windows-x86_64.exe
(...)
INFO[make_msi_lib.ps1 11:41:33] Creating wixobj
INFO[make_msi_lib.ps1 11:41:33] Creating msi
INFO[make_msi_lib.ps1 11:41:42] Done: ..\bazel-releases\0.26.1\bazel-0.26.1-windows-x86_64.msi

c:\src\bazel>start ..\bazel-releases\0.26.1\bazel-0.26.1-windows-x86_64.msi
```

TODO(@laszlocsomor) before we can use this:
- figure out if we should bundle vc_redist with
  the Bazel installer
- figure out the UpgradeCode for such a combined
  bundle

Closes #8579.

PiperOrigin-RevId: 254744890
diff --git a/scripts/packages/msi/README.md b/scripts/packages/msi/README.md
new file mode 100644
index 0000000..89cbb5e
--- /dev/null
+++ b/scripts/packages/msi/README.md
@@ -0,0 +1,13 @@
+# Windows Installer Package (.msi) generator for Bazel
+
+Usage:
+
+```
+powershell scripts\packages\msi\make_msi.ps1 bazel-0.28.2rc5-windows-x86_64.exe
+```
+
+Testing:
+
+```
+powershell scripts\packages\msi\make_msi_test.ps1
+```
diff --git a/scripts/packages/msi/banner.bmp b/scripts/packages/msi/banner.bmp
new file mode 100644
index 0000000..a81f49f
--- /dev/null
+++ b/scripts/packages/msi/banner.bmp
Binary files differ
diff --git a/scripts/packages/msi/bazelmsi.wxs b/scripts/packages/msi/bazelmsi.wxs
new file mode 100644
index 0000000..b372c92
--- /dev/null
+++ b/scripts/packages/msi/bazelmsi.wxs
@@ -0,0 +1,135 @@
+<?xml version='1.0' encoding='windows-1252'?>
+<!--
+  Copyright 2019 The Bazel Authors. All rights reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+  <!-- Product/@Id is auto-generated so every Bazel version (including release
+       candidates) has a unique product GUID. This is fine, all upgrades are
+       "Major upgrades" (see
+       https://www.firegiant.com/wix/tutorial/upgrades-and-modularization/).
+
+       IMPORTANT: to see the Product GUID of an .msi file:
+         1. install the Windows SDK
+         2. find the Orca installer underneath (Orca-x86_en-us.msi) and run it
+         3. open the .msi file with Orca
+         4. open the "Property" table: the "ProductCode" property is the
+            generated Product/@Id GUID
+  -->
+
+  <!-- IMPORTANT: Product/@UpgradeCode must be the same for backwards-compatible
+       versions, but a new code is needed when an incompatible release comes.
+
+       Versions with the same UpgradeCode are upgraded: higher versions replace
+       lower versions (e.g. version 1.5.2 replaces version 1.0.3). Downgrading
+       is not permitted.
+       Versions with different UpgradeCodes can co-exist (e.g version 2.0.4rc5
+       can co-exist with version 1.2.0).
+  -->
+  <Product
+      Name="Bazel $(var.RELEASE_NAME)"
+      Codepage="1252"
+      Id="*"
+      Language="1033"
+      Manufacturer="The Bazel Authors (https://bazel.build)"
+      UpgradeCode="$(var.UPGRADE_GUID)"
+      Version="$(var.VERSION)">
+    <!-- Package/@Id is auto-generated. This is standard practice with WiX,
+         although it makes the compilation step ("candle.exe")
+         non-deterministic.
+         Even if we used a fixed GUID though, the linking step ("light.exe")
+         would still be non-deterministic.
+    -->
+    <Package
+        AdminImage="no"
+        Compressed="yes"
+        Description="Bazel $(var.RELEASE_NAME) installer"
+        Id="*"
+        InstallPrivileges="limited"
+        InstallScope="perUser"
+        InstallerVersion="200"
+        Languages="1033"
+        Manufacturer="The Bazel Authors (https://bazel.build)"
+        Platform="x64"
+        SummaryCodepage="1252" />
+    <Media Id="1" Cabinet="bazel.cab" EmbedCab="yes" />
+
+    <Directory Id="TARGETDIR" Name="SourceDir">
+      <Directory Id="InstallDirRoot" Name="bazel">
+        <Directory Id="INSTALLDIR" Name="$(var.RELEASE_NAME)">
+          <!-- RANDOM_GUID_1: can't use "*" because we'd get LGHT0231 from
+               light.exe -->
+          <Component
+              Guid="$(var.RANDOM_GUID_1)"
+              Id="MainExecutable"
+              Win64="yes">
+            <File
+                Name="bazel.exe"
+                Id="BazelExe"
+                KeyPath="yes"
+                Source="$(var.BAZEL_EXE)"
+                Vital="yes" />
+            <Environment
+                Name="PATH"
+                Action="set"
+                Id="UpdatePathEnv"
+                Part="first"
+                System="no"
+                Value="[INSTALLDIR]" />
+          </Component>
+          <!-- RANDOM_GUID_2: can't use "*" because we'd get LGHT0230 from
+               light.exe -->
+          <Component
+              Guid="$(var.RANDOM_GUID_2)"
+              Id="InstallDirRoot">
+            <RemoveFolder Id="InstallDirRoot" On="uninstall" />
+          </Component>
+        </Directory>
+      </Directory>
+    </Directory>
+
+    <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
+    <UIRef Id="WixUI_InstallDir" />
+
+    <Icon Id="BazelIcon" SourceFile="$(var.ICON)" />
+    <Property Id="ARPPRODUCTICON" Value="BazelIcon" />
+
+    <Condition
+        Message="Bazel requires 64 bit Windows 7 or newer (64 bit Windows 10 recommended)">
+      <![CDATA[VersionNT64 >= 601]]>
+    </Condition>
+
+    <Feature
+        Absent="disallow"
+        AllowAdvertise="no"
+        ConfigurableDirectory="INSTALLDIR"
+        Display="expand"
+        Id="Complete"
+        Level="1">
+      <ComponentRef Id="InstallDirRoot" />
+      <ComponentRef Id="MainExecutable" />
+    </Feature>
+
+    <!-- AllowSameVersionUpgrades is enabled, so we can install RC versions,
+         e.g. install 0.28.0rc3 then 0.28.0. Unfortunately we can't easily
+         prevent upgrading a release version to its RC version, or upgrading an
+         RC with an older RC (because their VERSION and UPGRADE_GUID are both
+         the same) but that's an acceptable flaw. -->
+    <MajorUpgrade
+        AllowDowngrades="no"
+        IgnoreRemoveFailure="no"
+        AllowSameVersionUpgrades="yes"
+        DowngradeErrorMessage="A newer and backwards-compatible version is already installed" />
+  </Product>
+</Wix>
diff --git a/scripts/packages/msi/dialog.bmp b/scripts/packages/msi/dialog.bmp
new file mode 100644
index 0000000..d4b4f35
--- /dev/null
+++ b/scripts/packages/msi/dialog.bmp
Binary files differ
diff --git a/scripts/packages/msi/guids.txt b/scripts/packages/msi/guids.txt
new file mode 100644
index 0000000..7bd47fe
--- /dev/null
+++ b/scripts/packages/msi/guids.txt
@@ -0,0 +1,45 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Product/@UpgradeCode GUIDs for Bazel.
+# Each UpgradeCode represents an upgradeable group.
+# Products with the same upgrade code can be upgraded (higher version replaces
+# lower version) but not downgraded.
+# Products with different upgrade codes can co-exist.
+
+# IMPORTANT: Never ever modify GUIDs that were once added here and used for
+# building .msi files, as that would break the upgradeability of compatible
+# versions.
+
+# IMPORTANT: If building the .msi yields an error like this:
+#
+#   UpgradeGuid for 0.28.0 not found in scripts\packages\msi\guids.txt
+#
+# ...then you have to add more GUIDs to this file.
+#
+# Each line is a key-value pair, separated by space.
+# Key: version prefix to match.
+#      E.g. the "0.29" prefix matches version "0.29.0" and "0.29.2rc11", and
+#           the "1" prefix matches every "1.*" version and their RCs
+# Value: UpgradeCode GUID to use.
+
+0.26 6D62E4DF-1533-4862-8CB7-CE858E004CCC
+0.27 4FEC9BA1-4CDD-4545-BA6B-8AEBC6DBC1D3
+0.28 B7864F52-FA13-402E-8334-5CF8FE168728
+0.29 BE8D4238-4EF4-4927-A771-8A0BB4BB4283
+1 68CCBF54-5048-4A1E-A425-A632FBB6D2D8
+2 E178166E-B73E-4585-AAEA-7E95769F2CB9
+3 756D74A1-0FFD-487C-AEA1-18BAD5E60E32
+4 F94D9042-6CD7-4918-BD2A-C75A9467BE89
+5 235EEC7E-9958-46CA-B014-B52604654D9E
diff --git a/scripts/packages/msi/license.rtf b/scripts/packages/msi/license.rtf
new file mode 100644
index 0000000..c57923c
--- /dev/null
+++ b/scripts/packages/msi/license.rtf
@@ -0,0 +1,12 @@
+{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Tahoma;}}
+{\colortbl ;\red0\green0\blue255;}
+{\*\generator Riched20 10.0.16299}\viewkind4\uc1
+\pard\sa200\sl276\slmult1\f0\fs16\lang9 Bazel is bundled with software licensed under the GPLv2 with Classpath exception. You can find the sources next to the installer on our release page:\par
+  https://github.com/bazelbuild/bazel/releases\par
+---\par
+Copyright 2019 The Bazel Authors. All rights reserved.\par
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:\par
+   http://www.apache.org/licenses/LICENSE-2.0\par
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.\par
+}
+
diff --git a/scripts/packages/msi/make_msi.ps1 b/scripts/packages/msi/make_msi.ps1
new file mode 100644
index 0000000..1a88615
--- /dev/null
+++ b/scripts/packages/msi/make_msi.ps1
@@ -0,0 +1,61 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This module creates a Windows Installer (.msi) package for a Bazel release.
+#
+# Example usage:
+#   C:\src\bazel> powershell -NonInteractive scripts\packages\msi\make_msi.ps1 outputs\bazel-0.28.0rc5-windows-x86_64.exe
+#
+# Mandatory arguments:
+#   -BazelExe <path>: path to the Bazel binary to package. Its name must
+#                     conform to "bazel-<version>-windows-x86_64.exe"
+#
+# Optional arguments:
+#    -OutMsi: path of the output file (default: same path as -BazelExe, with
+#             ".msi" extension)
+#    -WorkDir: directory for temp files (default: ".\_make_msi_tmp_")
+
+# Command line arguments.
+param (
+    [Parameter(Mandatory = $true, Position = 0)]
+    [ValidatePattern("bazel-[0-9]+\.[0-9]+\.[0-9]+(rc[1-9]|rc[1-9][0-9]+)?-windows-x86_64.exe$")]
+    [string]$BazelExe,
+
+    [string]$WorkDir,
+    [string]$OutMsi
+)
+
+# Exit immediately when a Cmdlet fails.
+$ErrorActionPreference = 'Stop'
+
+Import-Module .\scripts\packages\msi\make_msi_lib.ps1
+
+# Ensure all paths are Windows-style.
+$BazelExe = "$(Replace-Slashes($BazelExe))"
+
+if ($WorkDir) {
+    $WorkDir = "$(Replace-Slashes($WorkDir))"
+}
+else {
+    $WorkDir = '.\_make_msi_tmp_'
+}
+
+if ($OutMsi) {
+    $OutMsi = "$(Replace-Slashes($OutMsi))"
+}
+else {
+    $OutMsi = $BazelExe.Substring(0, $BazelExe.Length - 3) + 'msi'
+}
+
+Make-Msi -BazelExe $BazelExe -WorkDir $WorkDir -OutMsi $OutMsi
diff --git a/scripts/packages/msi/make_msi_lib.ps1 b/scripts/packages/msi/make_msi_lib.ps1
new file mode 100644
index 0000000..144e14a
--- /dev/null
+++ b/scripts/packages/msi/make_msi_lib.ps1
@@ -0,0 +1,205 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set path constants. These are relative to the Bazel source tree root.
+$Icon = 'site\images\favicon.ico'
+$ArtworkBig = 'scripts\packages\msi\dialog.bmp'
+$ArtworkSmall = 'scripts\packages\msi\banner.bmp'
+$Licence = 'scripts\packages\msi\license.rtf'
+$Guids = 'scripts\packages\msi\guids.txt'
+$Wxs = 'scripts\packages\msi\bazelmsi.wxs'
+
+# Logs a message to stdout.
+function Log-Info {
+    param (
+      [string]$msg
+    )
+    $basename = $PSCommandPath.Substring($PSCommandPath.LastIndexOfAny('/\') + 1)
+    $time = (Get-Date).ToString(.HH:mm:ss.)
+    Write-Host 'INFO[$basename $time] $msg'
+}
+
+function Replace-Slashes {
+    param (
+      [string]$s
+    )
+    return $s.Replace('/', '\')
+}
+
+# Computes the release name from the 'BazelExe'.
+# 'BazelExe' must already have been validated to match this regex.
+# 'ReleaseName' is like "0.28.0rc5"
+# 'Version' is the SemVer part of 'ReleaseName', e.g. "0.28.0"
+function Compute-RelaseNameAndVersion {
+    param (
+      [Parameter(Mandatory=$true)][string]$BazelExe
+    )
+
+    $rel = [regex]::Match($BazelExe, "bazel-([^-]+)-windows.*\.exe$").captures.groups[1].value
+    $ver = [regex]::Match($rel, "^([0-9]+\.[0-9]+\.[0-9]+)(rc[0-9]+)?$").captures.groups[1].value
+    return $rel, $ver
+}
+
+# Generates a new GUID, prints it as uppercase.
+# The WiX Toolkit expects uppercase GUIDs in the .wxs file.
+function Generate-Guid {
+    return [guid]::NewGuid().ToString().ToUpper()
+}
+
+# Returns the UpgradeGuid for this release.
+function Get-UpgradeGuid {
+    param (
+      [Parameter(Mandatory=$true)][string]$release_name
+    )
+
+    $d = @{}
+    $result = $null
+    foreach ($line in Get-Content -Path $Guids) {
+        $line = '$line'.Split('#')[0]
+        if ($line) {
+            $k, $v = $line.Split(' ', 2)
+            # Ensure all version prefixes in the file are unique.
+            if ($d.ContainsKey($k)) {
+                throw 'Duplicate relase prefix $k in $Guids'
+            }
+            else {
+                $d[$k] = $null
+            }
+            # Ensure all GUIDs in the file are unique. We can use the same
+            # hashtable because the value domains are distinct.
+            if ($d.ContainsKey($v)) {
+                throw 'Duplicate GUID $v in $Guids'
+            }
+            else {
+                $d[$v] = $null
+            }
+            if (! $result -and $release_name.StartsWith($k)) {
+                $result = '$v'
+                # Do not return yet, so we check that all GUIDs are unique.
+            }
+        }
+    }
+    if ($result) {
+        return $result
+    }
+    else {
+        throw "UpgradeGuid for $release_name not found in $Guids"
+    }
+}
+
+# Returns the Bazel version (as a SemVer string) from the release name.
+function Get-BazelVersion {
+    if ($ReleaseName -match "rc[0-9]+$") {
+        return $ReleaseName.Substring(0, $ReleaseName.LastIndexOf('rc'))
+    }
+    else {
+        return $ReleaseName
+    }
+}
+
+# Downloads and extracts the WiX Toolkit.
+# Returns the path where the tools are (e.g. "candle.exe").
+function Download-Wix {
+    # Use TLS1.2 for HTTPS (fixes an issue where later steps can't connect to github.com).
+    # See https://github.com/bazelbuild/continuous-integration/blob/master/buildkite/setup-windows.ps1
+    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+    $wix_dir = $(New-Item -Path "${WorkDir}\_wix" -ItemType Directory -Force).FullName
+    $zip_file = "$wix_dir\wix311.zip"
+    (New-Object Net.WebClient).DownloadFile(
+        'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip',
+        $zip_file)
+
+    $actual_sha = (Get-FileHash -Path $zip_file -Algorithm SHA256).Hash.ToString()
+    $expected_sha = '37F0A533B0978A454EFB5DC3BD3598BECF9660AAF4287E55BF68CA6B527D051D'
+    if ($actual_sha -ne $expected_sha) {
+        throw "Bad checksum: wix311-binaries.zip SHA256 is $actual_sha, expected $expected_sha"
+    }
+
+    Expand-Archive -Path $zip_file -DestinationPath $wix_dir -Force
+
+    return $wix_dir
+}
+
+# Creates the .wixobj file using candle.exe (the "compiler").
+function Run-Candle {
+    param (
+      [Parameter(Mandatory=$true)][string]$wix_root
+    )
+
+    $out = "${WorkDir}\bazelmsi.wixobj"
+
+    $output = & "${wix_root}\candle.exe" `
+        -nologo `
+        -arch x64 `
+        "-dBAZEL_EXE=$BazelExe" `
+        "-dICON=$Icon" `
+        "-dUPGRADE_GUID=$(Get-UpgradeGuid $ReleaseName)" `
+        "-dRELEASE_NAME=$ReleaseName" `
+        "-dVERSION=$(Get-BazelVersion)" `
+        "-dRANDOM_GUID_1=$(Generate-Guid)" `
+        "-dRANDOM_GUID_2=$(Generate-Guid)" `
+        -o $out `
+        $Wxs
+    if (! $?) {
+        throw $output
+    }
+    return $out
+}
+
+# Creates the .msi file using light.exe (the "linker").
+function Run-Light {
+    param (
+        [Parameter(Mandatory=$true)][string]$wix_root,
+        [Parameter(Mandatory=$true)][string]$wixobj
+    )
+
+    $output = & "${wix_root}\light.exe" `
+        -nologo `
+        -ext WixUIExtension `
+        '-cultures:en-us' `
+        "-dWixUILicenseRtf=$Licence" `
+        "-dWixUIDialogBmp=$ArtworkBig" `
+        "-dWixUIBannerBmp=$ArtworkSmall" `
+        -o $OutMsi `
+        $wixobj
+    if (! $?) {
+        throw $output
+    }
+}
+
+function Make-Msi {
+    param (
+        [Parameter(Mandatory=$true)]
+        [ValidatePattern("bazel-[0-9]+\.[0-9]+\.[0-9]+(rc[1-9]|rc[1-9][0-9]+)?-windows-x86_64.exe$")]
+        [string]
+        $BazelExe,
+
+        [Parameter(Mandatory=$true)][string]$WorkDir,
+        [Parameter(Mandatory=$true)][string]$OutMsi
+    )
+
+    $ReleaseName, $Version = Compute-RelaseNameAndVersion $BazelExe
+
+    Log-Info 'Downloading WiX Toolkit'
+    $wix_root = Download-Wix
+
+    Log-Info 'Creating wixobj'
+    $wixobj = Run-Candle -wix_root $wix_root
+
+    Log-Info 'Creating msi'
+    Run-Light -wix_root $wix_root -wixobj $wixobj
+
+    Log-Info "Done: $OutMsi"
+}
diff --git a/scripts/packages/msi/make_msi_test.ps1 b/scripts/packages/msi/make_msi_test.ps1
new file mode 100644
index 0000000..9ac45f9
--- /dev/null
+++ b/scripts/packages/msi/make_msi_test.ps1
@@ -0,0 +1,46 @@
+Import-Module .\scripts\packages\msi\make_msi_lib.ps1
+
+function Assert-Equal {
+    param (
+        [Parameter(Position = 0)][string]$x,
+        [Parameter(Position = 1)][string]$y
+    )
+    if ($x -ne $y) {
+        throw "Expected equality of ($x) and ($y)"
+    }
+}
+
+function Assert-NotEqual {
+    param (
+        [Parameter(Position = 0)][string]$x,
+        [Parameter(Position = 1)][string]$y
+    )
+    if ($x -eq $y) {
+        throw "Expected non-equality of ($x) and ($y)"
+    }
+}
+
+# Tests for Replace-Slashes
+Assert-Equal $(Replace-Slashes "") ""
+Assert-Equal $(Replace-Slashes 'foo') 'foo'
+Assert-Equal $(Replace-Slashes 'foo/bar/baz\qux') 'foo\bar\baz\qux'
+
+# Test for Compute-RelaseNameAndVersion
+$rel, $ver = Compute-RelaseNameAndVersion 'bazel-1.2.3-windows-x86_64.exe'
+Assert-Equal $rel '1.2.3'
+Assert-Equal $ver '1.2.3'
+$rel, $ver = Compute-RelaseNameAndVersion 'bazel-0.99.5rc3-windows-x86_64.exe'
+Assert-Equal $rel '0.99.5rc3'
+Assert-Equal $ver '0.99.5'
+
+# Test for Get-UpgradeGuid
+Assert-Equal $(Get-UpgradeGuid 0.28.0) 'B7864F52-FA13-402E-8334-5CF8FE168728'
+Assert-Equal $(Get-UpgradeGuid 1.0.0) $(Get-UpgradeGuid 1.0.5)
+Assert-Equal $(Get-UpgradeGuid 1.0.0) $(Get-UpgradeGuid 1.0.5rc2)
+Assert-Equal $(Get-UpgradeGuid 1.0.0) $(Get-UpgradeGuid 1.3.3)
+Assert-Equal $(Get-UpgradeGuid 2.1.0) $(Get-UpgradeGuid 2.0.3rc1)
+Assert-NotEqual $(Get-UpgradeGuid 0.28.0) $(Get-UpgradeGuid 0.29.0)
+Assert-NotEqual $(Get-UpgradeGuid 1.5.0) $(Get-UpgradeGuid 2.0.3rc1)
+
+
+Write-Host 'PASSED'