diff --git a/.gitignore b/.gitignore index d782c87a3..f8215b206 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ Thumbs.db node_modules/ wix/Workdir/ + +inno_setup/output/ +inno_setup/src/ +inno_setup/workspace/ \ No newline at end of file diff --git a/inno_setup/CreateExe.ps1 b/inno_setup/CreateExe.ps1 new file mode 100644 index 000000000..1c4fb928c --- /dev/null +++ b/inno_setup/CreateExe.ps1 @@ -0,0 +1,387 @@ +<# +.SYNOPSIS + Script that uses Inno Setup to create an EXE installer (for a JDK/JRE) from a zip file by either copying it from a local directory or downloading it from a URL. + +.DESCRIPTION + This script automates the process of creating an EXE installer for OpenJDK distributions. It accepts either a local zip file path or a URL to a zip file (containing a zip file of the JDK binaries), extracts the contents, and prepares the necessary folder structure. This script is designed to be used with Inno Setup, a tool for creating Windows installers. First, a template Inno Setup script is modified with the provided parameters. The script then generates an EXE installer package that can be used to install OpenJDK on Windows systems. If a signing cli command is provided, the script will also sign the resulting EXE package (Note: this is the only way to also sign the uninstall script that goes into the EXE). The script supports various parameters to customize the installer, such as application name, vendor information, architecture, and versioning details. + +.PARAMETER ZipFilePath + Optional. The local path to a zip file to be copied and unzipped. + +.PARAMETER ZipFileUrl + Optional. The URL of a zip file to be downloaded and unzipped. + +.PARAMETER ProductMajorVersion + Example: if the version is 17.0.16+8, this is 17 + +.PARAMETER ProductMinorVersion + Example: if the version is 17.0.16+8, this is 0 + +.PARAMETER ProductMaintenanceVersion + Example: if the version is 17.0.16+8, this is 16 + +.PARAMETER ProductPatchVersion + This is a number that comes between the maintenance and build numbers if it exists. + For most builds, this is 0. If this is a respin, it will be the respin number. + Examples: + - if the version is 17.0.16+8, this is 0 + - if the version is 17.0.8.1+1, this is 1 + +.PARAMETER ProductBuildNumber + Example: if the version is 17.0.16+8, this is 8 + +.PARAMETER ExeProductVersion + The full version of the JDK/EXE product as written with only '.' and numbers. + Example: if the version is 17.0.16+8, this is "17.0.16.8" + +.PARAMETER Arch + Mostly used to determine default display names like $AppName and $OutputFileName. + Examples: x86, x64, arm, arm64, aarch64 + +.PARAMETER JVM + The JVM used in the JDK/JRE. This is used to determine default display names like $AppName and $OutputFileName. + Valid values: hotspot, openj9, dragonwell + +.PARAMETER ProductCategory + The category of the product, either jdk or jre. This is used to determine + default display names like $AppName and $OutputFileName, and Registry Key behavior. + Valid values: jdk, jre + +.PARAMETER AppName + Optional. The name of the App. + Example: "Eclipse Temurin JDK with Hotspot 25.0.1+8 (x64)" + Default: "$VendorBranding $($ProductCategory.ToUpper()) with $CapitalizedJVM $ProductMajorVersion.$ProductMinorVersion.$ProductMaintenanceVersion+$ProductBuildNumber ($Arch)" + +.PARAMETER Vendor + Optional. Default: Eclipse Adoptium + +.PARAMETER VendorBranding + Optional. Helps determine default values for $AppName + but goes unused if those are both provided. + Default: Eclipse Temurin + +.PARAMETER VendorBrandingLogo + Optional. The path to the ".ico" file to be used as the logo in the installer. + This can be a full path to any file, or a relative path to a logo file in the inno_setup/logos folder. + Default: "logos\logo.ico" + +.PARAMETER VendorBrandingDialog + Optional. The path to the ".png" file to be used as the welcome dialog in the installer. + This can be a full path to any file, or a relative path to a logo file in the inno_setup/logos folder. + Default: "logos\welcome-dialog.png" + +.PARAMETER VendorBrandingSmallIcon + Optional. The path to the ".png" file to be used as the small icon in the installer. + This can be a full path to any file, or a relative path to a logo file in the inno_setup/logos folder. + Default: "logos\logo-small.png" + +.PARAMETER ProductPublisherLink + Optional. The URL that represents the product publisher. Default: "https://adoptium.net" + +.PARAMETER ProductSupportLink + Optional. The URL for where customers can go to for support. Default: "https://adoptium.net/support" + +.PARAMETER ProductUpdateInfoLink + Optional. The URL for product update information. Default: "https://adoptium.net/temurin/releases" + +.PARAMETER OutputFileName + Optional. The name of the output file. Note: Inno Setup will automatically add the '.exe' file extension + Default: + "OpenJDK${ProductMajorVersion}U-$ProductCategory_$Arch_windows_$JVM-$ProductMajorVersion.$ProductMinorVersion.$ProductMaintenanceVersion.$ProductPatchVersion_$ProductBuildNumber" + +.PARAMETER License + Optional. The path to the license file. This can either be a full path to any file, or a relative path to a license file in the inno_setup/licenses folder. + Default: "licenses/license-GPLv2+CE.en-us.rtf" + +.PARAMETER UpgradeCodeSeed + Optional. A seed string used to generate a deterministic PRODUCT_UPGRADE_CODE. + This is used to ensure that the PRODUCT_UPGRADE_CODE is consistent across builds. + If this is not provided, a random PRODUCT_UPGRADE_CODE will be generated. + Default: "" + +.PARAMETER TranslationFile + Optional. The path to the translation file .iss containing text translations for the installer's custom messages. + This can be a path relative to the `installer/inno_setup` directory, or this can be an absolute path. + Default: "translations\default.iss" + +.PARAMETER IncludeUnofficialTranslations + Optional. Set this flag to support unofficial Inno Setup translations like Chinese. + ## Note: Here, unofficial means that there are a few default messages that do not + ## have translations (from English) supported by Inno Setup yet. + Default: "false" + +.PARAMETER SigningCommand + Optional. The command to sign the resulting EXE file. This is typically a command that + uses signtool.exe to sign the EXE file. If this is not provided, the EXE will not be signed. + See the following link for more info on input variables that can be used within the command: https://jrsoftware.org/ishelp/index.php?topic=setup_signtool + Examples: + 'signtool.exe sign /fd SHA256 $f' + 'signtool.exe sign /a /n $qMy Common Name$q /t http://timestamp.comodoca.com/authenticode /d $qMy Program$q $f' + Default: "" + +.EXAMPLE + # Only mandatory inputs are defined here + .\CreateExe.ps1 ` + -ZipFilePath "C:\path\to\file.zip" ` + -ProductMajorVersion 17 ` + -ProductMinorVersion 0 ` + -ProductMaintenanceVersion 16 ` + -ProductPatchVersion 0 ` + -ProductBuildNumber 8 ` + -ExeProductVersion "17.0.16.8" ` + -Arch "x64" ` + -JVM "hotspot" ` + -ProductCategory "jdk" + +.EXAMPLE + # All inputs are defined here + .\CreateExe.ps1 ` + # Mandatory inputs + -ZipFileUrl "https://example.com/file.zip" ` + -ProductMajorVersion 21 ` + -ProductMinorVersion 0 ` + -ProductMaintenanceVersion 8 ` + -ProductPatchVersion 0 ` + -ProductBuildNumber 9 ` + -ExeProductVersion "21.0.8.9" ` + -Arch "aarch64" ` + -JVM "hotspot" ` + -ProductCategory "jdk" ` + # Optional inputs: These are the defaults that will be used if not specified + -AppName "Eclipse Temurin JDK with Hotspot 21.0.8+9 (aarch64)" ` + -Vendor "Eclipse Adoptium" ` + -VendorBranding "Eclipse Temurin" ` + -VendorBrandingLogo "logos\logo.ico" ` + -VendorBrandingDialog "logos\welcome-dialog.png" ` + -VendorBrandingSmallIcon "logos\logo-small.png" ` + -ProductPublisherLink "https://adoptium.net" ` + -ProductSupportLink "https://adoptium.net/support" ` + -ProductUpdateInfoLink "https://adoptium.net/temurin/releases" ` + -OutputFileName "OpenJDK21-jdk_aarch64_windows_hotspot-21.0.8.0.9" ` + -License "licenses/license-GPLv2+CE.en-us.rtf" ` + -UpgradeCodeSeed "MySecretSeedCode(SameAsWix)" ` + -TranslationFile "translations/default.iss" ` + # Additional Optional Inputs: Omitting these inputs will cause their associated process to be skipped + -IncludeUnofficialTranslations "true" ` + -SigningCommand "signtool.exe sign /f C:\path\to\cert" + +.NOTES + Ensure that you have downloaded Inno Setup (can be done through winget or directly from their website: https://jrsoftware.org/isdl.php). For more information, please see the #Dependencies section of the README.md file. + If you do not have Inno Setup installed, you can install it using the following command: + winget install --id JRSoftware.InnoSetup -e -s winget --scope + Or directly download the installation exe by modifying this link to the latest version: + https://files.jrsoftware.org/is/6/innosetup-#.#.#.exe + Example: https://files.jrsoftware.org/is/6/innosetup-6.5.0.exe + Afterwards, please set the following environment variable to the path of the Inno Setup executable (if the default, machine-scope path below is incorrect): + $env:INNO_SETUP_PATH = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" +#> + +param ( + [Parameter(Mandatory = $false)] + [string]$ZipFilePath, + + [Parameter(Mandatory = $false)] + [string]$ZipFileUrl, + + [Parameter(Mandatory = $true)] + [int]$ProductMajorVersion, + + [Parameter(Mandatory = $true)] + [int]$ProductMinorVersion, + + [Parameter(Mandatory = $true)] + [int]$ProductMaintenanceVersion, + + [Parameter(Mandatory = $true)] + [int]$ProductPatchVersion, + + [Parameter(Mandatory = $true)] + [int]$ProductBuildNumber, + + [Parameter(Mandatory = $true)] + [string]$ExeProductVersion, + + [Parameter(Mandatory = $true)] + [string]$Arch, + + [Parameter(Mandatory = $true)] + [ValidateSet("hotspot", "openj9", "dragonwell")] + [string]$JVM, + + [Parameter(Mandatory = $true)] + [ValidateSet("jdk", "jre")] + [string]$ProductCategory = "jdk", + + [Parameter(Mandatory = $false)] + [string]$AppName = "", + + [Parameter(Mandatory = $false)] + [string]$Vendor = "Eclipse Adoptium", + + [Parameter(Mandatory = $false)] + [string]$VendorBranding = "Eclipse Temurin", + + [Parameter(Mandatory = $false)] + [string]$VendorBrandingLogo = "logos\logo.ico", + + [Parameter(Mandatory = $false)] + [string]$VendorBrandingDialog = "logos\welcome-dialog.png", + + [Parameter(Mandatory = $false)] + [string]$VendorBrandingSmallIcon = "logos\logo-small.png", + + [Parameter(Mandatory = $false)] + [string]$ProductPublisherLink = "https://adoptium.net", + + [Parameter(Mandatory = $false)] + [string]$ProductSupportLink = "https://adoptium.net/support", + + [Parameter(Mandatory = $false)] + [string]$ProductUpdateInfoLink = "https://adoptium.net/temurin/releases", + + [Parameter(Mandatory = $false)] + [string]$OutputFileName, + + [Parameter(Mandatory = $false)] + [string]$License = "licenses/license-GPLv2+CE.en-us.rtf", + + [Parameter(Mandatory = $false)] + [string]$UpgradeCodeSeed = "", + + [Parameter(Mandatory = $false)] + [string]$TranslationFile = "translations\default.iss", + + [Parameter(Mandatory = $false)] + [string]$IncludeUnofficialTranslations = "false", + + [Parameter(Mandatory = $false)] + [string]$SigningCommand = "" +) + +# Get the path to Inno Setup folder (parent directory of this script) +$InnoSetupRootDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Find and source the Helpers.ps1 script located in the scripts folder to get access to helper functions +$HelpersScriptPath = Join-Path -Path $InnoSetupRootDir -ChildPath "ps_scripts\Helpers.ps1" +if (-not (Test-Path -Path $HelpersScriptPath)) { + throw "Error: The Helpers.ps1 script was not found at '$HelpersScriptPath'." +} +. $HelpersScriptPath +# Validate the inputs +## Ensure that either a local zip file path or a URL is provided, but not both +ValidateZipFileInput -ZipFilePath $ZipFilePath -ZipFileUrl $ZipFileUrl + +# Validate Architecture input and get ArchitecturesAllowed value for template input +$ArchitecturesAllowed = GetArchitectureAllowedTemplateInput -Arch $Arch + +# Set values needed in file +$CapitalizedJVM = CapitalizeString ` + -InputString $JVM ` + +$VersionMajorToMaintenance = "${ProductMajorVersion}.${ProductMinorVersion}.${ProductMaintenanceVersion}" + +# Set default values if optional parameters are not provided +$AppName = SetDefaultIfEmpty ` + -InputValue $AppName ` + -DefaultValue "$VendorBranding $($ProductCategory.ToUpper()) with ${CapitalizedJVM} ${VersionMajorToMaintenance}+${ProductBuildNumber} ($Arch)" + +## Note: Inno Setup will add the '.exe' file extension automatically +$OutputFileName = SetDefaultIfEmpty ` + -InputValue $OutputFileName ` + -DefaultValue "OpenJDK${ProductMajorVersion}U-${ProductCategory}_${Arch}_windows_${JVM}_${VersionMajorToMaintenance}_${ProductBuildNumber}" + +## If $env:INNO_SETUP_PATH is not set, default to the standard installation path for a machine-scope installation +$INNO_SETUP_PATH = SetDefaultIfEmpty ` + -InputValue $env:INNO_SETUP_PATH ` + -DefaultValue "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" + +# Clean the src, workspace, and output folders +$srcFolder = Clear-TargetFolder -TargetFolder (Join-Path -Path $InnoSetupRootDir -ChildPath "src") +$workspaceFolder = Clear-TargetFolder -TargetFolder (Join-Path -Path $InnoSetupRootDir -ChildPath "workspace") +$outputFolder = Clear-TargetFolder -TargetFolder (Join-Path -Path $InnoSetupRootDir -ChildPath "output") +Write-Host "Folders cleaned: $srcFolder, $workspaceFolder, $outputFolder" + +# Download zip file if a URL is provided, otherwise use the local path +if ($ZipFileUrl) { + Write-Host "Downloading zip file from URL: $ZipFileUrl" + $ZipFilePath = DownloadFileFromUrl -Url $ZipFileUrl -DestinationDirectory $workspaceFolder +} +Write-Host "Using ZipFilePath: $ZipFilePath" +UnzipFile -ZipFilePath $ZipFilePath -DestinationPath $srcFolder +$unzippedFolder = (Get-ChildItem -Path $srcFolder -Directory | Select-Object -First 1).FullName + +if (-not $UpgradeCodeSeed) { + # If no UpgradeCodeSeed is given, generate a new PRODUCT_UPGRADE_CODE (random GUID, not upgradable) + $PRODUCT_UPGRADE_CODE = [guid]::NewGuid().ToString("B").ToUpper() + Write-Host "Unique PRODUCT_UPGRADE_CODE: $PRODUCT_UPGRADE_CODE" +} +else { + # Generate a deterministic PRODUCT_UPGRADE_CODE based on input values and UpgradeCodeSeed + # Compose SOURCE_TEXT_GUID similar to the original script + $SOURCE_TEXT_GUID = "${ProductCategory}-${ProductMajorVersion}-${Arch}-${JVM}" + Write-Host "SOURCE_TEXT_GUID (without displaying secret UpgradeCodeSeed): $SOURCE_TEXT_GUID" + # Call getGuid.ps1 to generate a GUID based on SOURCE_TEXT_GUID and UpgradeCodeSeed + $getGuidScriptPath = Join-Path -Path $InnoSetupRootDir -ChildPath "getGuid.ps1" + $PRODUCT_UPGRADE_CODE = GenerateGuidFromString -SeedString "${SOURCE_TEXT_GUID}-${UpgradeCodeSeed}" + Write-Host "Constant PRODUCT_UPGRADE_CODE: $PRODUCT_UPGRADE_CODE" +} +# /DAppId: Inno setup needs us to escape '{' literals by putting two together. The '}' does not need to be escaped +$AppId = "{" + "${PRODUCT_UPGRADE_CODE}" + +# Build the argument list +# For info on CLI options: https://jrsoftware.org/ishelp/index.php?topic=isppcc +# and https://jrsoftware.org/ishelp/index.php?topic=compilercmdline +$InnoSetupArgs = @() + +$InnoSetupArgs += "/J$TranslationFile" +$InnoSetupArgs += "/DArchitecturesAllowed=`"$ArchitecturesAllowed`"" +$InnoSetupArgs += "/DAppName=`"$AppName`"" +$InnoSetupArgs += "/DVendor=`"$Vendor`"" +$InnoSetupArgs += "/DProductCategory=`"$($ProductCategory.ToUpper())`"" +$InnoSetupArgs += "/DJVM=`"$JVM`"" +$InnoSetupArgs += "/DProductMajorVersion=`"$ProductMajorVersion`"" +$InnoSetupArgs += "/DProductMinorVersion=`"$ProductMinorVersion`"" +$InnoSetupArgs += "/DProductMaintenanceVersion=`"$ProductMaintenanceVersion`"" +$InnoSetupArgs += "/DProductPatchVersion=`"$ProductPatchVersion`"" +$InnoSetupArgs += "/DProductBuildNumber=`"$ProductBuildNumber`"" +$InnoSetupArgs += "/DExeProductVersion=`"$ExeProductVersion`"" +$InnoSetupArgs += "/DAppPublisherURL=`"$ProductPublisherLink`"" +$InnoSetupArgs += "/DAppSupportURL=`"$ProductSupportLink`"" +$InnoSetupArgs += "/DAppUpdatesURL=`"$ProductUpdateInfoLink`"" +$InnoSetupArgs += "/DOutputFileName=`"$OutputFileName`"" +$InnoSetupArgs += "/DVendorBrandingLogo=`"$VendorBrandingLogo`"" +$InnoSetupArgs += "/DVendorBrandingDialog=`"$VendorBrandingDialog`"" +$InnoSetupArgs += "/DVendorBrandingSmallIcon=`"$VendorBrandingSmallIcon`"" +$InnoSetupArgs += "/DLicenseFile=`"$License`"" +$InnoSetupArgs += "/DAppId=`"$AppId`"" +$InnoSetupArgs += "/DSourceFiles=`"$unzippedFolder`"" + +# Set this flag to support unofficial inno_setup translations like Chinese +## Note: Here, unofficial means that there are a few default messages that do not +## have translations (from English) supported by Inno Setup yet +if ($IncludeUnofficialTranslations -ne "false") { + Write-Host "Including unofficial translations." + $InnoSetupArgs += '/DINCLUDE_UNOFFICIAL_TRANSLATIONS="true"' +} + +# Sign only if $SigningCommand is not empty or null +# See the following link for more info on Inno Setup signing: https://jrsoftware.org/ishelp/index.php?topic=setup_signtool +# See here for info on /S flag format: https://jrsoftware.org/ishelp/index.php?topic=compilercmdline +if (![string]::IsNullOrEmpty($SigningCommand)) { + Write-Host "Executing Inno Setup with signing." + $InnoSetupArgs += "/SsigningCommand=$SigningCommand" + # set flag /DsignFiles to enable signing with above command + $InnoSetupArgs += '/DsignFiles="true"' +} +else { + Write-Host "Executing Inno Setup without signing." +} + +# Create .exe file based on create_exe.template.iss. +$InnoSetupArgs += "${InnoSetupRootDir}\create_exe.template.iss" +# Run the Inno Setup Compiler (ISCC.exe) with the arguments +& "$INNO_SETUP_PATH" @InnoSetupArgs + +CheckForError -ErrorMessage "ISCC.exe failed to create .exe file." + +Write-Host "EXE file created successfully in '$outputFolder'" diff --git a/inno_setup/README.md b/inno_setup/README.md new file mode 100644 index 000000000..9e877c4a3 --- /dev/null +++ b/inno_setup/README.md @@ -0,0 +1,112 @@ +# Introduction +This tool is designed to create EXE files which are modern and accessibility-friendly. This EXE format provides a reliable and user-friendly installation experience, including a graphical installer interface that achieves the highest standards for accessibility. When installed, the folder `jdk-${ExeProductVersion}-${JVM}` is placed at `C:\Program Files\${Vendor}\` (for machine-wide installs), or `C:\Users\${env:USERNAME}\AppData\Local\Programs\${Vendor}\` (for user installs). + +# How to create EXE files + +## Dependencies +The following files are required in order to successfully run `CreateExe.ps1`. +- Inno Setup: + - Main download page: https://jrsoftware.org/isdl.php + - Direct download link: https://jrsoftware.org/download.php/is.exe?site=1 + - Note: by default, the compiler is downloaded to the path `C:\Program Files (x86)\Inno Setup 6\ISCC.exe`. If Inno Setup is installed to another path, you will need to set `$env:INNO_SETUP_PATH` to this new path. + +## Creating EXE files through CreateExe.ps1 +Please take a look at the [Dependencies](#dependencies) section above to make sure that you have everything needed in order to run our `CreateExe.ps1` script successfully. In this section, you will find a few examples for how to run our script from a powershell terminal. + +For more information on each variable, use the `powershell` command `Get-Help -Detailed .\CreateExe.ps1` or see the `powershell` style header within `inno_setup\CreateExe.ps1` + +**First Example**: Running with all required + optional inputs. Below, you will see the inputs divided into sections: required, optional with a default value (shown below), and optional + changes behavior if omitted. This example builds an Eclipse Temurin EXE file for jdk `21.0.8+9` +```powershell + .\CreateExe.ps1 ` + # Mandatory inputs + -ZipFileUrl "https://example.com/file.zip" ` # You can use either ZipFileUrl or ZipFilePath, not both + -ProductMajorVersion 21 ` + -ProductMinorVersion 0 ` + -ProductMaintenanceVersion 8 ` + -ProductPatchVersion 0 ` + -ProductBuildNumber 9 ` + -ExeProductVersion "21.0.8.9" ` + -Arch "aarch64" ` + -JVM "hotspot" ` + -ProductCategory "jdk" ` + # Optional inputs: These are the defaults that will be used if not specified + -AppName "Eclipse Temurin JDK with Hotspot 21.0.8+9 (aarch64)" ` + -Vendor "Eclipse Adoptium" ` + -VendorBranding "Eclipse Temurin" ` + -VendorBrandingLogo "logos\logo.ico" ` + -VendorBrandingDialog "logos\welcome-dialog.png" ` + -VendorBrandingSmallIcon "logos\logo-small.png" ` + -ProductPublisherLink "https://adoptium.net" ` + -ProductSupportLink "https://adoptium.net/support" ` + -ProductUpdateInfoLink "https://adoptium.net/temurin/releases" ` + -OutputFileName "OpenJDK21-jdk_aarch64_windows_hotspot-21.0.8.0.9" ` + -License "licenses/license-GPLv2+CE.en-us.rtf" ` + -UpgradeCodeSeed "MySecretSeedCode(SameAsWix)" ` + -TranslationFile "translations/default.iss" ` + # Additional Optional Inputs: Omitting these inputs will cause their associated process to be skipped + -IncludeUnofficialTranslations "true" ` + -SigningCommand "signtool.exe sign /f C:\path\to\cert" # For more explanation, see: https://jrsoftware.org/ishelp/index.php?topic=setup_signtool +``` + +**Second Example**: Running with only required inputs. This will produce an EXE file, but many values (ex: OutputFileName) will take the default Eclipse/Adoptium value. Note: either `-ZipFilePath` or `-ZipFileUrl` are required inputs, but you cannot specify both. This example builds an Eclipse Temurin EXE file for jdk `17.0.16+8` +```powershell +.\CreateExe.ps1 ` + -ZipFilePath "C:\path\to\file.zip" ` # You can use either ZipFileUrl or ZipFilePath, not both + -ProductMajorVersion 17 ` + -ProductMinorVersion 0 ` + -ProductMaintenanceVersion 16 ` + -ProductPatchVersion 0 ` + -ProductBuildNumber 8 ` + -ExeProductVersion "17.0.16.8" ` + -Arch "x64" ` + -JVM "hotspot" ` + -ProductCategory "jdk" +``` + +### Sign EXE file +Here you can either sign during compilation (recommended) or after compilation. To sign during compilation, you will need to pass in a formatted CLI command as the value to the `SigningCommand` variable when running `CreateExe.ps1`. Signing during compilation is recommended as it is the only way to ensure that the uninstall script (packaged within the EXE) is also signed by you. For more information on how to format the input to `SigningCommand`, see: https://jrsoftware.org/ishelp/index.php?topic=setup_signtool + +While not recommended, you can choose not to use the `SigningCommand` input and instead manually sign the resulting EXE file after compilation is completed. + +> [!WARNING] +> If you do not use the `SigningCommand` to sign during compilation, then the uninstall script (packaged within your EXE) will not be signed. In this case, if the user attempts to uninstall your OpenJDK, they will be warned that they are about to run a program from an unknown vendor. + + +Example input to `SigningCommand`: +```powershell +-SigningCommand signtool.exe sign /a /n $qMy Common Name$q /t http://timestamp.comodoca.com/authenticode /d $qMy Program$q $f +``` + +# Using EXE files + +## Install using EXE file +To install via UI, simply double-click on the EXE installer file and follow the instructions in the setup wizard. + +To install via CLI, follow these steps: +1. Choose the features you want to install from the following table: + + | Feature | Description | + |-------------------------|----------------------------------------------------------| + | `FeatureEnvironment` | Update the `PATH` environment variable. (DEFAULT) | + | `FeatureJarFileRunWith` | Associate *.jar* files with Java applications. (DEFAULT) | + | `FeatureJavaHome` | Update the `JAVA_HOME` environment variable. | + | `FeatureOracleJavaSoft` | Updates registry keys `HKLM\SOFTWARE\JavaSoft\`. | + + > [!NOTE] + > You can use `FeatureOracleJavaSoft` to prevent Oracle Java from launching from PATH when the Microsoft Build of OpenJDK is uninstalled. Reinstall Oracle Java if you need to restore the Oracle registry keys. + +2. Run the EXE file from the command line. Use the selected features, as shown in the following example. + + ```cmd + .\.exe /SILENT /SUPPRESSMSGBOXES /ALLUSERS /TASKS="FeatureEnvironment,FeatureJarFileRunWith" /DIR="C:\Program Files\Microsoft\" + ``` + + > [!NOTE] + > If installing for only the current user, use the flag `/CURRENTUSER` instead of `/ALLUSERS`. + > + > To suppress the progress bar screen of the installation, use the flag `/VERYSILENT` instead of `/SILENT`. + > + > The `/DIR` flag is optional. If omitted, the default installation directory is used based on the installation mode: `/ALLUSERS` or `/CURRENTUSER`. + +## Uninstall EXE file +To uninstall the OpenJDK, open your Windows settings and navigate to `Apps > Installed Apps`. Search for the name of the OpenJDK that was installed. Once located, click on the `...` on the right-hand side of the entry and select `Uninstall` from the dropdown menu. A UI uninstaller will appear; follow the remaining instructions. diff --git a/inno_setup/create_exe.template.iss b/inno_setup/create_exe.template.iss new file mode 100644 index 000000000..17630d6e9 --- /dev/null +++ b/inno_setup/create_exe.template.iss @@ -0,0 +1,199 @@ +;------------------------------------------------------------------------------ +; This Inno Setup script file is used to configure the installation process +; for the specified application. It defines the setup parameters, files to be +; installed, registry modifications, shortcuts, and other installation tasks. +; Modify this file to customize the installer behavior and options. +;------------------------------------------------------------------------------ + +; Define useful variables based off inputs +#define OutputDir "output" +#define IniFile '{app}\install_tasks.ini' + +; Include code helper scripts +#include "inno_scripts\install_handler.iss" +#include "inno_scripts\uninstall_handler.iss" +#include "inno_scripts\boolean_checks.iss" + +[Setup] +; For more info, see https://jrsoftware.org/ishelp/index.php?topic=setupsection + +;; Inno settings +#ifdef signFiles +SignTool=signingCommand +#endif +Uninstallable=yes +Compression=lzma +SolidCompression=yes +WizardStyle=modern +; Specify the architectures that the installer supports (e.g., x86compatible, x64os, x64compatible,arm64, etc.) +; See this link to learn more: https://jrsoftware.org/ishelp/index.php?topic=archidentifiers +ArchitecturesAllowed={#ArchitecturesAllowed} +; Ensure correct install dirs by setting the architectures that are 64-bit +ArchitecturesInstallIn64BitMode=win64 +; Notify Windows Explorer that the environment variables have changed +ChangesEnvironment=yes +; Debug +; SetupLogging=yes + +;; App info +AppId={#AppId} +AppName={#AppName} +AppVerName={#AppName} +AppVersion={#ExeProductVersion} +AppPublisher={#Vendor} +AppPublisherURL={#AppPublisherURL} +AppSupportURL={#AppSupportURL} +AppUpdatesURL={#AppUpdatesURL} + +;; Dirs and logos +OutputDir={#OutputDir} +OutputBaseFilename={#OutputFileName} +; Setting default installDir based on the install mode +DefaultDirName={code:GetDefaultDir} +; Enable the user to select the installation directory every time +UsePreviousAppDir=no +; UninstallFilesDir={app}\uninstall +LicenseFile={#LicenseFile} +SetupIconFile={#VendorBrandingLogo} +UninstallDisplayIcon={uninstallexe} +; Add these lines to change the banner images +WizardImageFile={#VendorBrandingDialog} +WizardSmallImageFile={#VendorBrandingSmallIcon} + +;; Dialog settings +DisableDirPage=no +AlwaysShowDirOnReadyPage=yes +DirExistsWarning=auto +; Disables folder selection for start menu entry +DisableProgramGroupPage=yes +DisableWelcomePage=no +UsedUserAreasWarning=no +; Enable the user to select the installation language if one is not detected +ShowLanguageDialog=auto + +;; Privileges settings +; Add these lines to enable installation scope selection +PrivilegesRequired=admin +PrivilegesRequiredOverridesAllowed=dialog +; Enable the user to select the installation mode every time (no means that upgrades will use the same mode as the previous install) +UsePreviousPrivileges=no + +[Tasks] +; For more info, see https://jrsoftware.org/ishelp/index.php?topic=taskssection +Name: "FeatureEnvironment"; Description: "{cm:FeatureEnvironmentDesc}"; GroupDescription: "{cm:FeatureEnvironmentTitle}"; +; AssocFileExtension is an Inno Setup provided translation. This message is translated into every language: &Associate %1 with the %2 file extension +Name: "FeatureJarFileRunWith"; Description: "{cm:AssocFileExtension,{#AppName},.jar}"; GroupDescription: "{cm:FeatureJarFileRunWithTitle}"; +Name: "FeatureJavaHome"; Description: "{cm:FeatureJavaHomeDesc}"; GroupDescription: "{cm:FeatureJavaHomeTitle}"; Flags: unchecked; +; HKLM keys can only be created/modified in Admin Install Mode +Name: "FeatureOracleJavaSoft"; Description: "{cm:FeatureOracleJavaSoftDesc,{#AppName}}"; GroupDescription: "{cm:FeatureOracleJavaSoftTitle}"; Flags: unchecked; Check: IsAdminInstallMode; + +[Files] +; For more info, see https://jrsoftware.org/ishelp/index.php?topic=filessection +Source: "{#SourceFiles}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs sortfilesbyextension sortfilesbyname + +[InstallDelete] +; For more info, see https://jrsoftware.org/ishelp/index.php?topic=installdeletesection +Type: files; Name: "{app}\install_tasks.ini" + +[UninstallDelete] +; For more info, see https://jrsoftware.org/ishelp/index.php?topic=uninstalldeletesection +; This section is needed since uninstall misses the install_tasks.ini file +Type: files; Name: "{app}\install_tasks.ini" +Type: dirifempty; Name: "{app}" + +[Registry] +; For more info, see https://jrsoftware.org/ishelp/index.php?topic=registrysection +; All registry key info translated from current wix/msi installer scripts + +; HKLM = HKEY_LOCAL_MACHINE +; HKCU = HKEY_CURRENT_USER +; HKA changes based on install mode: +; On per machine install = HKLM = HKEY_LOCAL_MACHINE +; On per user install = HKCU = HKEY_CURRENT_USER + +; Always created +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\"; \ + ValueType: none; \ + Flags: uninsdeletekeyifempty; +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}"; \ + ValueType: none; \ + Flags: uninsdeletekey; +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: string; ValueName: "Path"; ValueData: "{app}"; \ + Flags: uninsdeletekey; +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "Main"; ValueData: "1"; \ + Flags: uninsdeletekey; + +; FeatureEnvironment: Add Environment Path keys if the user requests them +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "EnvironmentPath"; ValueData: "1"; \ + Flags: uninsdeletekey; Check: WasTaskSelected('FeatureEnvironment'); +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "EnvironmentPathSetForSystem"; ValueData: "1"; \ + Flags: uninsdeletekey; Check: IsAdminInstallMode and WasTaskSelected('FeatureEnvironment'); +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "EnvironmentPathSetForUser"; ValueData: "1"; \ + Flags: uninsdeletekey; Check: not IsAdminInstallMode and WasTaskSelected('FeatureEnvironment'); + +; FeatureJarFileRunWith: Add .jar file association keys if the user requests them +; Note: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.jar\OpenWithProgids is +; automatically created by Windows when running jar file for the first time +Root: HKA; Subkey: "SOFTWARE\Classes\.jar"; \ + ValueType: string; ValueName: ""; ValueData: "{#Vendor}.jarfile"; \ + Flags: uninsdeletevalue uninsdeletekeyifempty; Check: WasTaskSelected('FeatureJarFileRunWith'); +Root: HKA; Subkey: "SOFTWARE\Classes\.jar"; \ + ValueType: string; ValueName: "Content Type"; ValueData: "application/java-archive"; \ + Flags: uninsdeletevalue uninsdeletekeyifempty; Check: WasTaskSelected('FeatureJarFileRunWith'); +; Creating null keys this way to make sure that they are removed as expected during uninstallation +Root: HKA; Subkey: "SOFTWARE\Classes\{#Vendor}.jarfile"; ValueType: none; Flags: uninsdeletekeyifempty; Check: WasTaskSelected('FeatureJarFileRunWith'); +Root: HKA; Subkey: "SOFTWARE\Classes\{#Vendor}.jarfile\shell"; ValueType: none; Flags: uninsdeletekeyifempty; Check: WasTaskSelected('FeatureJarFileRunWith'); +Root: HKA; Subkey: "SOFTWARE\Classes\{#Vendor}.jarfile\shell\open"; ValueType: none; Flags: uninsdeletekeyifempty; Check: WasTaskSelected('FeatureJarFileRunWith'); +; Two doublequotes (") are used in the ValueName to escape the quotes properly. Example value written to key: "C:\Program Files\Adoptium\jdk-17.0.15.6-hotspot\bin\javaw.exe" -jar "%1" %* +Root: HKA; Subkey: "SOFTWARE\Classes\{#Vendor}.jarfile\shell\open\command"; \ + ValueType: string; ValueName: ""; ValueData: """{app}\bin\javaw.exe"" -jar ""%1"" %*"; \ + Flags: uninsdeletevalue uninsdeletekeyifempty; Check: WasTaskSelected('FeatureJarFileRunWith'); +; TODO: Add HKA keys for JDK8 on x64 (if IcedTeaWeb is bundled) to process .jnlp files (similar to the .jar file handling above). +; OR: decide that EXEs will no longer support JDK8 and remove this TODO + +; FeatureJavaHome: Add JavaHome keys if the user requests them +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "JavaHome"; ValueData: "1"; \ + Flags: uninsdeletekey; Check: WasTaskSelected('FeatureJavaHome'); +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "JavaHomeSetForSystem"; ValueData: "1"; \ + Flags: uninsdeletekey; Check: IsAdminInstallMode and WasTaskSelected('FeatureJavaHome'); +Root: HKA; Subkey: "SOFTWARE\{#Vendor}\{#ProductCategory}\{#ExeProductVersion}\{#JVM}\EXE"; \ + ValueType: dword; ValueName: "JavaHomeSetForUser"; ValueData: "1"; \ + Flags: uninsdeletekey; Check: not IsAdminInstallMode and WasTaskSelected('FeatureJavaHome'); +; Add JAVA_HOME env var for system-level environment variables (admin install) +Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \ + ValueType: string; ValueName: "JAVA_HOME"; ValueData: "{app}"; \ + Flags: uninsdeletevalue; Check: IsAdminInstallMode and WasTaskSelected('FeatureJavaHome'); +; Add JAVA_HOME env var for user-level environment variables (user install) +Root: HKCU; Subkey: "Environment"; \ + ValueType: string; ValueName: "JAVA_HOME"; ValueData: "{app}"; \ + Flags: uninsdeletevalue; Check: not IsAdminInstallMode and WasTaskSelected('FeatureJavaHome'); + +; FeatureOracleJavaSoft: Add JavaSoft Keys if the user requests them +Root: HKLM; Subkey: "SOFTWARE\JavaSoft\{#ProductCategory}"; \ + ValueType: string; ValueName: "CurrentVersion"; ValueData: "{#ProductMajorVersion}"; \ + Flags: uninsdeletekeyifempty; Check: (ShouldUpdateJavaVersion and not IsUninstaller and WasTaskSelected('FeatureOracleJavaSoft')) or (IsUninstaller and WasTaskSelected('FeatureOracleJavaSoft')); +Root: HKLM; Subkey: "SOFTWARE\JavaSoft\{#ProductCategory}\{#ProductMajorVersion}"; \ + ValueType: string; ValueName: "JavaHome"; ValueData: "{app}"; \ + Flags: uninsdeletevalue uninsdeletekeyifempty; Check: WasTaskSelected('FeatureOracleJavaSoft'); +Root: HKLM; Subkey: "SOFTWARE\JavaSoft\{#ProductCategory}\{#ExeProductVersion}"; \ + ValueType: string; ValueName: "JavaHome"; ValueData: "{app}"; \ + Flags: uninsdeletekey; Check: WasTaskSelected('FeatureOracleJavaSoft'); +; The RuntimeLib key is only used by JREs, not JDKs +#if ProductCategory == "JRE" +Root: HKLM; Subkey: "SOFTWARE\JavaSoft\{#ProductCategory}\{#ProductMajorVersion}"; \ + ValueType: string; ValueName: "RuntimeLib"; ValueData: "{app}\bin\server\jvm.dll"; \ + Flags: uninsdeletevalue uninsdeletekeyifempty; Check: WasTaskSelected('FeatureOracleJavaSoft'); +Root: HKLM; Subkey: "SOFTWARE\JavaSoft\{#ProductCategory}\{#ExeProductVersion}"; \ + ValueType: string; ValueName: "RuntimeLib"; ValueData: "{app}\bin\server\jvm.dll"; \ + Flags: uninsdeletekey; Check: WasTaskSelected('FeatureOracleJavaSoft'); +#endif +; TODO: Add HKLM key for JDK8 on x64 (if IcedTeaWeb is bundled) below +; OR: decide that EXEs will no longer support JDK8 and remove this TODO +; Root: HKLM; Subkey: "SOFTWARE\Classes\MIME\Database\Content Type\application/x-java-jnlp-file"; ValueType: string; ValueName: "Extension"; ValueData: ".jnlp"; Flags: uninsdeletevalue; \ No newline at end of file diff --git a/inno_setup/inno_scripts/boolean_checks.iss b/inno_setup/inno_scripts/boolean_checks.iss new file mode 100755 index 000000000..c08738f37 --- /dev/null +++ b/inno_setup/inno_scripts/boolean_checks.iss @@ -0,0 +1,66 @@ +#ifndef BOOLEAN_CHECKS_INCLUDED +#define BOOLEAN_CHECKS_INCLUDED + +#include "get_constants.iss" + +[Code] +// Check if we should update the Java version: +// True if NewMajorVersion > UsersMajorVersion or if UsersMajorVersion == 1.8 (Java 8) +function ShouldUpdateJavaVersion(): Boolean; +var + CurrentVersion: String; + CurrentMajorVersion: Integer; + NewMajorVersion: Integer; +begin + // Convert our new version to integer for comparison + NewMajorVersion := StrToInt('{#ProductMajorVersion}'); + + // Check if the registry key exists + if RegQueryStringValue(HKLM, 'SOFTWARE\JavaSoft\' + '{#ProductCategory}', 'CurrentVersion', CurrentVersion) then + begin + // Special case: Always update if current version is 1.8 + if CurrentVersion = '1.8' then + Result := True + else + begin + // Try to convert the existing version to an integer for comparison + // If conversion fails, the default value of 0 will be used + CurrentMajorVersion := StrToIntDef(CurrentVersion, 0); + + // Update only if our version is higher + Result := NewMajorVersion > CurrentMajorVersion; + end; + end + else + begin + // Key doesn't exist, so we should update + Result := True; + end; +end; + +// Reads local INI file to check if a task was selected during installation +// During installation: INI file is not needed yet, task is equivalent to WizardIsTaskSelected +// During uninstallation: INI file is read to determine keys + env vars to remove +function WasTaskSelected(TaskName: string): Boolean; +var + TaskValue: string; + TaskStateFile: string; +begin + // During installation, use WizardIsTaskSelected + if not IsUninstaller then + Result := WizardIsTaskSelected(TaskName) + else + begin + // During uninstallation, read from INI file + TaskStateFile := ExpandConstant('{#IniFile}'); + if FileExists(TaskStateFile) then + begin + TaskValue := GetIniString('Tasks', TaskName, '0', TaskStateFile); + Result := TaskValue = '1'; + end + else + Result := False; + end; +end; + +#endif \ No newline at end of file diff --git a/inno_setup/inno_scripts/get_constants.iss b/inno_setup/inno_scripts/get_constants.iss new file mode 100755 index 000000000..a0a25463c --- /dev/null +++ b/inno_setup/inno_scripts/get_constants.iss @@ -0,0 +1,40 @@ +#ifndef GET_CONSTANTS_INCLUDED +#define GET_CONSTANTS_INCLUDED + +[Code] +// Returns appropriate registry root based on installation mode +// (returns an int representing an enum) +function GetRegistryRoot(): Integer; +begin + if IsAdminInstallMode then + Result := HKLM + else + Result := HKCU; +end; + +// Returns path to Registry Key that contains environment variable based on installation mode +function GetEnvironmentRegPath(): string; +begin + if IsAdminInstallMode then + Result := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + else + Result := 'Environment'; +end; + +// Returns the default installation directory based on the installation mode and system architecture +function GetDefaultDir(Param: string): string; +var + DefaultProductFolder: string; +begin + // Example: jdk-25.0.1.8-hotspot + DefaultProductFolder := Lowercase(ExpandConstant('{#ProductCategory}')) + '-' + ExpandConstant('{#ExeProductVersion}') + '-' + ExpandConstant('{#JVM}'); + if IsAdminInstallMode then + // Here {commonpf}='C:\Program Files' for x64, aarch64, and x86 Archs since + // 'ArchitecturesInstallIn64BitMode=win64' was set in [Setup] + Result := ExpandConstant('{commonpf}\{#Vendor}\' + DefaultProductFolder) + else + // {userpf}='C:\Users\\AppData\Local\Programs' + Result := ExpandConstant('{userpf}\{#Vendor}\' + DefaultProductFolder); +end; + +#endif diff --git a/inno_setup/inno_scripts/helpers.iss b/inno_setup/inno_scripts/helpers.iss new file mode 100644 index 000000000..e366d56f0 --- /dev/null +++ b/inno_setup/inno_scripts/helpers.iss @@ -0,0 +1,159 @@ +#ifndef HELPERS_INCLUDED +#define HELPERS_INCLUDED + +[Code] + +// Replace OldSubstring with the specified NewSubstring in InputString +function ReplaceSubstring(InputString: string; OldSubstring: string; NewSubstring: string): string; +begin + // For info on StringChangeEx: https://jrsoftware.org/ishelp/index.php?topic=isxfunc_stringchangeex + Result := InputString; + StringChangeEx(Result, OldSubstring, NewSubstring, True); +end; + +// Compare two version strings in X.X.X.X format +// Returns: -1 if Version1 < Version2, 0 if equal, 1 if Version1 > Version2 +function CompareVersions(Version1: string; Version2: string): Integer; +var + V1Parts, V2Parts: TStringList; + i, MaxLen, Part1, Part2: Integer; +begin + Result := 0; + V1Parts := TStringList.Create; + V2Parts := TStringList.Create; + try + // Split versions by '.' + V1Parts.Delimiter := '.'; + V1Parts.DelimitedText := Version1; + V2Parts.Delimiter := '.'; + V2Parts.DelimitedText := Version2; + + // We do not have a Max() function available to us, so we do it manually + if V1Parts.Count > V2Parts.Count then + MaxLen := V1Parts.Count + else + MaxLen := V2Parts.Count; + + // Compare each part + for i := 0 to MaxLen do + begin + // Get the part of V1 as integer (default to 0 if not present) + if i < V1Parts.Count then + Part1 := StrToIntDef(V1Parts[i], 0) + else + Part1 := 0; + + // Get the part of V2 as integer (default to 0 if not present) + if i < V2Parts.Count then + Part2 := StrToIntDef(V2Parts[i], 0) + else + Part2 := 0; + + // Compare the part + if Part1 < Part2 then + begin + Result := -1; + Exit; + end + else if Part1 > Part2 then + begin + Result := 1; + Exit; + end; + end; + finally + V1Parts.Free; + V2Parts.Free; + end; +end; + +// Switches each pair of characters in the string +// This is needed to programmatically "reverse" MSI GUID segments to find its mapping to the Product Upgrade Code +// Example: "A1B2C3" becomes "1A2B3C" +// Note: All MSI GUID segments have even lengths, so no need to handle odd-length strings +function ReversePairs(const s: string): string; +var + i: Integer; +begin + Result := ''; + i := 1; + while i <= Length(s) do + begin + Result := Result + s[i+1] + s[i]; + i := i + 2; + end; +end; + +// Reverses the order of characters in the string +// This is needed to programmatically "reverse" MSI GUID segments to find its mapping to the Product Upgrade Code +// Example: "ABCDEF" becomes "FEDCBA" +function ReverseString(const s: string): string; +var + i: Integer; +begin + Result := ''; + for i := Length(s) downto 1 do + Result := Result + s[i]; +end; + +// Reverses an MSI PRODUCT_UPGRADE_CODE to standard GUID format and vice versa +// Needed for determining the mapping between MSI PRODUCT_UPGRADE_CODE and MSI Product Codes in the registry +function ReverseMsiGuid(const RevCode: string; AddFormatting: Boolean): string; +var + RevCodePlain, part1, part2, part3, part4, part5: string; +begin + // Remove hyphens if present + // Revcodes stored in '\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes' do not have hyphens + RevCodePlain := ReplaceSubstring(RevCode, '-', ''); + // Remove braces if present -- same issue as above + RevCodePlain := ReplaceSubstring(RevCodePlain, '{', ''); + RevCodePlain := ReplaceSubstring(RevCodePlain, '}', ''); + + // Break into segments + part1 := Copy(RevCodePlain, 1, 8); + part2 := Copy(RevCodePlain, 9, 4); + part3 := Copy(RevCodePlain, 13, 4); + part4 := Copy(RevCodePlain, 17, 4); + part5 := Copy(RevCodePlain, 21, 12); + + // This is the process that Windows Installer uses to map UpgradeCodes to ProductCodes + + // Reverse the first three parts by character + part1 := ReverseString(part1); + part2 := ReverseString(part2); + part3 := ReverseString(part3); + + // Reverse the final two parts by pair + part4 := ReversePairs(part4); + part5 := ReversePairs(part5); + + // Combine into GUID format + if AddFormatting then + Result := '{' + part1 + '-' + part2 + '-' + part3 + '-' + part4 + '-' + part5 + '}' + else + Result := part1 + part2 + part3 + part4 + part5; +end; + +// Returns true if an MSI installation with the given PRODUCT_UPGRADE_CODE exists in the specified RegistryRoot, +// and sets MsiGuid to the corresponding mapping if found. Otherwise, returns false. +function GetInstalledMsiGuid(RegistryRoot: Integer; UpgradeCode: string; var MsiGuid: string): Boolean; +var + ValueNames: TArrayOfString; + i: Integer; +begin + if RegGetValueNames(RegistryRoot, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\' + ReverseMsiGuid(UpgradeCode, False), ValueNames) then + begin + for i := 0 to GetArrayLength(ValueNames)-1 do + begin + if ValueNames[i] <> '' then // skip empty or default + begin + MsiGuid := ReverseMsiGuid(ValueNames[i], True); + Result := True; + Exit; + end; + end; + end; + Result := False; +end; + +#endif \ No newline at end of file diff --git a/inno_setup/inno_scripts/install_handler.iss b/inno_setup/inno_scripts/install_handler.iss new file mode 100755 index 000000000..79918c92c --- /dev/null +++ b/inno_setup/inno_scripts/install_handler.iss @@ -0,0 +1,271 @@ +#ifndef INSTALL_HANDLER_INCLUDED +#define INSTALL_HANDLER_INCLUDED + +#include "helpers.iss" +#include "get_constants.iss" +#include "boolean_checks.iss" + +[Code] +// Store the user's task choices + metadata in an INI file in the installation directory +// This allows us to read the task selections during uninstallation +procedure StoreTaskSelections(TaskName: string); +var + TaskStateFile: string; +begin + // Store each task selection state in an INI file during installation + TaskStateFile := ExpandConstant('{#IniFile}'); + + // Add metadata to the INI file only if TaskName is 'METADATA' + if TaskName = 'METADATA' then + begin + SetIniString('Metadata', 'Publisher', ExpandConstant('{#Vendor}'), TaskStateFile); + SetIniString('Metadata', 'InstallDate', GetDateTimeString('yyyy/mm/dd hh:nn:ss', #0, #0), TaskStateFile); + SetIniString('Metadata', 'Version', ExpandConstant('{#ExeProductVersion}'), TaskStateFile); + end + // Create the INI file with task selections + else if WizardIsTaskSelected(TaskName) then + SetIniString('Tasks', TaskName, '1', TaskStateFile) + else + SetIniString('Tasks', TaskName, '0', TaskStateFile); +end; + +// Add the JDK to either the System or User PATH environment variable. +procedure AddToPath(AppBinPath: string; EnvRegKey: string; RegRoot: Integer); +var + UserPath: string; +begin + // Read current PATH + if not RegQueryStringValue(RegRoot, EnvRegKey, 'PATH', UserPath) then + UserPath := ''; + + // Check if our path entry is already in PATH (returns 0 if not found) + if Pos(AppBinPath, UserPath) = 0 then + begin + // Prepend our path entry to PATH + if UserPath <> '' then + UserPath := AppBinPath + ';' + UserPath + else + // If PATH is empty, just set it to our path entry + UserPath := AppBinPath; + + // Write back to registry + RegWriteStringValue(RegRoot, EnvRegKey, 'PATH', UserPath); + end; +end; + +// Uninstall the previous version of the same openJDK package if it exists +// Logs if /LOG is passed into compile cli, or if SetupLogging=yes in [Setup] section +// Without a specified log location, logs to: '%TEMP%\Setup Log YYYY-MM-DD #001.txt' by default +procedure UninstallPreviousInstallation(); +var + UninstallKeyExe: string; + MsiGuid: string; + UninstallString: string; + ResultCode: Integer; + DisplayName: string; + RegistryRoots: array[0..1] of Integer; + RootNames: array[0..1] of string; + i: Integer; + CurrentRoot: Integer; + RootName: string; +begin + // Initialize arrays for registry roots and their names + RegistryRoots[0] := HKLM; + RegistryRoots[1] := HKCU; + RootNames[0] := 'system'; + RootNames[1] := 'user'; + + // All EXE uninstall strings are stored here, regardless of vendor + UninstallKeyExe := ExpandConstant('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{#AppID}_is1'); + + // Loop through both HKLM and HKCU + for i := 0 to 1 do + begin + CurrentRoot := RegistryRoots[i]; + RootName := RootNames[i]; + + // Check for EXE uninstaller. If found, var UninstallString is assigned + if RegQueryStringValue(CurrentRoot, UninstallKeyExe, 'UninstallString', UninstallString) then + begin + if RegQueryStringValue(CurrentRoot, UninstallKeyExe, 'DisplayName', DisplayName) then + begin + Log('Found previous ' + RootName + ' installation: ' + DisplayName); + end; + Log('Uninstall string (with quotes): ' + UninstallString); + Log('Uninstall string (quotes removed): ' + RemoveQuotes(UninstallString)); + + // Run the uninstaller silently (the Uninstall string has quotes that we need to remove) + if Exec(RemoveQuotes(UninstallString), '/VERYSILENT /NORESTART', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + Log('Previous ' + RootName + ' installation uninstalled successfully. Result code: ' + IntToStr(ResultCode)); + end + else + begin + Log('Failed to uninstall previous ' + RootName + ' installation. Result code: ' + IntToStr(ResultCode)); + end; + end; + + // Check for MSI uninstaller. If found, var MsiGuid is assigned + if GetInstalledMsiGuid(CurrentRoot, ExpandConstant('{#AppID}'), MsiGuid) then + begin + Log('Found installed MSI: ' + MsiGuid); + + // Uninstall the MSI silently - try with elevation if needed + if not Exec('MsiExec.exe', '/x ' + MsiGuid + ' /qn /norestart', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode = 1603) then + begin + Log('MSI uninstall failed or requires elevation. Result code: ' + IntToStr(ResultCode) + '. Attempting with elevation.'); + // Try with elevated permissions using ShellExec + // 'runas' verb to request elevation. Allowed verbs documented at: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea#parameters + if ShellExec('runas', 'MsiExec.exe', '/x ' + MsiGuid + ' /qn /norestart', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + Log('Previous ' + RootName + ' MSI installation uninstalled successfully with elevation. Result code: ' + IntToStr(ResultCode)); + end + else + begin + Log('Failed to uninstall previous ' + RootName + ' MSI installation even with elevation. Result code: ' + IntToStr(ResultCode)); + end; + end + else + begin + Log('Previous ' + RootName + ' MSI installation uninstalled successfully. Result code: ' + IntToStr(ResultCode)); + end; + end; + + end; +end; + +// This function defines installation logic at each step of the installation process: +// ssInstall - just before the actual installation starts +// ssPostInstall - just after the actual installation finishes +// ssDone - just before Setup terminates after a successful install +// For more info, see the CurStepChanged section in https://jrsoftware.org/ishelp/index.php?topic=scriptevents +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssInstall then + begin + // Uninstall existing JDK of the same LTS if it exists + UninstallPreviousInstallation(); + end + // Store task selections just after the actual installation finishes but before registry entries are created + else if CurStep = ssPostInstall then + begin + StoreTaskSelections('FeatureEnvironment'); + StoreTaskSelections('FeatureJarFileRunWith'); + StoreTaskSelections('FeatureJavaHome'); + StoreTaskSelections('FeatureOracleJavaSoft'); + StoreTaskSelections('METADATA'); + + // Add {app}\bin to PATH only if the user requested it + if WasTaskSelected('FeatureEnvironment') then + AddToPath(ExpandConstant('{app}\bin'), GetEnvironmentRegPath(), GetRegistryRoot()); + end; +end; + +// As the EXE installer initializes, compare the version being installed with any existing version +// If an existing version is newer, abort installation with an error message +// For more info, see https://jrsoftware.org/ishelp/index.php?topic=scriptevents +function InitializeSetup(): Boolean; +var + UninstallKeyExe: string; + DisplayVersion: string; + DisplayName: string; + RegistryRoots: array[0..1] of Integer; + i: Integer; + CurrentRoot: Integer; + VersionComparison: Integer; + MsgBoxString: string; + MsiGuid: string; +begin + Result := True; + + // Initialize arrays for registry roots and their names + RegistryRoots[0] := HKLM; + RegistryRoots[1] := HKCU; + + // All EXE info is stored here, regardless of vendor + UninstallKeyExe := ExpandConstant('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{#AppID}_is1'); + + // Loop through both HKLM and HKCU + for i := 0 to 1 do + begin + CurrentRoot := RegistryRoots[i]; + + // Check if a previous version was installed via EXE and compare + if RegQueryStringValue(CurrentRoot, UninstallKeyExe, 'DisplayVersion', DisplayVersion) then + begin + RegQueryStringValue(CurrentRoot, UninstallKeyExe, 'DisplayName', DisplayName); + Log('Previous version to uninstall: ' + DisplayVersion); + Log('Previous version display name: ' + DisplayName); + + // Compare versions and exit if existing version is higher + // Note: see helpers.iss for CompareVersions() function + VersionComparison := CompareVersions(DisplayVersion, ExpandConstant('{#ExeProductVersion}')); + if VersionComparison > 0 then + begin + // This message (translated into all languages supported by Inno Setup), reads: + // The existing file is newer than the one Setup is trying to install. version {Existing_ExeProductVersion} > version {Installer_ExeProductVersion} + // Example: + // The existing file is newer than the one Setup is trying to install. version 25.0.1.8 > version 25.0.0.36 + MsgBoxString := SetupMessage(msgExistingFileNewer2) + + FmtMessage(CustomMessage('NameAndVersion'), ['', DisplayVersion]) + + ' >' + + FmtMessage(CustomMessage('NameAndVersion'), ['', ExpandConstant('{#ExeProductVersion}')]); + // For info on MsgBox(), see https://jrsoftware.org/ishelp/index.php?topic=isxfunc_msgbox + MsgBox(MsgBoxString, mbError, MB_OK); + Log('Newer version detected. Exiting installation.'); + Result := False; + Exit; + end + else if VersionComparison = 0 then + begin + // This message (translated into all languages supported by Inno Setup), reads: + // Setup is preparing to install {APP_NAME} on your computer. The file already exists. Overwrite the existing file? + // Example: + // Setup is preparing to install Eclipse Temurin JDK with Hotspot 25.0.1+8 (x64) on your computer. The file already exists. Overwrite the existing file? + MsgBoxString := ReplaceSubstring(SetupMessage(msgPreparingDesc), '[name]', ExpandConstant('{#AppName}')) + + ' ' + SetupMessage(msgFileExists2) + + ' ' + ReplaceSubstring(SetupMessage(msgFileExistsOverwriteExisting), '&', '') + '?'; + // For info on SuppressibleMsgBox(), see https://jrsoftware.org/ishelp/index.php?topic=isxfunc_suppressiblemsgbox + if SuppressibleMsgBox(MsgBoxString, mbInformation, MB_YESNO, IDYES) = IDYES then + begin + Log('Same version detected: "' + DisplayVersion + '". Proceeding with reinstallation.'); + // Exit here since we do not need to ask the user again if they want to overwrite older installations + Exit; + end + else + begin + Log('User chose not to reinstall same version.'); + Result := False; + Exit; + end; + end; + end; + + // Check if a previous version was installed via MSI + if GetInstalledMsiGuid(CurrentRoot, ExpandConstant('{#AppID}'), MsiGuid) then + begin + // This message (translated into all languages supported by Inno Setup), reads: + // Setup is preparing to install {APP_NAME} on your computer. The file already exists. Overwrite the existing file? + // Example: + // Setup is preparing to install Eclipse Temurin JDK with Hotspot 25.0.1+8 (x64) on your computer. The file already exists. Overwrite the existing file? + MsgBoxString := ReplaceSubstring(SetupMessage(msgPreparingDesc), '[name]', ExpandConstant('{#AppName}')) + + ' ' + SetupMessage(msgFileExists2) + + ' ' + ReplaceSubstring(SetupMessage(msgFileExistsOverwriteExisting), '&', '') + '?'; + // For info on SuppressibleMsgBox(), see https://jrsoftware.org/ishelp/index.php?topic=isxfunc_suppressiblemsgbox + if SuppressibleMsgBox(MsgBoxString, mbInformation, MB_YESNO, IDYES) = IDYES then + begin + Log('Legacy MSI version detected. Proceeding with overwriting.'); + Exit; + end + else + begin + Log('User chose not to overwrite legacy version.'); + Result := False; + Exit; + end; + end; + + end; +end; + +#endif \ No newline at end of file diff --git a/inno_setup/inno_scripts/uninstall_handler.iss b/inno_setup/inno_scripts/uninstall_handler.iss new file mode 100755 index 000000000..4fd86942f --- /dev/null +++ b/inno_setup/inno_scripts/uninstall_handler.iss @@ -0,0 +1,125 @@ +#ifndef UNINSTALL_HANDLER_INCLUDED +#define UNINSTALL_HANDLER_INCLUDED + +#include "get_constants.iss" +#include "boolean_checks.iss" + +[Code] +procedure RemoveFromPath(AppBinPath: string; EnvRegKey: string; RegRoot: Integer); +var + UserPath: string; + PathEntries: TArrayOfString; + i: Integer; + NewPath: string; +begin + // Read current PATH + if not RegQueryStringValue(RegRoot, EnvRegKey, 'PATH', UserPath) then + Exit; + + // Split PATH into individual entries, excluding empty entries + PathEntries := StringSplit(UserPath, [';'], stExcludeEmpty); + NewPath := ''; + + // Rebuild PATH without our entry + for i := 0 to GetArrayLength(PathEntries) - 1 do + begin + if PathEntries[i] <> AppBinPath then + begin + if NewPath <> '' then + NewPath := NewPath + ';' + PathEntries[i] + else + // Initialize NewPath with the first valid entry + NewPath := PathEntries[i]; + end; + end; + + // Write back to registry + RegWriteStringValue(RegRoot, EnvRegKey, 'PATH', NewPath); +end; + +// Set registry key HKLM "SOFTWARE\JavaSoft\{#ProductCategory}" to either: +// 1. The name of the subkey that is the highest LTS integer (with FeatureOracleJavaSoft, ignore subkeys that are not integers) under "SOFTWARE\JavaSoft\{#ProductCategory}" +// 2. Delete the value if no other versions are found +procedure SetHighestJavaVersionRemaining(); +var + SubKeys: TArrayOfString; + i: Integer; + MaxVersion: Integer; + CurrentVersion: Integer; + MaxVersionStr: string; + CurrentVersionStr: string; +begin + // Initialize max version + MaxVersion := -1; + MaxVersionStr := ''; + + // Check that the JavaSoft registry key and corresponding CurrentVersion value exist + if not RegQueryStringValue(HKLM, ExpandConstant('SOFTWARE\JavaSoft\{#ProductCategory}'), 'CurrentVersion', CurrentVersionStr) then + begin + // Key or value does not exist, so nothing to do + Exit; + end + else + begin + if CurrentVersionStr <> ExpandConstant('{#ProductMajorVersion}') then + begin + // Current version does not match our installed version, so no need to update + Exit; + end; + end; + + // Get subkeys under "SOFTWARE\JavaSoft\{#ProductCategory}" + if RegGetSubKeyNames(HKLM, ExpandConstant('SOFTWARE\JavaSoft\{#ProductCategory}'), SubKeys) then + begin + // Iterate through subkeys to find the highest integer LTS JDK version installed (with FeatureOracleJavaSoft) + for i := 0 to GetArrayLength(SubKeys) - 1 do + begin + CurrentVersion := StrToIntDef(SubKeys[i], -1); + if (CurrentVersion > MaxVersion) and (SubKeys[i] <> ExpandConstant('{#ProductMajorVersion}')) then + begin + MaxVersion := CurrentVersion; + MaxVersionStr := SubKeys[i]; + end; + end; + // Set or delete the CurrentVersion value based on the max version found + if MaxVersion > 0 then + begin + // Set CurrentVersion to the highest LTS version still on the User's system (with FeatureOracleJavaSoft) + RegWriteStringValue(HKLM, ExpandConstant('SOFTWARE\JavaSoft\{#ProductCategory}'), 'CurrentVersion', MaxVersionStr); + end + else + begin + // No JDKs with FeatureOracleJavaSoft remaining on the user's system, so delete the RegistryKey + RegDeleteValue(HKLM, ExpandConstant('SOFTWARE\JavaSoft\{#ProductCategory}'), 'CurrentVersion'); + // Delete the remaining JavaSoft keys if empty + RegDeleteKeyIfEmpty(HKLM, ExpandConstant('SOFTWARE\JavaSoft\{#ProductCategory}')); + RegDeleteKeyIfEmpty(HKLM, ExpandConstant('SOFTWARE\JavaSoft')); + end; + end; +end; + + +// This function defines uninstallation logic at each step of the uninstallation process: +// usUninstall - just before the actual uninstallation starts +// usPostUninstall - just after the actual uninstallation finishes +// usDone - just before process terminates after a successful uninstall +// For more info, see the CurStepChanged and TUninstallStep sections in https://jrsoftware.org/ishelp/index.php?topic=scriptevents +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + // Action is performed right before uninstallation starts + if CurUninstallStep = usUninstall then + begin + // Remove {app}\bin from PATH only if added during installation + if WasTaskSelected('FeatureEnvironment') then + begin + RemoveFromPath(ExpandConstant('{app}\bin'), GetEnvironmentRegPath(), GetRegistryRoot()); + end; + + if WasTaskSelected('FeatureOracleJavaSoft') then + begin + SetHighestJavaVersionRemaining(); + end; + end; +end; + +#endif \ No newline at end of file diff --git a/inno_setup/licenses/license-GPLv2+CE.en-us.rtf b/inno_setup/licenses/license-GPLv2+CE.en-us.rtf new file mode 100644 index 000000000..2bfa40bb0 --- /dev/null +++ b/inno_setup/licenses/license-GPLv2+CE.en-us.rtf @@ -0,0 +1,145 @@ +{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1031\deflangfe1031{\fonttbl{\f0\fnil\fcharset0 Consolas;}} +{\*\generator Riched20 10.0.17134}{\*\mmathPr\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 +\pard\nowidctlpar\f0\fs17 GNU GENERAL PUBLIC LICENSE\par +Version 2, June 1991 \par +\par +Copyright (C) 1989, 1991 Free Software Foundation, Inc. \par +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA\par +\par +Everyone is permitted to copy and distribute verbatim copies\par +of this license document, but changing it is not allowed.\par +\par +Preamble\par +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. \par +\par +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. \par +\par +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. \par +\par +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. \par +\par +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. \par +\par +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. \par +\par +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. \par +\par +The precise terms and conditions for copying, distribution and modification follow. \par +\par +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\par +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". \par +\par +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. \par +\par +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. \par +\par +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. \par +\par +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: \par +\par +\par +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. \par +\par +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. \par +\par +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) \par +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. \par +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. \par +\par +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. \par +\par +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: \par +\par +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, \par +\par +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, \par +\par +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) \par +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. \par +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. \par +\par +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. \par +\par +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. \par +\par +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. \par +\par +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. \par +\par +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. \par +\par +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. \par +\par +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. \par +\par +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. \par +\par +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. \par +\par +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. \par +\par +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. \par +\par +NO WARRANTY\par +\par +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. \par +\par +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. \par +\par +\par +END OF TERMS AND CONDITIONS\par +\lang7\par +How to Apply These Terms to Your New Programs\par +\par +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.\par +\par +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.\par +\par + +\pard\nowidctlpar\li720 One line to give the program's name and a brief idea of what it does.\par +\par +Copyright (C) \par +\par +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.\par +\par +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par +\par +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\par + +\pard\nowidctlpar\par +Also add information on how to contact you by electronic and paper mail.\par +\par +If the program is interactive, make it output a short notice like this when it starts in an interactive mode:\par +\par + +\pard\nowidctlpar\li720 Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free software, and you are welcome to redistribute it under certain conditions; type 'show c' for details.\par + +\pard\nowidctlpar\par +The hypothetical commands 'show w' and 'show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than 'show w' and 'show c'; they could even be mouse-clicks or menu items--whatever suits your program.\par +\par +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:\par +\par + +\pard\nowidctlpar\li720 Yoyodyne, Inc., hereby disclaims all copyright interest in the program 'Gnomovision' (which makes passes at compilers) written by James Hacker.\par +\par +signature of Ty Coon, 1 April 1989\par +\par +Ty Coon, President of Vice\par + +\pard\nowidctlpar\par +This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.\par +\par +\par +"CLASSPATH" EXCEPTION TO THE GPL\par +\par +Certain source files distributed by Oracle America and/or its affiliates are subject to the following clarification and special exception to the GPL, but only where Oracle has expressly included in the particular source file's header the words "Oracle designates this particular file as subject to the "Classpath" exception as provided by Oracle in the LICENSE file that accompanied this code."\par +\par + +\pard\nowidctlpar\li720 Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination.\par +\par +As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version.\par + +\pard\nowidctlpar\par +\par +} + \ No newline at end of file diff --git a/inno_setup/logos/logo-small.png b/inno_setup/logos/logo-small.png new file mode 100644 index 000000000..dc2530a68 Binary files /dev/null and b/inno_setup/logos/logo-small.png differ diff --git a/inno_setup/logos/logo.ico b/inno_setup/logos/logo.ico new file mode 100644 index 000000000..0ef2318fe Binary files /dev/null and b/inno_setup/logos/logo.ico differ diff --git a/inno_setup/logos/welcome-dialog.png b/inno_setup/logos/welcome-dialog.png new file mode 100644 index 000000000..fa0dca1ff Binary files /dev/null and b/inno_setup/logos/welcome-dialog.png differ diff --git a/inno_setup/ps_scripts/Helpers.ps1 b/inno_setup/ps_scripts/Helpers.ps1 new file mode 100644 index 000000000..44d9513a8 --- /dev/null +++ b/inno_setup/ps_scripts/Helpers.ps1 @@ -0,0 +1,168 @@ +<# +.SYNOPSIS + Helper functions for OpenJDK EXE packaging scripts. + +.DESCRIPTION + This script provides utility functions for validating input, downloading files, unzipping archives, and handling signing parameters. + Intended for use in the OpenJDK EXE installer build process via Inno Setup. + +.NOTES + File Name: Helpers.ps1 + +#> + +function ValidateZipFileInput { + param ( + [string]$ZipFilePath, + [string]$ZipFileUrl + ) + if (-not $ZipFilePath -and -not $ZipFileUrl) { + throw "Error: You must provide either -ZipFilePath or -ZipFileUrl." + } + elseif ($ZipFilePath -and $ZipFileUrl) { + throw "Error: You cannot provide both -ZipFilePath and -ZipFileUrl." + } + else { + Write-Host "ZipFile input validation passed." + } +} + +function GetArchitectureAllowedTemplateInput { + param ( + [string]$Arch + ) + if ($Arch -eq "x86" -or $Arch -like "*32*" -or $Arch -eq "all") { + return "x86compatible" + } + elseif ($Arch -eq "x64") { + return "x64compatible" + } + elseif ($Arch -like "arm*" -or $Arch -eq "aarch64") { + return "arm64" + } + else { + throw "Error: Unsupported architecture '$Arch'. Supported architectures are: x86, x86_32, x64, arm, arm64, aarch64, or all." + } +} + +function SetDefaultIfEmpty { + param ( + [string]$InputValue, + [string]$DefaultValue + ) + if (-not $InputValue) { + return $DefaultValue + } + else { + return $InputValue + } +} + +function DownloadFileFromUrl { + param ( + [string]$Url, + [string]$DestinationDirectory + ) + if (-not (Test-Path -Path $DestinationDirectory)) { + New-Item -ItemType Directory -Path $DestinationDirectory | Out-Null + } + $fileName = [System.IO.Path]::GetFileName($Url) + $downloadPath = Join-Path -Path $DestinationDirectory -ChildPath $fileName + + Write-Host "Downloading file from $Url to $DestinationDirectory" + + # Download zip file (needs to be silent or it will print the progress bar and take ~30 times as long to download) + $OriginalLocalProgressPreference = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $Url -OutFile $downloadPath + $ProgressPreference = $OriginalLocalProgressPreference + + return $downloadPath +} + +function UnzipFile { + param ( + [string]$ZipFilePath, + [string]$DestinationPath + ) + if (-not (Test-Path -Path $ZipFilePath)) { + throw "Error: Zip file not found at path: $ZipFilePath" + } + if (-not (Test-Path -Path $DestinationPath)) { + New-Item -ItemType Directory -Path $DestinationPath | Out-Null + } + Write-Host "Unzipping file $ZipFilePath to $DestinationPath" + + # Unzip file (needs to be silent or it will print the progress bar and take much longer) + $OriginalProgressPreference = $global:ProgressPreference + $global:ProgressPreference = 'SilentlyContinue' + Expand-Archive -Path $ZipFilePath -DestinationPath $DestinationPath -Force + $global:ProgressPreference = $OriginalProgressPreference +} + +function CheckForError { + param ( + [string]$ErrorMessage + ) + if ($LASTEXITCODE -ne 0) { + throw "Last exit code: $LASTEXITCODE. Error: $ErrorMessage" + } +} + +function CapitalizeString { + param ( + [Parameter(Mandatory = $true)] + [string]$InputString, + [Parameter(Mandatory = $false)] + [switch]$AllLetters + ) + + if ([string]::IsNullOrEmpty($InputString)) { + return $InputString + } + + if ($AllLetters) { + # Capitalize all letters (uppercase) + return $InputString.ToUpper() + } + else { + # Capitalize only the first letter + return $InputString.Substring(0, 1).ToUpper() + $InputString.Substring(1).ToLower() + } +} + +function Clear-TargetFolder { + param( + [string]$TargetFolder, + [string]$ExcludeSubfolder = $null + ) + if (-not (Test-Path -Path $TargetFolder)) { + New-Item -ItemType Directory -Path $TargetFolder | Out-Null + Write-Host "Created folder: $TargetFolder" + } + + if ($ExcludeSubfolder) { + Get-ChildItem -Path $TargetFolder -Recurse | Where-Object { + $_.FullName -notlike "*\$ExcludeSubfolder*" + } | Remove-Item -Recurse -Force + Write-Host "Cleaned $TargetFolder, excluding $ExcludeSubfolder." + } + else { + Get-ChildItem -Path $TargetFolder -Recurse | Remove-Item -Recurse -Force + Write-Host "Cleaned $TargetFolder." + } + + return $TargetFolder +} + +function GenerateGuidFromString { + param( + [string] $SeedString = "" + ) + # This is the same function used for the WIX upgrade code generation + $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider + $utf8 = new-object -TypeName System.Text.UTF8Encoding + $hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($SeedString))) -replace "-", "" + $guid = [System.Guid]::Parse($hash) + Write-Output $guid.ToString('b').ToUpper() +} \ No newline at end of file diff --git a/inno_setup/translations/default.iss b/inno_setup/translations/default.iss new file mode 100755 index 000000000..7d752ec8d --- /dev/null +++ b/inno_setup/translations/default.iss @@ -0,0 +1,90 @@ +; This file contains translations for all text in the resulting EXE installer. +; [Languages]: contains the list of languages we support. Inno Setup uses the compiler to translate default installer text. +; [CustomMessages]: contains the list of translations for the custom tasks that the user can select during installation. + +[Languages] +Name: "English"; MessagesFile: "compiler:Default.isl" +Name: "German"; MessagesFile: "compiler:Languages\German.isl" +Name: "Spanish"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "French"; MessagesFile: "compiler:Languages\French.isl" +Name: "Japanese"; MessagesFile: "compiler:Languages\Japanese.isl" + +#ifdef INCLUDE_UNOFFICIAL_TRANSLATIONS + +; Note: ChineseTW and ChineseCN still need translations for certain progress-bar screen messages +Name: "ChineseTW"; MessagesFile: "compiler:Languages\Unofficial\ChineseTraditional.isl" +Name: "ChineseCN"; MessagesFile: "compiler:Languages\Unofficial\ChineseSimplified.isl" + +#endif + +[CustomMessages] +; Notes: +; 1) All translations in the initial PR were pulled directly from the wix MSI installer translation files (Jul 2025) +; 2) Any missing translations will default to English +; 3) When testing translations, the `Yes` and `No` buttons will always be in the language of the user's machine (regardless of the selected language in the installer) +; 4) All commented out translations were made by AI and need to be verified before using/uncommenting +; 5) Translation entries allow us to have input arguments. They are specified as %1, %2, etc. + +; For a list of default custom messages (Translated by Inno Setup), see: https://jrsoftware.org/ishelp/index.php?topic=custommessagessection +; Example: we are using AssocFileExtension from this list + +; Custom task descriptions - English (default) +FeatureEnvironmentDesc=Modify PATH environment variable by prepending the JDK installation directory to the beginning of PATH. +FeatureJavaHomeDesc=Sets or overrides JAVA_HOME environment variable with the JDK installation directory. +FeatureOracleJavaSoftDesc=Overwrites Oracle's reg key HKLM\Software\JavaSoft. After uninstallation of %1, Oracle Java needs to be reinstalled to re-create these registry keys. +FeatureEnvironmentTitle=Modify PATH variable +FeatureJavaHomeTitle=Set or override JAVA_HOME variable +FeatureJarFileRunWithTitle=Associate .jar +FeatureOracleJavaSoftTitle=JavaSoft (Oracle) registry keys + +German.FeatureEnvironmentDesc=In die PATH-Umgebungsvariable einfügen. +German.FeatureJavaHomeDesc=Als JAVA_HOME-Umgebungsvariable verwenden. +; German.FeatureOracleJavaSoftDesc=Überschreibt Oracles Registrierungsschlüssel HKLM\Software\JavaSoft. Nach der Deinstallation von %1 muss Oracle Java neu installiert werden, um diese Registrierungsschlüssel wiederherzustellen. +German.FeatureEnvironmentTitle=Zum PATH hinzufügen +German.FeatureJavaHomeTitle=JAVA_HOME-Variable konfigurieren +; German.FeatureJarFileRunWithTitle=.jar-Datei verknüpfen +; German.FeatureOracleJavaSoftTitle=JavaSoft (Oracle) Registrierungsschlüssel + +Spanish.FeatureEnvironmentDesc=Añadir a la variable de entorno PATH. +Spanish.FeatureJavaHomeDesc=Establecer la variable de entorno JAVA_HOME. +Spanish.FeatureOracleJavaSoftDesc=Sobrescribir las claves de registro HKLM\Software\JavaSoft (Oracle). Si se desinstala %1, la ejecución de Oracle Java desde la ruta "C:\Program Files (x86)\Common Files\Oracle\Java\javapath" no funcionará. Será necesario reinstalarlo. +Spanish.FeatureEnvironmentTitle=Añadir al PATH +Spanish.FeatureJavaHomeTitle=Establecer la variable JAVA_HOME +Spanish.FeatureJarFileRunWithTitle=Asociar .jar +Spanish.FeatureOracleJavaSoftTitle=Claves de registro JavaSoft (Oracle) + +French.FeatureEnvironmentDesc=Ajouter à la variable d'environnement PATH. +French.FeatureJavaHomeDesc=Définir la variable d'environnement JAVA_HOME. +French.FeatureOracleJavaSoftDesc=Écrase les clés de registre HKLM\Software\JavaSoft (Oracle). Après la désinstallation d'%1, Oracle Java lancé depuis le PATH "C:\Program Files (x86)\Common Files\Oracle\Java\javapath" ne fonctionne plus. Réinstaller Oracle Java si besoin +French.FeatureEnvironmentTitle=Ajouter au PATH +French.FeatureJavaHomeTitle=Définir la variable JAVA_HOME +French.FeatureJarFileRunWithTitle=Associer les .jar +French.FeatureOracleJavaSoftTitle=Clés de registre JavaSoft (Oracle) + +; Japanese.FeatureEnvironmentDesc=JDKインストールディレクトリをPATHの先頭に追加してPATH環境変数を変更します。 +; Japanese.FeatureJavaHomeDesc=JDKインストールディレクトリでJAVA_HOME環境変数を設定または上書きします。 +; Japanese.FeatureOracleJavaSoftDesc=OracleのレジストリキーHKLM\Software\JavaSoftを上書きします。%1のアンインストール後、これらのレジストリキーを再作成するにはOracle Javaの再インストールが必要です。 +; Japanese.FeatureEnvironmentTitle=PATH変数を変更 +; Japanese.FeatureJavaHomeTitle=JAVA_HOME変数を設定または上書き +; Japanese.FeatureJarFileRunWithTitle=.jarを関連付け +; Japanese.FeatureOracleJavaSoftTitle=JavaSoft (Oracle) レジストリキー + +#ifdef INCLUDE_UNOFFICIAL_TRANSLATIONS + +ChineseCN.FeatureEnvironmentDesc=通过将 JDK 安装路径添加到 PATH 值开头来修改 PATH 环境变量值. +ChineseCN.FeatureJavaHomeDesc=使用 JDK 安装路径来设置或重写 JAVA_HOME 环境变量值. +; ChineseCN.FeatureOracleJavaSoftDesc=覆盖 Oracle 的注册表项 HKLM\Software\JavaSoft。卸载 %1 后,需要重新安装 Oracle Java 以重新创建这些注册表项。 +ChineseCN.FeatureEnvironmentTitle=修改 PATH 变量值. +ChineseCN.FeatureJavaHomeTitle=设置或重写 JAVA_HOME 变量. +; ChineseCN.FeatureJarFileRunWithTitle=关联 .jar +; ChineseCN.FeatureOracleJavaSoftTitle=JavaSoft (Oracle) 注册表项 + +ChineseTW.FeatureEnvironmentDesc=將 JDK 安裝路徑新增至 PATH 值開頭來修改 PATH 環境變數值. +ChineseTW.FeatureJavaHomeDesc=使用 JDK 安裝路徑來設定或重寫 JAVA_HOME 環境變數值. +; ChineseTW.FeatureOracleJavaSoftDesc=覆寫 Oracle 的登錄機碼 HKLM\Software\JavaSoft。解除安裝 %1 後,需要重新安裝 Oracle Java 以重新建立這些登錄機碼。 +ChineseTW.FeatureEnvironmentTitle=修改 PATH 變數值 +ChineseTW.FeatureJavaHomeTitle=設定或重寫 JAVA_HOME 變量 +; ChineseTW.FeatureJarFileRunWithTitle=關聯 .jar +; ChineseTW.FeatureOracleJavaSoftTitle=JavaSoft (Oracle) 登錄機碼 + +#endif \ No newline at end of file diff --git a/wix/Lang/OpenJDK.Base.fr-fr.wxl.template b/wix/Lang/OpenJDK.Base.fr-fr.wxl.template index 44470b96a..f33fc17f7 100644 --- a/wix/Lang/OpenJDK.Base.fr-fr.wxl.template +++ b/wix/Lang/OpenJDK.Base.fr-fr.wxl.template @@ -17,7 +17,7 @@ - +