#Requires -Version 5.0
Set-StrictMode -Version Latest

<#
    .SYNOPSIS
    Updates openHAB to the latest version.
    .DESCRIPTION
    The Update-openHAB function performs the necessary tasks to update openHAB.
    .PARAMETER OHDirectory
    The directory where openHAB is installed (default: current directory).
    .PARAMETER OHVersion
    The version to upgrade to.
    .PARAMETER Snapshot
    DEPRECATED - Upgrade to a snapshot version ($true) or a release version ($false) (default: $false)
    DEPRECATED - Please specify "-snapshot" in the OHVersion instead (ex: "3.0.0-SNAPSHOT")
    .PARAMETER AutoConfirm
    Automatically confirm update (used for headless mode)
    .EXAMPLE
    Update the openHAB distribution in the current directory to the current stable version
    Update-openHAB
    .EXAMPLE
    Update the openHAB distribution in the C:\oh-snapshot directory to the next snapshot version
    Update-openHAB -OHDirectory C:\oh-snapshot -OHVersion 3.0.0-SNAPSHOT
#>

###########################################################################
# NOTE: changes in this script should be reflected in update.sh as well
###########################################################################

Function Update-openHAB() {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $True)]
        [string]$OHDirectory = ".",
        [Parameter(ValueFromPipeline = $True)]
        [string]$OHVersion,
        [Parameter(ValueFromPipeline = $True)]
        [boolean]$Snapshot = $false,
        [Parameter(ValueFromPipeline = $True)]
        [boolean]$AutoConfirm = $false,
        [Parameter(ValueFromPipeline = $True)]
        [boolean]$SkipNew = $false,          # sssh - secret switch ;)
        [Parameter(ValueFromPipeline = $True)]
        [boolean]$KeepUpdateScript = $false  # sssh - secret switch ;)
    )

    # Downloads the URL into a file showing a progress meter.  Any error will be thrown to the caller

    function DownloadFiles() {
        param(    
            [Parameter(Mandatory = $True)]
            [string] $URL, 
            [Parameter(Mandatory = $True)]
            [string] $OutputFile
        ) 
    
        # Create the request
        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
        $uri = New-Object "System.Uri" "$URL"
        $request = [System.Net.HttpWebRequest]::Create($uri)
        $request.set_Timeout(15000)

        #Get the response (along with the total size)
        $response = $request.GetResponse()
        $totalLength = [System.Math]::Floor($response.get_ContentLength()/1024)

        try {
            # Gets the response stream and setup the buffer
            $responseStream = $response.GetResponseStream()
            $targetStream = New-Object -TypeName System.IO.FileStream -ArgumentList $Outputfile, Create
            $buffer = new-object byte[] 10KB

            # Save console settings
            $startTop = [System.Console]::CursorTop
            $startVisibility = [System.Console]::CursorVisible
            $startColor = [System.Console]::ForegroundColor

            # Process each chunk into the output file (updating the progress meter along the way)
            try {
                [System.Console]::CursorVisible = $False
                [System.Console]::ForegroundColor = "Blue"
                $count = $responseStream.Read($buffer,0,$buffer.length)
                $downloadedBytes = $count
                while ($count -gt 0)
                {
                    $bytes = [System.Math]::Floor($downloadedBytes/1024)
                    $perc = [System.Math]::Floor(($bytes / $totalLength) * 100)
                    [System.Console]::CursorLeft = 0
                    [System.Console]::CursorTop = $startTop
                    [System.Console]::Write("Downloaded {0}K of {1}K [{2}%]", $bytes, $totalLength, $perc)
                    $targetStream.Write($buffer, 0, $count)
                    $count = $responseStream.Read($buffer,0,$buffer.length)
                    $downloadedBytes = $downloadedBytes + $count
                }
                Write-Host "`nFinished Download"
            } finally {
                # Set the console settings back
                [System.Console]::CursorVisible = $startVisibility
                [System.Console]::ForegroundColor = $startColor
            }
        } finally {
            # Cleanup resources
            if ($targetStream) {
                $targetStream.Flush()
                $targetStream.Close()
                $targetStream.Dispose()
            }
            if ($responseStream) {
                $responseStream.Dispose()
            }
        }
    }

    # This function 'normalizes' the version number - creates left 0 padded segments "0000.0000.0000" that can be compared against
    function NormalizeVersionNumber() {
        param(    
            [Parameter(Mandatory = $True)]
            [string] $VersionNumber
        )

        $parts = $VersionNumber.Split(".")
        if ($parts.Length -eq 2) {
            $parts += "0"
        }
        if ($parts.Length -ne 3) {
            throw "$VersionNumber is not formatted correctly (d.d.d)"
        }

        $rc = "";
        $parts | ForEach-Object {
            $rc += $_.PadLeft(5 - $_.Length, '0')
            $rc += "."
        }
        return $rc.Substring(0, $rc.Length - 1)
    }

    # This function will process a command from the upgrade.lst file (called from ProcessVersionChange)
    function ProcessCommand() {
        param(    
            [Parameter(Mandatory = $True)]
            [string] $Line
        )

        # Use of global variables (not passed in)
        $Line = $Line.Replace("`$OPENHAB_USERDATA", $OHUserData)
        $Line = $Line.Replace("`$OPENHAB_CONF", $OHConf)
        $Line = $Line.Replace("`$OPENHAB_HOME", $OHDirectory)
        $Line = $Line.Replace("`$OPENHAB_RUNTIME", $OHRuntime)
        
        # Split the line into it's distinct parts
        $parts = $Line.Split(";")

        # blank line - simply return
        if ($parts.length -eq 0) {
            return;
        }

        # If default - rename an item to "x.bak" (assumes a new version of this file will be added by the upgrade process)
        if ($parts[0] -eq "DEFAULT") {
            if ($parts.length -le 1) {
                Write-Host -ForegroundColor Red "Badly formatted: $Line"
            }
            else {
                try {
                    Rename-Item -Path $parts[1] "$parts[1].bak" -ErrorAction Stop
                    Write-Host -ForegroundColor Cyan "$($parts[1]) renamed to $($parts[1]).bak"
                }
                catch {
                    Write-Host -ForegroundColor Yellow "Could not rename $($parts[1]) to $($parts[1]).bak"
                }
            }
        }

        # Deletes an item
        ElseIf ($parts[0] -eq "DELETEDIR" -or $parts[0] -eq "DELETE") {
            if ($parts.length -le 1) {
                Write-Host -ForegroundColor Red "Badly formatted: $Line"
            }
            else {
                try {
                    if ($parts[0] -eq "DELETEDIR") {
                        DeleteIfExists $parts[1] $True
                    } else {
                        DeleteIfExists $parts[1]
                    }
                    Write-Host -ForegroundColor Cyan "Deleted $($parts[1])"
                }
                catch {
                    Write-Host -ForegroundColor Yellow "Could not delete $($parts[1])"

                }
            }
        }

        # Moves an item
        ElseIf ($parts[0] -eq "MOVE") {
            if ($parts.length -le 2) {
                Write-Host -ForegroundColor Red "Badly formatted: $Line"
            }
            else {
                try {
                    Move-Item -Path $parts[1] -Destination $parts[2] -ErrorAction Stop
                    Write-Host -ForegroundColor Cyan "Moved $($parts[1]) to $($parts[2])"
                }
                catch {
                    Write-Host -ForegroundColor Yellow "Could not move $($parts[1]) to $($parts[2])"
                }
            }
        }

        # Replaces text in a file
        ElseIf ($parts[0] -eq "REPLACE") {
            if ($parts.length -le 3) {
                Write-Host -ForegroundColor Red "Badly formatted: $Line"
            }
            else {
                try {
                    (Get-Content $parts[3]).replace($parts[1], $parts[2]) | Set-Content $parts[3]
                    Write-Host -ForegroundColor Cyan "Replaced string $($parts[1]) to $($parts[2]) in file $($parts[3])"
                }
                catch {
                    Write-Host -ForegroundColor Yellow "Could not replace string $($parts[1]) to $($parts[2]) in file $($parts[3])"
                }
            }
        }

        # Shows a note (console message with a green label)
        ElseIf ($parts[0] -eq "NOTE") {
            if ($parts.length -le 1) {
                Write-Host -ForegroundColor Red "Badly formatted: $Line"
            }
            else {
                Write-Host -ForegroundColor Green "Note: " -NoNewLine 
                Write-Host $parts[1]
            }
        }
        # Shows a note (console message with a red label)
        ElseIf ($parts[0] -eq "ALERT") {
            if ($parts.length -le 1) {
                Write-Host -ForegroundColor Red "Badly formatted: $Line"
            }
            else {
                Write-Host -ForegroundColor Red "Warning: " -NoNewLine 
                Write-Host $parts[1]
            }
        }
        Else {
            Write-Host -ForegroundColor Red "Unknown command: $Line"
        }
    }

    # Processes the update.lst file for the specific section, and version (going from x.x.x to x.x.x).
    # A boolean is returned indicating whether we found any commands or not
    function ProcessVersionChange() {
        param(
            [Parameter(Mandatory = $True)]
            [string] $FileName,
            [Parameter(Mandatory = $True)]
            [string] $Section,
            [Parameter(Mandatory = $True)]
            [string] $VersionMsg,
            [Parameter(Mandatory = $True)]
            [string] $OldVersion,
            [Parameter(Mandatory = $True)]
            [string] $NewVersion
        )

        # Flags used
        $InSection = $false
        $InNewVersion = $false

        # Normalize our lower/upper versions
        $NormalizedOldVersion = NormalizeVersionNumber $OldVersion
        $NormalizedNewVersion = NormalizeVersionNumber $NewVersion

        # FoundSomething is true if we did some action (and is returned to caller)
        $FoundSomething = $False

        # Loops through the content of the file...
        Get-Content $FileName -ErrorAction Stop | ForEach-Object {
            # Skip blank lines
            if ($_ -ne "") {
                # If it's OUR section - flip the switch
                if ($_ -match "\[\[$Section\]\]") {
                    $InSection = $True
                }
                # If it's not OUR section - flip it false
                ElseIf ($_ -match "\[\[.*\]\]") {
                    $InSection = $false
                    $InNewVersion = $false
                }
                # If its a version number section
                ElseIf ($_ -match "\[\d\.*\d\.*\d\]") {
                    # Determine if we are in a section and the version number is greater than our lower bound but less than or equal to our upper bound
                    if ($InSection) {
                        $NormalizedSectionVersion = NormalizeVersionNumber $_.Substring(1, $_.length - 2)
                        $InNewVersion = ($NormalizedSectionVersion -gt $NormalizedOldVersion) -and ($NormalizedSectionVersion -le $NormalizedNewVersion)
                        if ($InNewVersion -and $InSection) {
                            # If so, show that we are processing this section
                            Write-Host ""
                            Write-Host -ForegroundColor Cyan "$VersionMsg $_ :"
                        }
                    }
                }
                else {
                    if ($InSection -and $InNewVersion) {
                        # Woohoo - found a command to process
                        $FoundSomething = $True
                        ProcessCommand $_
                    }
                }
            }
        }

        return $FoundSomething
    }

    # Force reimport of common functions (in case of upgrading the script)
    Import-Module $PSScriptRoot\common.psm1 -Force

    # Write out startup message
    Write-Host ""
    BoxMessage "openHAB update script" Magenta
    Write-Host ""
    
    # Check for admin (commented out - don't think we need it)
    # CheckForAdmin

    # Check for openhab running
    CheckOpenHABRunning

    # Check if service is installed, stop and delete it
    Write-Host -ForegroundColor Cyan "Checking whether a service exists"
    try {
        $service = Get-Service 'openHAB%' -ErrorAction Ignore
        if ($service) {
            # Stop and delete the service
            Write-Host -ForegroundColor Cyan "Stopping the service"
            Stop-Service $service.Name -Force -ErrorAction Stop
            Write-Host -ForegroundColor Cyan "Deleting the service"
            Remove-Service $service.Name -ErrorAction Stop
        }
    }
    catch {
        exit PrintAndReturn "Could not stop/delete the openHAB windows server - please do that manually and try again" $_
    }

    
    # Find the proper directory root directory
    Write-Host -ForegroundColor Cyan "Checking the specified openHAB directory"
    $OHDirectory = GetOpenHABRoot $OHDirectory
    if ($OHDirectory -eq "") {
        exit PrintAndReturn "Could not find the openHAB directory! Make sure you are in the openHAB directory or specify the -OHDirectory parameter!"
    }

    # Get the various 'other' directories
    $OHConf = GetOpenHABDirectory "OPENHAB_CONF" "$OHDirectory\conf"
    $OHUserData = GetOpenHABDirectory "OPENHAB_USERDATA" "$OHDirectory\userdata"
    $OHRuntime = GetOpenHABDirectory "OPENHAB_RUNTIME" "$OHDirectory\runtime"
    $OHAddons = GetOpenHABDirectory "OPENHAB_ADDONS" "$OHDirectory\addons"

    # Validate that all the directories exist (and are directories)
    if (-NOT (Test-Path -Path $OHConf -PathType Container)) {
        exit PrintAndReturn "Configuration directory does not exist:  $OHConf"
    }

    if (-NOT (Test-Path -Path $OHUserData -PathType Container)) {
        exit PrintAndReturn "Userdata directory does not exist:  $OHUserData"
    }
    
    if (-NOT (Test-Path -Path $OHRuntime -PathType Container)) {
        exit PrintAndReturn "Runtime directory does not exist:  $OHRuntime"
    }
    
    if (-NOT (Test-Path -Path $OHAddons -PathType Container)) {
        exit PrintAndReturn "Addons directory does not exist:  $OHAddons"
    }
    
    # Tell the user what we are processing
    Write-Host -ForegroundColor Yellow "Using $OHConf as conf folder"
    Write-Host -ForegroundColor Yellow "Using $OHUserData as userdata folder"
    Write-Host -ForegroundColor Yellow "Using $OHRuntime as runtime folder"
    Write-Host -ForegroundColor Yellow "Using $OHAddons as addons folder"

    # Get current openHAB version
    $CurrentVersion = GetOpenHABVersion $OHUserData
    if ($CurrentVersion -eq "") {
        exit PrintAndReturn "Can't get the current openhab version from $OHUserData\etc\version.properties"
    }

    # Get the current version (d.d.d) 
    #    If stable, simply use d.d.d
    #    If snapshot, strip the "-snapshot" part (d.d.d-snapshort)
    #    If milestone, strip the milestone part (d.d.d.milestone)
    $parts = $CurrentVersion.Split(".")
    if (($parts.Length -lt 3) -or ($parts.Length -gt 4)) {
       exit PrintAndReturn "Current openhab version from $OHUserData\etc\version.properties is malformed.  Should be either d.d.d (for stable) or d.d.d-snapshot (for snapshot) or d.d.d.d (for milestones) - malformed version: $CurrentVersion"
    }
    
    if ($parts[2].EndsWith("-SNAPSHOT", "CurrentCultureIgnoreCase")) {
        $CurrentVersion = $parts[0] + "." + $parts[1] + "." + $parts[2].Substring(0, $parts[2].Length - "-SNAPSHOT".Length);
    }
    else {
        $CurrentVersion = $parts[0] + "." + $parts[1] + "." + $parts[2]
    }
    Write-Host -ForegroundColor Yellow "The current version is $CurrentVersion"        
    
    # If the OHVersion parameter was not specified,
    #    If the current version is snapshot  - make OHVersion the same snapshot
    #    If the current version is stable    - make OHVersion the next minor upgrade (current version 3.0.0 would make our OHversion 3.1.0)
    #    If the current version is milestone - make OHVersion the stable version (3.1.0.M6 becomes 3.1.0)
    if (-Not $OHVersion) {
        if ($parts.Length -eq 3) {
            if ($parts[2].EndsWith("-SNAPSHOT", "CurrentCultureIgnoreCase")) {
                $OHVersion = $parts[0] + "." + $parts[1] + "." + $parts[2]
            } else {
                $OHVersion = $parts[0] + "." + ([int]$parts[1] + 1) + "." + $parts[2]
            }
        } elseif ($parts.Length -eq 4) {
            $OHVersion = $parts[0] + "." + $parts[1] + "." + $parts[2]
        }
        else {
            exit PrintAndReturn "The current version $CurrentVersion was not formatted correctly (d.d.d)"
        }
    }
    
    # If snapshot was defined, add "-snapshot" to the OHVersion if not already present
    if ($Snapshot -eq $true) {
        BoxMessage "-SNAPSHOT is deprecated - please put '-snapshot' in OHVersion instead (ex: 3.1.0-snapshot)" Magenta
        if (-Not $OHVersion.EndsWith("-SNAPSHOT", "CurrentCultureIgnoreCase")) {
            $OHVersion = $OHVersion + "-SNAPSHOT"
        }
    }

    # Split up the OHVersion to validate
    $parts = $OHVersion.Split(".")

    # If only "x.y" - make "x.y.0"
    if ($parts.Length -eq 2) {
        $parts += "0"
    }

    # Valid versions: 
    #    Stable:    "3.x.y"
    #    Snapshot:  "3.x.y-SNAPSHOT"
    #    Milestone: "3.x.y.Mz"
    if (($parts.Length -lt 3) -or ($parts.Length -gt 4)) {
        exit PrintAndReturn "The specified OH version $OHVersion was not formatted correctly (d.d.d[.d])"
    }

    $Snapshot = $False
    $Milestone = ""
    if ($parts[2].EndsWith("-SNAPSHOT", "CurrentCultureIgnoreCase")) {
        $Snapshot = $True
        $parts[2] = $parts[2].Substring(0, $parts[2].Length - "-SNAPSHOT".Length);
    } elseif ($parts.Length -eq 4) {
        $Milestone = $parts[3]
    }
    $OHVersion = $parts[0] + "." + $parts[1] + "." + $parts[2]

    # Recreate the name - should be standardized now and is used for messages and downloads
    if ($Snapshot -eq $True) {
        $OHVersionName =  "$OHVersion-SNAPSHOT"
    } elseif ($Milestone -ne "") {
        $OHVersionName = $OHVersion + "." + $Milestone
    } else {
        $OHVersionName = $OHVersion
    }

    # Get the current directory (so we can switch back to it at the end)
    try {
        $StartDir = Get-Location -ErrorAction Stop 
    }
    catch {
        exit PrintAndReturn "Can't retrieve the current location - exiting" $_
    }

    # Set the current directory to our OH root directory
    Write-Host -ForegroundColor Cyan "Changing location to $OHDirectory"
    try {
        Set-Location -Path $OHDirectory
    }
    catch {
        exit PrintAndReturn "Could not change location to $OHDirectory - exiting" $_
    }

    # Setup where our temporary locations will be
    $TempDir = "$(GetOpenHABTempDirectory)"
    $TempDistributionZip = "$TempDir\openhab-$OHVersion.zip";
    $TempDistribution = "$TempDir\update"

    # Create the proper download URLs
    if ($Snapshot) {
        $DownloadLocation="https://ci.openhab.org/job/openHAB-Distribution/lastSuccessfulBuild/artifact/distributions/openhab/target/openhab-$OHVersionName.zip"
        $AddonsDownloadLocation="https://ci.openhab.org/job/openHAB-Distribution/lastSuccessfulBuild/artifact/distributions/openhab-addons/target/openhab-addons-$OHVersionName.kar"
    }
    elseif ($Milestone -ne "") {
        $DownloadLocation="https://www.openhab.org/download/milestones/org/openhab/distro/openhab/$OHVersionName/openhab-$OHVersionName.zip"
        $AddonsDownloadLocation="https://www.openhab.org/download/milestones/org/openhab/distro/openhab-addons/$OHVersionName/openhab-addons-$OHVersionName.kar"
    }
    else {
        $DownloadLocation = "https://www.openhab.org/download/releases/org/openhab/distro/openhab/$OHVersionName/openhab-$OHVersionName.zip"
        $AddonsDownloadLocation = "https://www.openhab.org/download/releases/org/openhab/distro/openhab-addons/$OHVersionName/openhab-addons-$OHVersionName.kar"
    }

    # If we are not in SkipNew (or SkipNew and the temporary distribution file/folders have not been created yet):
    #   1. Delete and recreate the temporary distribution directory if it exists
    #   2. Download the distribution to the temp directory
    #   3. Expand the distribution to the temp directory
    #   4. Copy the update.ps1/common.psm1 files to the temp distribution if KeepUpdateScript is true (dev purposes only)
    if (($SkipNew -eq $False) -or 
            (($SkipNew -eq $True) -and -NOT
                ((Test-Path -Path $TempDistributionZip -PathType Leaf) -and (Test-Path -Path $TempDistribution -PathType Container))
            )
        ) {
        ########### STEP 1 - Delete and recreate the temporary distribution directory if it exists
        try {
            DeleteIfExists $TempDir $True
        }
        catch {
            # Do nothing here - probably a file lock issue
        }

        try {
            Write-Host -ForegroundColor Cyan "Creating temporary update directory $TempDir"
            CreateDirectory $TempDir
        }
        catch {
            exit PrintAndReturn "Error creating temporary update directory $TempDir - exiting" $_
        }

        ########### STEP 2 - download the distribution
        try {
            Write-Host -ForegroundColor Cyan "Downloading the openHAB $OHVersionName distribution to $TempDistributionZip"
            DownloadFiles $DownloadLocation $TempDistributionZip
        }
        catch {
            if ([int]$_.Exception.InnerException.Response.StatusCode -eq 404) {
                exit PrintAndReturn "Download of $OHVersionName failed because it's not a valid version" $_
            } else {
                exit PrintAndReturn "Download of $DownloadLocation failed" $_
            }
        }

        ########### STEP 3 - Expand the archive
        try {
            Write-Host -ForegroundColor Cyan "Extracting the archive ($TempDistributionZip) to $TempDistribution"
            Expand-Archive -Path $TempDistributionZip -DestinationPath $TempDistribution -Force -ErrorAction Stop
        } catch {
            exit PrintAndReturn "Unzipping of $TempDistributionZip to $TempDistribution failed." $_
        }

        ########### STEP 4 - Copy the update/common over if we are keeping the update scripts
        if ($KeepUpdateScript) {
            try {
                Write-Host -ForegroundColor Cyan "Keeping commons.psm1 and update.ps1 by copying to $TempDistribution\runtime\bin"
                Copy-Item -Path "$OHRuntime\bin\common.psm1" -Destination "$TempDistribution\runtime\bin\common.psm1" -Force
                Copy-Item -Path "$OHRuntime\bin\update.ps1" -Destination "$TempDistribution\runtime\bin\update.ps1" -Force
            } catch {
                Write-Error $_
                Write-Host -ForegroundColor Magenta "Could not copy the common.psm1 and update.ps1 to $TempDistribution\runtime\bin"
                # Don't bother with AutoConfirm here since this is special debugging logic to begin with
                $confirmation = Read-Host "Okay to Continue? [y/N]"
                if ($confirmation -ne 'y') {
                    exit PrintAndReturn "Cancelling update"
                }
            }
        }
    }

    # If not SkipNew - check to see if the new distribution has an update.ps1 (which is likely)
    # and then execute it (exiting with it's result)
    if ($SkipNew -eq $False) {
        $newUpdate = Join-Path $TempDistribution "\runtime\bin\update.ps1"

        If (Test-Path $newUpdate) {
            Write-Host ""
            BoxMessage "New update.ps1 was found - executing it instead (found in $newUpdate)" Magenta
            Write-Host ""
            try {
                # go back to our original directory so the new update script does it as well
                Set-Location -Path $StartDir  -ErrorAction Continue 
                . $newUpdate
                exit Update-openHAB -OHDirectory $OHDirectory -OHVersion $OHVersionName -AutoConfirm $AutoConfirm -SkipNew $true -KeepUpdateScript $KeepUpdateScript
            } catch {
                exit PrintAndReturn "Execution of new update.ps1 failed - please execute it yourself (found in $newUpdate)" $_
            }
        }
    }

    # Do the following questions after the update.ps1 check to make sure this question isn't asked twice!

    # Are we resinstalling the current version (as long as it's not a snapshot)
    if ($OHVersion -eq $CurrentVersion -and $Snapshot -eq $False) {
        if ($AutoConfirm) {
            Write-Host -ForegroundColor Magenta "Current version is equal to specified version ($OHVersionName).  ***REINSTALLING*** $OHVersionName instead (rather than upgrading)."
        } else {
            Write-Host -ForegroundColor Magenta "Current version is equal to specified version ($OHVersionName).  If you continue, you will REINSTALL $OHVersionName rather than upgrade."
            $confirmation = Read-Host "Okay to Continue? [y/N]"
            if ($confirmation -ne 'y') {
                exit PrintAndReturn "Cancelling update"
            }
        }
        Write-Host -ForegroundColor Yellow "REINSTALLING" -NoNewline -BackgroundColor Blue
        Write-Host -ForegroundColor Yellow " version $OHVersionName"
    } else {

        # Are we trying to downgrade the distribution (yikes!)
        if ((NormalizeVersionNumber $OHVersion) -lt (NormalizeVersionNumber $CurrentVersion)) {
            # Don't use autoconfirm on a downgrade warning
            BoxMessage "You are attempting to downgrade from $CurrentVersion to $OHVersionName !!!" Red
            Write-Host -ForegroundColor Magenta "This script is not meant to downgrade and the results will be unpredictable"
            $confirmation = Read-Host "Okay to Continue? [y/N]"
            if ($confirmation -ne 'y') {
                exit PrintAndReturn "Cancelling update"
            }
            Write-Host -ForegroundColor Yellow "DOWNGRADING" -NoNewline -BackgroundColor Red
            Write-Host -ForegroundColor Yellow " to version $OHVersionName"
        } else {
            Write-Host -ForegroundColor Yellow "Upgrading to version $OHVersionName"
        }
    }


    # Crete the temporary backup locations to the current distribution
    $TempBackupDir = "$TempDir\backup-$CurrentVersion"
    $TempBackupDirHome = $TempBackupDir + "\home"
    $TempBackupDirRuntime = $TempBackupDir + "\runtime"
    $TempBackupDirUserData = $TempBackupDir + "\userdata"
    $TempBackupDirConf = $TempBackupDir + "\conf"

    # Backup the current distribution to those locations
    Write-Host ""
    Write-Host -ForegroundColor Cyan "Making a backup of your distribution to $TempBackupDir"
    try {
        Write-Host -ForegroundColor Cyan "Creating backup directories in $TempBackupDir"
        DeleteIfExists $TempBackupDir $True

        Write-Host -ForegroundColor Cyan "Copying directory conf, userdata and runtime to $TempBackupDirConf"
        Copy-Item -Path $OHConf, $OHUserData, $OHRuntime -Destination $TempBackupDir -Recurse -Force -ErrorAction Stop

        Write-Host -ForegroundColor Cyan "Copying files from $OHDirectory to $TempBackupDirHome"
        Get-ChildItem $OHDirectory -File -ErrorAction Stop | Copy-Item -Destination $TempBackupDirHome -Force -ErrorAction Stop

    } catch {
        exit PrintAndReturn "Could not backup existing distribution to $TempBackupDir" $_
    }
    
    # Alright - we are ready to being the update process.  This will be wrapped in a 
    # try/catch/finally to restore our current distribution on error and to cleanup
    # the temporary files when finished
    try {

        # If our update.lst exists, process the notes (ie MSG section) and the PRE section
        $updateLst = Join-Path $TempDistribution "\runtime\bin\update.lst"

        if (Test-Path $updateLst) {
            Write-Host ""
            Write-Host -ForegroundColor Cyan "The script will attempt to update openHAB to version $OHVersionName"
            Write-Host -ForegroundColor Cyan "Please read the following " -NoNewLine
            Write-Host -ForegroundColor Green "notes" -NoNewLine
            Write-Host -ForegroundColor Cyan " and " -NoNewLine
            Write-Host -ForegroundColor Red "warnings"
            $NotesFound = $False
            try {
                $NotesFound = ProcessVersionChange $updateLst "MSG" "Important notes for version" $CurrentVersion $OHVersion
            } catch {
                # PrintAndReturn since there have been no file changes yet
                exit PrintAndReturn "Could not process 'MSG' of $updateLst" $_
            }

            if ($NotesFound) {
                if (-Not $AutoConfirm) {
                    $confirmation = Read-Host "Okay to Continue? [y/N]"
                    if ($confirmation -ne 'y') {
                        exit PrintAndReturn "Cancelling update"
                    }
                }
            } else {
                Write-Host -ForegroundColor Blue "No notes found for version $OHVersionName"    
            }

            try {
                Write-Host ""
                Write-Host -ForegroundColor Cyan "Execute 'PRE' instructions for version $OHVersionName"
                if (-NOT (ProcessVersionChange $updateLst "PRE" "Performing pre-update tasks for version" $CurrentVersion $OHVersion)) {
                    Write-Host -ForegroundColor Blue "No 'PRE' instructions found for version $OHVersionName"
                }
            } catch {
                return PrintAndThrow "Could not process 'PRE' of $updateLst" $_
            }
            Write-Host ""
        }


        # Delete current userdata files
        # Update openHAB
        #   1. First remove all file in runtime (they will all be replaced)
        #   2. Remove all the userdata\etc files listed in userdata_sysfiles.lst (they may be replaced)
        #   3. Remove the cache/tmp directories
        #   4. Then copy all files from our new distribution WITHOUT overwriting anything
        #      (by removals in 1 & 2 - that means we will replace those)
        #

        ############## STEP 1 - remove runtime
        try {
            Write-Host -ForegroundColor Cyan "Deleting current runtime ($OHRuntime)"
            DeleteIfExists $OHRuntime $True
        } catch {
            return PrintAndThrow "Could not delete current runtime ($OHRuntime)" $_
        }

        ############## STEP 2 - remove userdata\etc files in userdata_sysfiles.lst
        $updateSysFilesLst = "$TempDistribution\runtime\bin\userdata_sysfiles.lst"
        if (Test-Path $updateSysFilesLst) {
            Write-Host -ForegroundColor Cyan "Deleting current files in userdata that should not persist"
            foreach ($FileName in Get-Content $updateSysFilesLst) {
                $fileToDelete = "$OHUserData\etc\$FileName"
                try {
                    if (Test-Path -Path $fileToDelete) {
                        DeleteIfExists $fileToDelete
                        Write-Host -ForegroundColor Cyan "Deleted $FileName from $OHUserData\etc"
                    }
                } catch {
                    Write-Error $_
                    Write-Host "Could not delete $fileToDelete.  File is no longer needed and should be manually deleted."
                }
            }
        }

        ############## STEP 3 - remove cache/tmp directories
        try {
            Write-Host -ForegroundColor Cyan "Removing $OHUserData\cache"
            DeleteIfExists "$OHUserData\cache" $True
        } catch {
            return PrintAndThrow "Could not delete the $OHUserData\cache directory" $_
        }

        try {
            Write-Host -ForegroundColor Cyan "Removing $OHUserData\tmp"
            DeleteIfExists "$OHUserData\tmp" $True
        } catch {
            return PrintAndThrow "Could not delete the $OHUserData\tmp directory" $_
        }

        ############## STEP 4 - copy files from temporary to distribution WITHOUT replacement
        Write-Host -ForegroundColor Cyan "Copying $TempDistribution to $OHDirectory without overwriting existing ones"
        try {
            Get-ChildItem -Path $TempDistribution -Recurse -ErrorAction Stop | ForEach-Object { 
                $relPath = GetRelativePath $TempDistribution $_.FullName

                if ($relPath.StartsWith(".\addons")) {
                    $localPath = Join-Path $OHAddons $relPath.Substring(".\addons".Length)
                } elseif ($relPath.StartsWith(".\conf")) {
                    $localPath = Join-Path $OHConf $relPath.Substring(".\conf".Length)    
                } elseif ($relPath.StartsWith(".\userdata")) {
                    $localPath = Join-Path $OHUserData $relPath.Substring(".\userdata".Length)
                } elseif ($relPath.StartsWith(".\runtime")) {
                    $localPath = Join-Path $OHRuntime $relPath.Substring(".\runtime".Length)
                } else {
                    $localPath = Join-Path $OHDirectory $relPath
                }
                if (-Not (Test-Path $localPath)) {
                    if (Test-Path $localPath -PathType Container) {
                        CreateDirectory $localPath
                    } else {
                        Copy-Item -Path $_.FullName -Destination $localPath -ErrorAction Stop
                    }
                }
            }
        } catch {
            return PrintAndThrow "Error occurred copying $TempDistribution to $OHDirectory" $_
        }

        # If we have an update.lst - process the "POST" section
        if (Test-Path $updateLst) {
            Write-Host ""
            try {
                Write-Host -ForegroundColor Cyan "Execute 'POST' instructions for version $OHVersionName"
                if (-NOT (ProcessVersionChange $updateLst "POST" "Performing post-update tasks for version" $CurrentVersion $OHVersion)) {
                    Write-Host -ForegroundColor Blue "No 'POST' instructions found for version $OHVersionName"
                }
            } catch {
                return PrintAndThrow "Could not process 'POST' of $updateLst" $_
            }
        }
        Write-Host ""


        # If there's an existing addons file, we need to replace it with the correct version.
        try {
            $AddonsFile = "$OHAddons\openhab-addons-$OHVersionName.kar"
            if (Test-Path -Path $AddonsFile) {
                Write-Host "Found an openHAB addons file, replacing with new version"
                DeleteIfExists $AddonsFile
                DownloadFiles $AddonsDownloadLocation "$OHAddons\openhab-addons-$OHVersionName.kar"
            }
        } catch {
            return PrintAndThrow "Could not replace the $AddonsFile" $_
        }

        # Now run the upgrade tool to update the JSON database
        Write-Host "Starting JSON database update..."
        java -jar "$OHRuntime\bin\upgradetool.jar"
        if ($LASTEXITCODE -ne 0) {
            Write-Error "Update tool failed, please check the openHAB website (www.openhab.org) for manual update instructions."
        exit 1
        }
        Write-Host "JSON database updated successfully."

        # Hop for joy - we did it!
        Write-Host -ForegroundColor Green "openHAB updated to version $OHVersionName!"
        Write-Host -ForegroundColor Green "Run start.bat to launch it."
        Write-Host -ForegroundColor Green "Check https://www.openhab.org/docs/installation/windows.html"
        Write-Host -ForegroundColor Green "for instructions on re-installing the Windows Service if desired"
    }
    catch {

        # Some issue happened - we need to copy the old distribution back 
        BoxMessage "Restoring your distribution from $TempBackupDir" Yellow
        try {
            Write-Host -ForegroundColor Cyan "Removing existing files in $OHDirectory"
            Get-ChildItem $OHDirectory -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
            Write-Host -ForegroundColor Cyan "Copying backup files from $TempBackupDirHome to $OHDirectory"
            Get-ChildItem $TempBackupDirHome -file -ErrorAction Stop | Copy-Item -Destination $OHDirectory -Force -ErrorAction Stop

            Write-Host -ForegroundColor Cyan "Removing the directory $OHConf"
            Remove-Item "$OHConf\*" -Recurse -Force -ErrorAction SilentlyContinue
            Write-Host -ForegroundColor Cyan "Copying backup directory $TempBackupDirConf to $OHConf"
            Copy-Item -Path "$TempBackupDirConf\*" -Destination $OHConf -Recurse -Force -ErrorAction Stop

            Write-Host -ForegroundColor Cyan "Removing the directory $OHUserData"
            Remove-Item "$OHUserData\*" -Recurse -Force -ErrorAction SilentlyContinue
            Write-Host -ForegroundColor Cyan "Copying backup directory $TempBackupDirUserData to $OHUserData"
            Copy-Item -Path "$TempBackupDirUserData\*" -Destination $OHUserData -Recurse -Force -ErrorAction Stop

            Write-Host -ForegroundColor Cyan "Removing the directory $OHRuntime"
            Remove-Item "$OHRuntime\*" -Recurse -Force -ErrorAction SilentlyContinue
            Write-Host -ForegroundColor Cyan "Copying backup directory $TempBackupDirRuntime to $OHRuntime"
            Copy-Item -Path "$TempBackupDirRuntime\*" -Destination $OHRuntime -Recurse -Force -ErrorAction Stop
        } catch {
            Write-Host -ForegroundColor Cyan "Restoration was unsuccessful - you may want to restore from $TempBackupDir yourself"
            Write-Error $_
        }
        exit -1
    }
    finally {
        # And we are done...
        Write-Host ""

        # If our temp backup directory exists - ask if we should remove it
        # TODO - maybe only do this if an error occurred
        try {
            if (Test-Path $TempBackupDir) {
                if ($AutoConfirm) {
                    Write-Host -ForegroundColor Cyan "Removing temporary distribution backup $TempBackupDir"
                    DeleteIfExists $TempBackupDir $True
                } else {
                    Write-Host -ForegroundColor Cyan "Your prior distribution is in $TempBackupDir"
                    $confirmation = Read-Host "Should it be deleted? [y/N]"
                    if ($confirmation -eq 'y') {
                        Write-Host -ForegroundColor Cyan "Removing temporary distribution backup $TempBackupDir"
                        DeleteIfExists $TempBackupDir $True
                    }
                }
            }
        }
        catch {
            Write-Host -ForegroundColor Red "Could not delete $TempBackupDir - delete it manually"
        }

        try {
            # If the backup directory doesn't exist - delete the tempdir directory (and it's parent)
            # (may exist if they answer "N" to the above question)
            if (-NOT (Test-Path $TempBackupDir)) {
                try {
                    Write-Host -ForegroundColor Cyan "Removing temporary directory $TempDir"
                    DeleteIfExists $TempDir $True
                }
                catch {
                    Write-Host -ForegroundColor Red "Could not delete $TempDir - delete it manually"
                }
            }
        }
        catch {
            Write-Host -ForegroundColor Red "Could not delete $parent - delete it manually"
        }

        # FINALLY - set our location back to where we began
        Write-Host -ForegroundColor Cyan "Setting location back to $StartDir"
        Set-Location -Path $StartDir  -ErrorAction Continue
    }
}
