├── .github ├── CODEOWNERS └── workflows │ └── jenkins-security-scan.yml ├── images ├── build-config.png └── tool-config.png ├── src └── main │ ├── resources │ ├── caphyon │ │ └── jenkins │ │ │ └── advinst │ │ │ ├── AdvinstInstallation │ │ │ ├── config.properties │ │ │ ├── help-home.html │ │ │ ├── help-name.html │ │ │ └── config.jelly │ │ │ ├── AdvinstBuilder │ │ │ ├── help-installName.html │ │ │ ├── help-aipProjectNoDigitalSignature.html │ │ │ ├── help-aipProjectBuild.html │ │ │ ├── help-aipProjectPath.html │ │ │ ├── help-advinstExtraCommands.html │ │ │ ├── help-aipProjectOutputName.html │ │ │ ├── help-aipProjectOutputFolder.html │ │ │ ├── config.properties │ │ │ └── config.jelly │ │ │ ├── AdvinstInstaller │ │ │ ├── config.properties │ │ │ ├── help-advinstLicense.html │ │ │ ├── help-advinstVersion.html │ │ │ └── config.jelly │ │ │ └── Messages.properties │ └── index.jelly │ └── java │ └── caphyon │ └── jenkins │ └── advinst │ ├── AdvinstException.java │ ├── AdvinstVersions.java │ ├── AdvinstConsts.java │ ├── AdvinstParameters.java │ ├── AdvinstInstallation.java │ ├── AdvinstTool.java │ ├── AdvinstParametersProcessor.java │ ├── AdvinstDescriptorImpl.java │ ├── AdvinstAipReader.java │ ├── AdvinstBuilder.java │ └── AdvinstInstaller.java ├── .gitignore ├── .gitattributes ├── .editorconfig ├── CHANGELOG.md ├── pom.xml ├── README.md └── checkstyle.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/advanced-installer-msi-builder-plugin-developers 2 | -------------------------------------------------------------------------------- /images/build-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/advanced-installer-msi-builder-plugin/master/images/build-config.png -------------------------------------------------------------------------------- /images/tool-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/advanced-installer-msi-builder-plugin/master/images/tool-config.png -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstallation/config.properties: -------------------------------------------------------------------------------- 1 | AdvinstInstallNameTitle=Name 2 | AdvinstInstallHomeTitle=Home 3 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-installName.html: -------------------------------------------------------------------------------- 1 |
2 | Select one of the configured Advanced Installer launchers. 3 |
-------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstaller/config.properties: -------------------------------------------------------------------------------- 1 | AdvinstVersion=Version 2 | AdvinstLicense=License ID 3 | AdvinstEnablePowerShell=Enable PowerShell Support 4 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstaller/help-advinstLicense.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Advanced Installer license that will be used for registration. 4 |

5 |
-------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-aipProjectNoDigitalSignature.html: -------------------------------------------------------------------------------- 1 |
2 | This option allows you to skip the digital signature phase of the build. 3 |
4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /work/ 3 | /.idea/ 4 | /.settings/ 5 | /.vscode/ 6 | nb-configuration.xml 7 | *.iml 8 | *.releaseBackup 9 | *.project 10 | *.classpath 11 | release.properties 12 | .factorypath 13 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 | Thie plugin allows to build setups using Advanced Installer. 7 |
8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.git* text eol=lf 2 | 3 | # Type Script 4 | *.java text eol=lf 5 | *.xml text eol=lf 6 | *.properties text eol=lf 7 | *.md text eol=lf 8 | *.txt text eol=lf 9 | *.html text eol=lf 10 | *.jelly text eol=lf 11 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-aipProjectBuild.html: -------------------------------------------------------------------------------- 1 |
2 | The build name to be executed. If this field is empty, all the builds from the project are performed. 3 | You can use build variables through the syntax ${VAR} or $VAR. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-aipProjectPath.html: -------------------------------------------------------------------------------- 1 |
2 | Path of the Advanced Installer project file (.AIP). It can be either an absolute path or relative to the workspace directory. 3 | You can use build variables through the syntax ${VAR} or $VAR. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstaller/help-advinstVersion.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Advanced Installer version to install. For a list of available versions please see 4 | Advanced Installer version history 5 |

6 |
-------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-advinstExtraCommands.html: -------------------------------------------------------------------------------- 1 |
2 | Additional commands to be executed. It supports all the edit commands supports 3 | in an Advanced Installer Commands file. You can use build variables through the syntax ${VAR} or $VAR. 4 | E.g. SetVersion x.x.x 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstallation/help-home.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Path of local Advanced Installer installation (E.g. C:\\Program Files (x86)\\Caphyon\\Advanced Installer x.x). 4 |

5 |

6 | The plugin assumes that the Advanced Installer executable resides under the 'bin' subdirectory of this home. 7 |

8 |
-------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstallation/help-name.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Name you want to give to this Advanced Installer installation. 4 |

5 |

6 | When you configure a job 'Build' section and select 'Invoke Advanced Installer' as the method to run gradle, this name will 7 | appear in the 'Launcher' list. 8 |

9 |
-------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{java,xml,html,jelly,properties}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [.editorconfig] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-aipProjectOutputName.html: -------------------------------------------------------------------------------- 1 |
2 |

The result package name.If this field id empty the name configured in the AIP project will be used. 3 | You can use build variables through the syntax ${VAR} or $VAR. 4 |

5 |

6 | This option is available only when a build name is specified. 7 |

8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/help-aipProjectOutputFolder.html: -------------------------------------------------------------------------------- 1 |
2 |

Folder where the result package should be created. It can be either an absolute path or relative to the workspace directory. 3 | If this field id empty the folder configured in the AIP project will be used. 4 | You can use build variables through the syntax ${VAR} or $VAR. 5 |

6 |

7 | This option is available only when a build name is specified. 8 |

9 |
10 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstallation/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstInstaller/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/config.properties: -------------------------------------------------------------------------------- 1 | AipProjectPath=AIP Project 2 | AipProjectBuild=Build 3 | AipProjectPackageType=Package Type 4 | AipProjectOutputFolder=Package Output Folder 5 | AipProjectOutputName=Package Name 6 | AipProjectAdvancedOptions=Advanced Options 7 | AipProjectNoDigitalSignature=Do not digitally sign package 8 | AdvinstExtraCommands=Additional commands 9 | AdvinstInstallationName=Tool Instance 10 | AdvinstInstallation.Error=There are no Advanced Installer instances configured.
\ 11 | Please add an Advanced Installer instance in the Global Tool Configuration. 12 | AdvinstRunTypeDeploy=Deploy Advanced Installer Tool 13 | AdvinstRunTypeBuild=Deploy Advanced Installer Tool and build the project 14 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package caphyon.jenkins.advinst; 7 | 8 | /** 9 | * Advanced Installer high level exception 10 | * 11 | * @author Ciprian Burca 12 | */ 13 | public class AdvinstException extends Exception { 14 | 15 | private static final long serialVersionUID = -6308290342414398576L; 16 | 17 | public AdvinstException(final String message, final Throwable t) { 18 | super(message, t); 19 | } 20 | 21 | public AdvinstException(final String message) { 22 | super(message); 23 | } 24 | 25 | public AdvinstException(final Throwable t) { 26 | super(t); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstVersions.java: -------------------------------------------------------------------------------- 1 | package caphyon.jenkins.advinst; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import org.ini4j.Profile.Section; 6 | 7 | import hudson.util.VersionNumber; 8 | 9 | import org.ini4j.Wini; 10 | import java.time.LocalDate; 11 | import java.time.format.DateTimeFormatter; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | public class AdvinstVersions { 17 | 18 | private List
mVersions; 19 | 20 | public AdvinstVersions() { 21 | mVersions = getAllowedReleaseInfo(); 22 | } 23 | 24 | public String getMinimumAllowedVersion() { 25 | if (mVersions.isEmpty()) 26 | return ""; 27 | return mVersions.get(mVersions.size() - 1).get("ProductVersion"); 28 | } 29 | 30 | public boolean isDeprecated(String version) { 31 | final VersionNumber minAllowedVer = new VersionNumber(getMinimumAllowedVersion()); 32 | final VersionNumber crtVer = new VersionNumber(version); 33 | return minAllowedVer.isNewerThan(crtVer); 34 | } 35 | 36 | private List
getAllowedReleaseInfo() { 37 | try { 38 | final LocalDate minReleaseDate = LocalDate.now().minusMonths(AdvinstConsts.ValidReleaseIntervalMonths); 39 | final URL updatesIniUrl = new URL("https://www.advancedinstaller.com/downloads/updates.ini"); 40 | Wini updatesIni = new Wini(updatesIniUrl); 41 | return updatesIni.values().stream().filter(s -> { 42 | LocalDate rd = LocalDate.parse(s.get("ReleaseDate"), DateTimeFormatter.ofPattern("dd/M/yyyy")); 43 | return minReleaseDate.isBefore(rd) || minReleaseDate.isEqual(rd); 44 | }).collect(Collectors.toList()); 45 | } catch (IOException e) { 46 | return new ArrayList
(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/AdvinstBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
${%AdvinstInstallation.Error(rootURL)}
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Run Type: 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstConsts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package caphyon.jenkins.advinst; 7 | 8 | import java.io.File; 9 | 10 | /** 11 | * Constants used by Advinst plugin. 12 | * 13 | * @author Ciprian 14 | */ 15 | final class AdvinstConsts { 16 | 17 | private AdvinstConsts() { 18 | } 19 | 20 | // -------------------------------------------------------------------------- 21 | public static final String AdvinstExeApp = "advinst.exe"; 22 | public static final String AdvinstComApp = "AdvancedInstaller.com"; 23 | 24 | public static final String AdvinstToolsSubfolder = "bin" + File.separator + "x86"; 25 | public static final String AdvinstComSubPath = AdvinstToolsSubfolder + File.separator + AdvinstComApp; 26 | public static final String AdvinstAicHeader = ";aic"; 27 | public static final String AdvinstBuildAll = "All"; 28 | 29 | // -------------------------------------------------------------------------- 30 | // Advinst commands 31 | public static final String AdvinstCommandListBuilds = "ListBuilds"; 32 | public static final String AdvinstCommandResetSig = "ResetSig"; 33 | 34 | // -------------------------------------------------------------------------- 35 | // Advinst parameters 36 | public static final String AdvinstParamAdvinstRootPath = "advinstRootPath"; 37 | public static final String AdvinstParamAdvinstRunType = "advinstRunType"; 38 | public static final String AdvinstParamAipBuild = "aipProjectBuild"; 39 | public static final String AdvinstParamAipPath = "aipProjectPath"; 40 | public static final String AdvinstParamAipOutputFolder = "aipProjectOutputFolder"; 41 | public static final String AdvinstParamAipOutputName = "aipProjectOutputName"; 42 | public static final String AdvinstParamAipNoDigSig = "aipProjectNoDigitalSignature"; 43 | public static final String AdvinstParamExtraCommands = "advinstExtraCommands"; 44 | public static final String AdvinstRunTypeDeploy = "deploy"; 45 | public static final String AdvinstRunTypeBuild = "build"; 46 | 47 | public static final int ValidReleaseIntervalMonths = 24; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/caphyon/jenkins/advinst/Messages.properties: -------------------------------------------------------------------------------- 1 | #Form strings 2 | 3 | ADVINST=Advanced Installer 4 | ADVINST_INVOKE=Invoke Advanced Installer 5 | 6 | #Form validation error messages 7 | ERR_REQUIRED=Please provide a value for this field. 8 | ERR_PATH_NOT_FOUND=Path not found. 9 | ERR_INVALID_ADVINST_FOLDER=Invalid Advanced Installer install folder. 10 | ERR_INVALID_ADVINST_AIP=Invalid Advanced Installer project file. 11 | ERR_BUILD_NAME_REQUIRED=This options can be used only if the build is specified. Leave the field blank otherwise. 12 | 13 | #Build error messages 14 | ERR_ADVINST_INSTALL_NOT_SET=Advanced Installer installation was not configured. 15 | ERR_ADVINST_COM_NOT_FOUND=Cannot retrieve the Advanced Installer executable. 16 | ERR_ADVINST_AIP_NOT_FOUND=Advanced Installer project file not found. Configured path: {0} 17 | ERR_ADVINST_AIP_PATH_COMPUTE=Error when computing Advanced Installer project path 18 | ERR_ADVINST_AIP_OUTPUT_PATH_COMPUTE=Error when computing output folder 19 | ERR_ADVINST_AIP_BUILD_NAME_COMPUTE=Error when computing build name 20 | ERR_ADVINST_AIP_BUILD_NOT_FOUND=The specified build is not present in the project file 21 | ERR_ADVINST_AIP_OUTPUT_NAME_COMPUTE=Error when computing output package name 22 | ERR_ADVINST_FAILED_AIC=Failed to create the command file (.AIC) 23 | 24 | #Tool warnings 25 | ERROR_ADVINST_DEPRECATED_VERSION=ERROR: We want to provide the best experience for you and support the newest Advanced Installer features. To do so, we are no longer supporting older versions. Please note that the minimum required version is {0} and the configured version is {1}. 26 | 27 | #Tool install messages 28 | ERR_ADVINST_UNSUPPORTED_OS=Advanced Installer only runs under Windows platform 29 | ERR_ADVINST_UNSUPPORTED_OS_VERSION=Advanced Installer only runs under Windows 7 or greater. 30 | ERR_ADVINST_INSTALL_FAILED=Advanced Installer failed to deploy. 31 | ERR_ADVINST_DOWNLOAD_FAILED=Failed to download Advanced Installer from {0}. Error {1} 32 | ERR_ADVINST_EXTRACT_FAILED=Failed to extract Advanced Installer from {0}. Error {1} 33 | ERR_ADVINST_REGISTER_FAILED=Failed to register Advanced Installer 34 | ERR_ADVINST_REGISTER_COM_FAILED=Failed to enable PowerShell support 35 | MSG_ADVINST_INSTALL_FROM_WEBSITE=Install from advancedinstaller.com 36 | MSG_ADVINST_INSTALL=Deploying Advanced Installer from {0} to {1} on {2} 37 | MSG_ADVINST_DOWNLOAD_PROGRESS=Downloading {0} to {1} 38 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstParameters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2015 Ciprian Burca. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package caphyon.jenkins.advinst; 26 | 27 | import org.apache.commons.lang.StringUtils; 28 | import java.util.Properties; 29 | 30 | public final class AdvinstParameters { 31 | 32 | private final Properties mProperties; 33 | 34 | public AdvinstParameters() { 35 | this(new Properties()); 36 | } 37 | 38 | public AdvinstParameters(final Properties aProperties) { 39 | this.mProperties = aProperties; 40 | } 41 | 42 | public String get(final String key, final String defaultValue) { 43 | String tmp = this.mProperties.getProperty(key); 44 | return (StringUtils.isEmpty(tmp)) ? defaultValue : tmp; 45 | } 46 | 47 | public boolean get(final String key, final boolean defaultValue) { 48 | boolean rvalue = defaultValue; 49 | String tmp = this.mProperties.getProperty(key); 50 | if (tmp != null) { 51 | try { 52 | rvalue = Boolean.parseBoolean(tmp); 53 | } catch (Exception e) { 54 | // nothing to do 55 | } 56 | } 57 | return rvalue; 58 | } 59 | 60 | public void set(final String key, final String value) { 61 | this.mProperties.setProperty(key, value); 62 | } 63 | 64 | public void set(final String key, final boolean value) { 65 | set(key, String.valueOf(value)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | Changelog of Advanced Installer plugin for Jenkins. 4 | 5 | ## Advanced Installer Msi Builder Plugin 3.0.0 6 | 7 | New: 8 | * Add [Pipeline](https://www.jenkins.io/doc/book/pipeline/) support. 9 | * Add [Azure Trusted Sign](https://www.advancedinstaller.com/trusted-signing-integration.html) support. 10 | 11 | Changes: 12 | * The plugin now requires Jenkins version 2.361 and newer. 13 | * Add deprecation error, if using an Advanced Installer version older than two years. 14 | 15 | ## Advanced Installer Msi Builder Plugin 2.1.1 16 | 17 | Improvements: 18 | 19 | * Add better integration with newer Advanced Installer versions. 20 | * Add deprecation warning, if using an Advanced Installer version older than two years. 21 | 22 | ## Advanced Installer Msi Builder Plugin 2.1 23 | 24 | Features: 25 | 26 | * Added **Enable PowerShell support** option. 27 | * The plugin now supports two execution senarios: 28 | * Deploy Advanced Installer tool 29 | * Deploy Advanced Installer tool and build project 30 | 31 | __If you are referencing an Advanced Installer version prior to 17.7, in order to use *Enable PowerShell support* the build agent needs to run with elevated privileges. Versions 17.7 and later do not have this restriction.__ 32 | 33 | ## Advanced Installer Msi Builder Plugin 2.0.3 34 | 35 | Bugs: 36 | 37 | * Fixed issue which prevented Advanced Installer to be deployed on nodes where JENKINS_HOME path contained spaces. 38 | * Fixed issue which prevented Advanced Installer to be deployed when no License ID was specified. 39 | 40 | ## Advanced Installer Msi Builder Plugin 2.0.2 41 | 42 | Bugs: 43 | 44 | * Fixed crashing bug which prevented the deploy of Advanced Installer on slave nodes. 45 | 46 | ## Advanced Installer Msi Builder Plugin 2.0.1 47 | 48 | Improvements: 49 | 50 | * Added specific license registration support (requires [Advanced Installer 14.6](https://www.advancedinstaller.com/version-history.html) ) 51 | 52 | ## Advanced Installer Msi Builder Plugin 2.0 53 | 54 | Features: 55 | 56 | * Advanced Installer is automatically deployed on the slave nodes. 57 | 58 | ## Advanced Installer Msi Builder Plugin 1.3.1 59 | 60 | Bugs: 61 | 62 | * AIP fails validation when it contains specials characters like &#x; 63 | 64 | ## Advanced Installer Msi Builder Plugin 1.3 65 | 66 | Added support for additional edit commands. 67 | 68 | ## Advanced Installer Msi Builder Plugin 1.2 69 | 70 | Added support for distributed build environments. 71 | 72 | ## Advanced Installer Msi Builder Plugin 1.1 73 | 74 | Changed plugin name to: Advanced Installer Msi Builder Plugin 75 | 76 | ## Advanced Installer Msi Builder Plugin 1.0 77 | 78 | First official release. 79 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | org.jenkins-ci.plugins 5 | plugin 6 | 4.88 7 | 8 | 9 | 10 | 11 | 2.462.3 12 | 13 | 14 | org.jenkins-ci.plugins 15 | advanced-installer-msi-builder 16 | 3.0.1-SNAPSHOT 17 | Advanced Installer Msi Builder Plugin 18 | This plugin builds Advanced Installer projects. 19 | hpi 20 | 21 | 22 | MIT license 23 | All source code is under the MIT license. 24 | http://www.opensource.org/licenses/mit-license.php 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | repo.jenkins-ci.org 36 | https://repo.jenkins-ci.org/public/ 37 | 38 | 39 | 40 | 41 | 42 | repo.jenkins-ci.org 43 | https://repo.jenkins-ci.org/public/ 44 | 45 | 46 | 47 | 48 | 49 | advinst 50 | Advanced Installer 51 | support@advancedinstaller.com 52 | 53 | 54 | 55 | 56 | 57 | net.java.dev.jna 58 | jna 59 | 60 | 61 | net.java.dev.jna 62 | jna-platform 63 | 5.14.0 64 | 65 | 66 | org.ini4j 67 | ini4j 68 | 0.5.4 69 | 70 | 71 | 72 | 73 | scm:git:ssh://github.com/jenkinsci/advanced-installer-msi-builder-plugin.git 74 | scm:git:ssh://git@github.com/jenkinsci/advanced-installer-msi-builder-plugin.git 75 | https://github.com/jenkinsci/advanced-installer-msi-builder-plugin 76 | advanced-installer-msi-builder-2.1.0 77 | 78 | 79 | https://github.com/jenkinsci/advanced-installer-msi-builder-plugin 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstInstallation.java: -------------------------------------------------------------------------------- 1 | package caphyon.jenkins.advinst; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import javax.inject.Inject; 9 | 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | 12 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 13 | import hudson.EnvVars; 14 | import hudson.Extension; 15 | import hudson.Launcher; 16 | import hudson.Util; 17 | import hudson.model.EnvironmentSpecific; 18 | import hudson.model.Node; 19 | import hudson.model.TaskListener; 20 | import hudson.slaves.NodeSpecific; 21 | import hudson.tools.ToolDescriptor; 22 | import hudson.tools.ToolInstallation; 23 | import hudson.tools.ToolInstaller; 24 | import hudson.tools.ToolProperty; 25 | import jenkins.security.MasterToSlaveCallable; 26 | 27 | public final class AdvinstInstallation extends ToolInstallation 28 | implements EnvironmentSpecific, NodeSpecific { 29 | 30 | private static final long serialVersionUID = -6715383276188462597L; 31 | private final String advinstHome; 32 | public static final String advinstComSubPath = "bin\\x86\\AdvancedInstaller.com"; 33 | 34 | @DataBoundConstructor 35 | public AdvinstInstallation(final String name, final String home, final List> properties) { 36 | super(name, home, properties); 37 | this.advinstHome = getHome(); 38 | } 39 | 40 | @Override 41 | public String getHome() { 42 | if (advinstHome != null) { 43 | return advinstHome; 44 | } 45 | return super.getHome(); 46 | } 47 | 48 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 49 | public String getExecutable(final Launcher launcher) throws IOException, InterruptedException { 50 | return launcher.getChannel().call(new MasterToSlaveCallable() { 51 | private static final long serialVersionUID = 8800376540325557778L; 52 | 53 | public String call() throws IOException { 54 | File exe = getExeFile(); 55 | if (exe.exists()) { 56 | return exe.getPath(); 57 | } 58 | return null; 59 | } 60 | }); 61 | } 62 | 63 | private File getExeFile() { 64 | String home = Util.replaceMacro(advinstHome, EnvVars.masterEnvVars); 65 | return new File(home, advinstComSubPath); 66 | } 67 | 68 | @Override 69 | public AdvinstInstallation forNode(final Node node, final TaskListener log) throws IOException, InterruptedException { 70 | return new AdvinstInstallation(getName(), translateFor(node, log), getProperties().toList()); 71 | } 72 | 73 | @Override 74 | public AdvinstInstallation forEnvironment(final EnvVars environment) { 75 | return new AdvinstInstallation(getName(), environment.expand(getHome()), getProperties().toList()); 76 | } 77 | 78 | @Extension 79 | public static final class DescriptorImpl extends ToolDescriptor { 80 | 81 | @Inject 82 | private AdvinstDescriptorImpl mAdvinstDescriptor; 83 | 84 | @Override 85 | public String getDisplayName() { 86 | return Messages.ADVINST(); 87 | } 88 | 89 | @Override 90 | public List getDefaultInstallers() { 91 | return Collections.singletonList(new AdvinstInstaller(null, null, null, false)); 92 | } 93 | 94 | @Override 95 | public AdvinstInstallation[] getInstallations() { 96 | return mAdvinstDescriptor.getInstallations(); 97 | } 98 | 99 | @Override 100 | public void setInstallations(final AdvinstInstallation... installations) { 101 | mAdvinstDescriptor.setInstallations(installations); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2015 Ciprian Burca. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package caphyon.jenkins.advinst; 25 | 26 | import java.io.IOException; 27 | import java.util.List; 28 | 29 | import hudson.EnvVars; 30 | import hudson.FilePath; 31 | import hudson.Launcher; 32 | import hudson.model.TaskListener; 33 | import hudson.util.ArgumentListBuilder; 34 | 35 | public final class AdvinstTool { 36 | private final String mAdvinstComPath; 37 | 38 | public AdvinstTool(final String advinstComPath) { 39 | this.mAdvinstComPath = advinstComPath; 40 | } 41 | 42 | public boolean executeCommands(final List commands, final FilePath aipPath, final FilePath workspace, 43 | final Launcher launcher, final TaskListener listener, final EnvVars env) throws AdvinstException { 44 | FilePath aicFilePath = null; 45 | try { 46 | if (launcher.isUnix()) { 47 | throw new AdvinstException(Messages.ERR_ADVINST_UNSUPPORTED_OS()); 48 | } 49 | 50 | FilePath pwd = workspace; 51 | if (null == pwd) { 52 | return false; 53 | } 54 | 55 | aicFilePath = createAicFile(pwd, commands); 56 | if (null == aicFilePath) { 57 | throw new AdvinstException(Messages.ERR_ADVINST_FAILED_AIC()); 58 | } 59 | 60 | ArgumentListBuilder cmdExecArgs = new ArgumentListBuilder(); 61 | cmdExecArgs.add(mAdvinstComPath, "/execute", aipPath.getRemote(), aicFilePath.getRemote()); 62 | 63 | int result = launcher.launch().cmds(cmdExecArgs).envs(env).stdout(listener).pwd(pwd).join(); 64 | return 0 == result; 65 | 66 | } catch (IOException e) { 67 | throw new AdvinstException(e); 68 | } catch (InterruptedException e) { 69 | throw new AdvinstException(e); 70 | } finally { 71 | try { 72 | if (aicFilePath != null) { 73 | aicFilePath.delete(); 74 | } 75 | } catch (IOException e) { 76 | throw new AdvinstException(e); 77 | } catch (InterruptedException e) { 78 | throw new AdvinstException(e); 79 | } 80 | } 81 | } 82 | 83 | private static FilePath createAicFile(final FilePath buildWorkspace, final List aCommands) 84 | throws IOException, InterruptedException { 85 | FilePath aicFile = buildWorkspace.createTempFile("aic", "aic"); 86 | StringBuffer fileContent = new StringBuffer(AdvinstConsts.AdvinstAicHeader + "\r\n"); 87 | for (String command : aCommands) { 88 | fileContent.append(command); 89 | fileContent.append("\r\n"); 90 | } 91 | 92 | aicFile.write(fileContent.toString(), "UTF-16"); 93 | return aicFile; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstParametersProcessor.java: -------------------------------------------------------------------------------- 1 | package caphyon.jenkins.advinst; 2 | 3 | import hudson.EnvVars; 4 | import hudson.FilePath; 5 | import hudson.Util; 6 | import hudson.model.AbstractBuild; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.StringTokenizer; 11 | 12 | /** 13 | * Translates the parameters provided by the user into something that Advanced 14 | * Installer can work with. It also validates the UI data 15 | * 16 | * It returns a list of commands to be executed. 17 | */ 18 | public class AdvinstParametersProcessor { 19 | private final EnvVars mEnvVars; 20 | private final AdvinstParameters mUiParameters; 21 | private final FilePath mBuildWorkspace; 22 | private final FilePath mAipPath; 23 | 24 | public AdvinstParametersProcessor(final AdvinstParameters uiParams, final FilePath aipPath, final FilePath workspace, final EnvVars envVars) { 25 | mEnvVars = envVars; 26 | mUiParameters = uiParams; 27 | mBuildWorkspace = workspace; 28 | mAipPath = aipPath; 29 | } 30 | 31 | public final List getCommands() throws AdvinstException { 32 | FilePath outputFolder; 33 | String buildName; 34 | String outputFileName; 35 | 36 | // ------------------------------------------------------------------------ 37 | // Compute and validate output folder path. It can be either an absolute path 38 | // or relative to the build workspace folder. 39 | { 40 | // Because the output folder may reference environment variables, expand them 41 | // before computing the absolute path. 42 | outputFolder = getExpandedFilePathValue(AdvinstConsts.AdvinstParamAipOutputFolder); 43 | } 44 | 45 | // ------------------------------------------------------------------------ 46 | // compute and validate build name. 47 | { 48 | 49 | buildName = getExpandedStringValue(AdvinstConsts.AdvinstParamAipBuild); 50 | 51 | // Check if this build actually exists in the AIP 52 | AdvinstAipReader aipReader = new AdvinstAipReader(mAipPath); 53 | if (!buildName.isEmpty() && !aipReader.getBuilds().contains(buildName)) { 54 | throw new AdvinstException(Messages.ERR_ADVINST_AIP_BUILD_NOT_FOUND()); 55 | } 56 | } 57 | 58 | // ------------------------------------------------------------------------ 59 | // compute and validate the output package name 60 | { 61 | outputFileName = getExpandedStringValue(AdvinstConsts.AdvinstParamAipOutputName); 62 | } 63 | 64 | List advinstCommands = new ArrayList(); 65 | 66 | if (!buildName.isEmpty()) { 67 | // These parameters require a build name; 68 | if (!outputFileName.isEmpty()) { 69 | advinstCommands.add(String.format("SetPackageName \"%s\" -buildname \"%s\"", outputFileName, buildName)); 70 | } 71 | 72 | if (null != outputFolder) { 73 | advinstCommands.add(String.format("SetOutputLocation -buildname \"%s\" -path \"%s\"", buildName, outputFolder)); 74 | } 75 | } 76 | 77 | if (mUiParameters.get(AdvinstConsts.AdvinstParamAipNoDigSig, false)) { 78 | advinstCommands.add("ResetSig"); 79 | } 80 | 81 | String additionalCommands = getExpandedStringValue(AdvinstConsts.AdvinstParamExtraCommands); 82 | if (!additionalCommands.isEmpty()) { 83 | StringTokenizer tokenizer = new StringTokenizer(additionalCommands, "\r\n"); 84 | while (tokenizer.hasMoreTokens()) { 85 | advinstCommands.add(tokenizer.nextToken()); 86 | } 87 | } 88 | 89 | advinstCommands.add(String.format("Build -buildslist \"%s\"", buildName)); 90 | 91 | return advinstCommands; 92 | } 93 | 94 | private String getExpandedStringValue(final String uiParamName) { 95 | String expandedValue = Util.replaceMacro(mUiParameters.get(uiParamName, ""), mEnvVars); 96 | return expandedValue; 97 | } 98 | 99 | private FilePath getExpandedFilePathValue(final String uiParamName) { 100 | final String expandedStringValue = getExpandedStringValue(uiParamName); 101 | if (expandedStringValue.isEmpty()) { 102 | return null; 103 | } 104 | return new FilePath(mBuildWorkspace, expandedStringValue); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Using this plugin you can easily integrate Advanced Installer into your 4 | Jenkins build system. The output package can be any of the supported 5 | packaging types supported by Advanced Installer, i.e. MSI, EXE, MSP, 6 | MSM, APPV, etc.  7 | 8 | Advanced Installer is a Windows Installer authoring tool that can be 9 | used to create installers for Windows, for desktop and web applications 10 | (running on Windows servers). The [Advanced Installer command line 11 | interface](http://www.advancedinstaller.com/user-guide/command-line.html) 12 |  works with any automated build system. Also, it features a Visual 13 | Studio extension, so you can create an Advanced Installer project and 14 | build your installer directly from Visual Studio IDE. The VS extension 15 | also integrates automatically with MSBuild, without requiring additional 16 | configuration. 17 | 18 | **Advanced Installer website**: 19 |  [http://www.advancedinstaller.com](http://www.advancedinstaller.com/) 20 | 21 | Please note that this plugin only works on Windows-driven Jenkins 22 | installations. 23 | 24 | # Prerequisites 25 | 26 | 1. This plugin requires [Advanced 27 | Installer](https://www.advancedinstaller.com/).  28 | 2. JRE/JDK 1.7 or newer is also required for this plugin to work 29 | correctly. Older versions of the JDK have not been tested and might 30 | not work as expected. 31 | 32 | # Tool Configuration 33 | 34 | Before you can use it you need to configure an* Advanced 35 | Installer installation.  *Here you have two options: 36 | 37 | 1. Specify the root location where Advanced Installer already exists. 38 | 2. Automatically install a new version 39 | from [advancedinstaller.com](https://www.advancedinstaller.com/version-history.html). 40 | You will need a  valid license key for registration, otherwise 41 | builds will fails or the generated setup packages will contains 42 | trial notification messages. If you are using the free edition of 43 | Advanced Installer, i.e. create only projects of type "Simple", a 44 | license is not required. 45 | 46 | ![Configure glocal tool](images/tool-config.png) 47 | 48 | Job Configuration 49 | 50 | Job configuration is easy, just enter the relative path to your AIP file 51 | and set the desired options. For details about the different options, 52 | please refer to the [Advanced Installer user 53 | guide](http://www.advancedinstaller.com/user-guide/introduction.html). 54 | 55 | ![Configure pipeline](images/build-config.png) 56 | 57 | # Use from Pipeline 58 | 59 | ### Just deploy 60 | 61 | ``` 62 | pipeline { 63 | agent { label 'windows' } 64 | stages { 65 | stage("build") { 66 | steps { 67 | script { 68 | advinstBuilder( 69 | installName: "Advinst 21.8.1", 70 | advinstRunType: "deploy", 71 | ) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | ``` 78 | ### Deploy and build AIP project 79 | 80 | ``` 81 | pipeline { 82 | agent { label 'windows' } 83 | stages { 84 | stage("build") { 85 | steps { 86 | script { 87 | advinstBuilder( 88 | installName: 'Advinst 21.8.1', 89 | advinstRunType: 'build', 90 | aipProjectPath: 'my_awesome_project.aip', 91 | aipProjectBuild: 'MyBuild', 92 | aipProjectOutputFolder: 'output', 93 | aipProjectOutputName: 'MySetup', 94 | aipProjectNoDigitalSignature: false, 95 | advinstExtraCommands: 'SetVersion "1.1.1"' 96 | ) 97 | } 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | # **Changelog** 104 | 105 | The latest changes are documented on **[GitHub 106 | Changelog](https://github.com/jenkinsci/advanced-installer-msi-builder-plugin/blob/master/CHANGELOG.md)**. 107 | 108 | # Technical Support 109 | 110 | Further information about Advanced Installer Plugin, including feature 111 | requests or bug reports, you may contact us 112 | on . 113 | 114 |   115 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstDescriptorImpl.java: -------------------------------------------------------------------------------- 1 | package caphyon.jenkins.advinst; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.Map; 6 | 7 | import javax.servlet.ServletException; 8 | 9 | import org.jenkinsci.Symbol; 10 | import org.kohsuke.stapler.QueryParameter; 11 | import org.kohsuke.stapler.StaplerRequest; 12 | 13 | import hudson.CopyOnWrite; 14 | import hudson.Extension; 15 | import hudson.model.AbstractProject; 16 | import hudson.model.Descriptor; 17 | import hudson.tasks.BuildStepDescriptor; 18 | import hudson.tasks.Builder; 19 | import hudson.tools.ToolInstallation; 20 | import hudson.util.FormValidation; 21 | import hudson.util.ListBoxModel; 22 | import net.sf.json.JSONObject; 23 | 24 | /** 25 | * Descriptor for {@link AdvinstBuilder}. Used as a singleton. The class is 26 | * marked as public so that it can be accessed from views. 27 | * 28 | *

29 | * See src/main/resources/caphyon/jenkins/AdvinstBuilder/*.jelly for 30 | * the actual HTML fragment for the configuration screen. 31 | * 32 | * @author Ciprian Burca 33 | */ 34 | @Extension // This indicates to Jenkins that this is an implementation of an extension 35 | // point. 36 | @Symbol("advinstBuilder") 37 | public final class AdvinstDescriptorImpl extends BuildStepDescriptor { 38 | 39 | @CopyOnWrite 40 | private volatile AdvinstInstallation[] installations = new AdvinstInstallation[0]; 41 | 42 | public AdvinstDescriptorImpl() { 43 | super(AdvinstBuilder.class); 44 | load(); 45 | } 46 | 47 | public ListBoxModel doFillInstallNameItems() { 48 | ListBoxModel items = new ListBoxModel(); 49 | for (AdvinstInstallation inst : getInstallations()) { 50 | items.add(new ListBoxModel.Option(inst.getName())); 51 | } 52 | return items; 53 | } 54 | 55 | public FormValidation doCheckAipProjectPath(final @QueryParameter String value) throws IOException, ServletException { 56 | if (value == null || value.length() == 0) { 57 | return FormValidation.error(Messages.ERR_REQUIRED()); 58 | } 59 | 60 | return FormValidation.ok(); 61 | } 62 | 63 | public FormValidation doCheckAipProjectOutputFolder(final @QueryParameter String value, 64 | final @QueryParameter String aipProjectBuild) throws IOException, ServletException { 65 | if (value != null && !value.isEmpty()) { 66 | if (aipProjectBuild == null || aipProjectBuild.length() == 0) { 67 | return FormValidation.error(Messages.ERR_BUILD_NAME_REQUIRED()); 68 | } 69 | } 70 | return FormValidation.ok(); 71 | } 72 | 73 | public FormValidation doCheckAipProjectOutputName(final @QueryParameter String value, 74 | final @QueryParameter String aipProjectBuild) throws IOException, ServletException { 75 | if (value != null && !value.isEmpty()) { 76 | if (aipProjectBuild == null || aipProjectBuild.length() == 0) { 77 | return FormValidation.error(Messages.ERR_BUILD_NAME_REQUIRED()); 78 | } 79 | } 80 | return FormValidation.ok(); 81 | } 82 | 83 | @Override 84 | public boolean isApplicable(final Class aClass) { 85 | // Indicates that this builder can be used with all kinds of project types 86 | return true; 87 | } 88 | 89 | /** 90 | * @return Human readable name is used in the configuration screen. 91 | */ 92 | @Override 93 | public String getDisplayName() { 94 | return Messages.ADVINST_INVOKE(); 95 | } 96 | 97 | public boolean configure(final StaplerRequest req, final JSONObject formData) throws Descriptor.FormException { 98 | save(); 99 | return super.configure(req, formData); 100 | } 101 | 102 | public AdvinstInstallation.DescriptorImpl getToolDescriptor() { 103 | return ToolInstallation.all().get(AdvinstInstallation.DescriptorImpl.class); 104 | } 105 | 106 | protected void convertfinal(final Map oldPropertyBag) { 107 | if (oldPropertyBag.containsKey("installations")) { 108 | installations = (AdvinstInstallation[]) oldPropertyBag.get("installations"); 109 | } 110 | } 111 | 112 | public AdvinstInstallation[] getInstallations() { 113 | return Arrays.copyOf(installations, installations.length); 114 | } 115 | 116 | public void setInstallations(final AdvinstInstallation... installations) { 117 | this.installations = installations; 118 | save(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstAipReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package caphyon.jenkins.advinst; 7 | 8 | import hudson.FilePath; 9 | import org.w3c.dom.Attr; 10 | import org.w3c.dom.Document; 11 | import org.w3c.dom.Node; 12 | import org.w3c.dom.NodeList; 13 | import org.xml.sax.SAXException; 14 | 15 | import javax.xml.parsers.DocumentBuilder; 16 | import javax.xml.parsers.DocumentBuilderFactory; 17 | import javax.xml.parsers.ParserConfigurationException; 18 | import javax.xml.xpath.XPath; 19 | import javax.xml.xpath.XPathConstants; 20 | import javax.xml.xpath.XPathExpressionException; 21 | import javax.xml.xpath.XPathFactory; 22 | import java.io.IOException; 23 | import java.io.StringReader; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import org.xml.sax.InputSource; 27 | 28 | /** 29 | * Utility class that extract information by reading the AIP file directly. 30 | * 31 | * @author Ciprian Burca 32 | */ 33 | class AdvinstAipReader { 34 | 35 | private final FilePath mAipFile; 36 | private Document mXmlDocument = null; 37 | 38 | /** 39 | * Class constructor. 40 | * 41 | * @param aAipFile Path to Advanced Installer project file (.AIP) 42 | */ 43 | AdvinstAipReader(final FilePath aAipFile) { 44 | this.mAipFile = aAipFile; 45 | } 46 | 47 | /** 48 | * Get the build names from the AIP. 49 | * 50 | * @return strings list containing the build names 51 | * @throws caphyon.jenkins.advinst.AdvinstException 52 | */ 53 | public List getBuilds() throws AdvinstException { 54 | List aipBuilds = new ArrayList(); 55 | 56 | loadXmlFile(); 57 | 58 | final String buildsXPAth = "/DOCUMENT/COMPONENT[@cid='caphyon.advinst.msicomp.BuildComponent']/ROW"; 59 | XPath xPath = XPathFactory.newInstance().newXPath(); 60 | NodeList buildRows; 61 | try { 62 | buildRows = (NodeList) xPath.evaluate(buildsXPAth, mXmlDocument, XPathConstants.NODESET); 63 | } catch (XPathExpressionException ex) { 64 | throw new AdvinstException("Failed read AIP builds. Exception: " + ex.getMessage(), ex); 65 | } 66 | for (int i = 0; i < buildRows.getLength(); i++) { 67 | Node buildRow = buildRows.item(i); 68 | Attr nameAttr = (Attr) buildRow.getAttributes().getNamedItem("BuildName"); 69 | if (null != nameAttr) { 70 | aipBuilds.add(nameAttr.getValue()); 71 | } 72 | } 73 | 74 | return aipBuilds; 75 | } 76 | 77 | public boolean isValidAip() throws AdvinstException { 78 | loadXmlFile(); 79 | NodeList children = mXmlDocument.getChildNodes(); 80 | if (children.getLength() != 1) { 81 | return false; 82 | } 83 | 84 | if (!"DOCUMENT".equals(children.item(1).getNodeName())) { 85 | return false; 86 | } 87 | 88 | Attr nameAttr = (Attr) children.item(1).getAttributes().getNamedItem("Type"); 89 | return null != nameAttr; 90 | 91 | } 92 | 93 | private void loadXmlFile() throws AdvinstException { 94 | try { 95 | if (null != mXmlDocument) { 96 | return; 97 | } 98 | 99 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 100 | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 101 | String aipContent = mAipFile.readToString(); 102 | // Update the XML version from 1.0 to 1.1. The AIP might contain special 103 | // characters like  104 | // which are invalid for XML 1.0. 105 | aipContent = aipContent.replace("", 106 | ""); 107 | mXmlDocument = documentBuilder.parse(new InputSource(new StringReader(aipContent))); 108 | } catch (SAXException ex) { 109 | throw new AdvinstException("Failed to load AIP file. Exception: " + ex.getMessage(), ex); 110 | } catch (ParserConfigurationException ex) { 111 | throw new AdvinstException("Failed to load AIP file. Exception: " + ex.getMessage(), ex); 112 | } catch (IOException ex) { 113 | throw new AdvinstException("Failed to load AIP file. Exception: " + ex.getMessage(), ex); 114 | } catch (InterruptedException ex) { 115 | throw new AdvinstException("Failed to load AIP file. Exception: " + ex.getMessage(), ex); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstBuilder.java: -------------------------------------------------------------------------------- 1 | package caphyon.jenkins.advinst; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import org.kohsuke.stapler.DataBoundConstructor; 7 | import org.kohsuke.stapler.DataBoundSetter; 8 | 9 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 10 | import hudson.EnvVars; 11 | import hudson.FilePath; 12 | import hudson.Launcher; 13 | import hudson.Util; 14 | import hudson.model.AbstractBuild; 15 | import hudson.model.BuildListener; 16 | import hudson.model.Computer; 17 | import hudson.model.Executor; 18 | import hudson.model.Node; 19 | import hudson.model.ParameterValue; 20 | import hudson.model.ParametersAction; 21 | import hudson.model.Result; 22 | import hudson.tasks.Builder; 23 | 24 | import hudson.model.Run; 25 | import hudson.model.StringParameterValue; 26 | import hudson.model.TaskListener; 27 | import jenkins.tasks.SimpleBuildStep; 28 | 29 | /** 30 | * Sample {@link Builder}. 31 | * 32 | * When a build is performed, the 33 | * {@link AdvinstBuilder#perform(AbstractBuild, Launcher, BuildListener)} method 34 | * will be invoked. 35 | * 36 | * @author Ciprian Burca 37 | */ 38 | public final class AdvinstBuilder extends Builder implements SimpleBuildStep { 39 | 40 | private final AdvinstParameters mAdvinstParameters; 41 | private String mInstallName; 42 | 43 | /** 44 | * Class DataBoundConstructor. Fields in config.jelly must match the parameter 45 | * names in the "DataBoundConstructor" 46 | * 47 | * @param installName name of the selected advinst installation name 48 | * @param advinstRunType execution mode for the plugin: deploy, build 49 | * @param aipProjectPath path to the Advanced Installer project to be built 50 | * @param aipProjectBuild build name to be executed 51 | * @param aipProjectOutputFolder output folder for the result package 52 | * @param aipProjectOutputName name of the result package 53 | * @param advinstExtraCommands list of aic commands to be executead against the aip 54 | * @param aipProjectNoDigitalSignature tells to skip the digital signature step 55 | */ 56 | @DataBoundConstructor 57 | public AdvinstBuilder(final String installName, final String advinstRunType, final String aipProjectPath, 58 | final String aipProjectBuild, final String aipProjectOutputFolder, final String aipProjectOutputName, 59 | final String advinstExtraCommands, final boolean aipProjectNoDigitalSignature) { 60 | this.mInstallName = installName; 61 | this.mAdvinstParameters = new AdvinstParameters(); 62 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamAipPath, aipProjectPath == null ? "" : aipProjectPath); 63 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamAdvinstRunType, advinstRunType == null ? "build" : advinstRunType); 64 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamAipBuild, aipProjectBuild == null ? "" : aipProjectBuild); 65 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamAipOutputFolder, aipProjectOutputFolder == null ? "" : aipProjectOutputFolder); 66 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamAipOutputName, aipProjectOutputName == null ? "" : aipProjectOutputName); 67 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamAipNoDigSig, aipProjectNoDigitalSignature); 68 | this.mAdvinstParameters.set(AdvinstConsts.AdvinstParamExtraCommands, advinstExtraCommands == null ? "" : advinstExtraCommands); 69 | } 70 | 71 | 72 | @Override 73 | public void perform(Run run, FilePath wotkspace, EnvVars envVars, Launcher launcher, TaskListener listener) 74 | throws InterruptedException, IOException { 75 | boolean success; 76 | try { 77 | EnvVars env = envVars; 78 | 79 | // Get the build parameters 80 | ParametersAction parameters = run.getAction(ParametersAction.class); 81 | if (parameters != null) { 82 | for (ParameterValue value : parameters.getParameters()) { 83 | if (value instanceof StringParameterValue) { 84 | StringParameterValue stringValue = (StringParameterValue) value; 85 | env.put(stringValue.getName(), (String)stringValue.getValue()); 86 | } 87 | } 88 | } 89 | 90 | final Node node = getNodeFromRun(run); 91 | final String advinstComPath = getAdvinstComPath(node, launcher, listener, env); 92 | 93 | if (getAdvinstRunType().equals(AdvinstConsts.AdvinstRunTypeDeploy)) { 94 | return; 95 | } 96 | 97 | final FilePath advinstAipPath = getAdvinstAipPath(wotkspace, launcher, env); 98 | 99 | AdvinstParametersProcessor paramsProcessor = new AdvinstParametersProcessor(mAdvinstParameters, advinstAipPath, 100 | wotkspace, env); 101 | final List commands = paramsProcessor.getCommands(); 102 | 103 | AdvinstTool advinstTool = new AdvinstTool(advinstComPath); 104 | success = advinstTool.executeCommands(commands, advinstAipPath, wotkspace, launcher, listener, env); 105 | run.setResult(success ? Result.SUCCESS : Result.FAILURE); 106 | } catch (AdvinstException e) { 107 | listener.fatalError(e.getMessage()); 108 | run.setResult(Result.FAILURE); 109 | } 110 | } 111 | 112 | @Override 113 | public AdvinstDescriptorImpl getDescriptor() { 114 | return (AdvinstDescriptorImpl) super.getDescriptor(); 115 | } 116 | 117 | public String getInstallName() { 118 | return this.mInstallName; 119 | } 120 | 121 | @DataBoundSetter 122 | public void setInstallName(final String installName) { 123 | this.mInstallName = installName; 124 | } 125 | 126 | public String getAdvinstRunType() { 127 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamAdvinstRunType, "build"); 128 | } 129 | 130 | /** 131 | * @return String containing the path to the Advanced Installer project to build 132 | */ 133 | public String getAipProjectPath() { 134 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamAipPath, ""); 135 | } 136 | 137 | /** 138 | * @return String containing the build name to performed 139 | */ 140 | public String getAipProjectBuild() { 141 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamAipBuild, ""); 142 | } 143 | 144 | /** 145 | * @return String containing the location of the result package 146 | */ 147 | public String getAipProjectOutputFolder() { 148 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamAipOutputFolder, ""); 149 | } 150 | 151 | /** 152 | * @return String containing the package name 153 | */ 154 | public String getAipProjectOutputName() { 155 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamAipOutputName, ""); 156 | } 157 | 158 | /** 159 | * @return String containing additional edit commands 160 | */ 161 | public String getAdvinstExtraCommands() { 162 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamExtraCommands, ""); 163 | } 164 | 165 | /** 166 | * @return Boolean that tells whether the digital signature step should be 167 | * performed 168 | */ 169 | public boolean getAipProjectNoDigitalSignature() { 170 | return this.mAdvinstParameters.get(AdvinstConsts.AdvinstParamAipNoDigSig, false); 171 | } 172 | 173 | private String getAdvinstComPath(final Node node, final Launcher launcher, final TaskListener listener, final EnvVars env) 174 | throws AdvinstException { 175 | 176 | AdvinstInstallation advinstInstall = getAdvinstInstallation(); 177 | if (null == advinstInstall) { 178 | throw new AdvinstException(Messages.ERR_ADVINST_INSTALL_NOT_SET()); 179 | } 180 | 181 | String advinstComPath = null; 182 | try { 183 | advinstInstall = advinstInstall.forNode(node, listener); 184 | advinstInstall = advinstInstall.forEnvironment(env); 185 | advinstComPath = advinstInstall.getExecutable(launcher); 186 | if (null == advinstComPath) { 187 | throw new AdvinstException(Messages.ERR_ADVINST_COM_NOT_FOUND()); 188 | } 189 | } catch (IOException ex) { 190 | throw new AdvinstException(ex); 191 | } catch (InterruptedException ex) { 192 | throw new AdvinstException(ex); 193 | } 194 | 195 | return advinstComPath; 196 | } 197 | 198 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 199 | private FilePath getAdvinstAipPath(final FilePath workspace, final Launcher launcher, final EnvVars env) 200 | throws AdvinstException { 201 | final String advinstAipPathParam = getAipProjectPath(); 202 | String expandedValue = Util.replaceMacro(advinstAipPathParam, env); 203 | assert expandedValue != null; 204 | FilePath advinstAipPath = new FilePath(workspace, expandedValue); 205 | try { 206 | if (!advinstAipPath.exists()) { 207 | throw new AdvinstException(Messages.ERR_ADVINST_AIP_NOT_FOUND(advinstAipPath.getRemote())); 208 | } 209 | } catch (IOException e) { 210 | throw new AdvinstException(e); 211 | } catch (InterruptedException e) { 212 | throw new AdvinstException(e); 213 | } 214 | 215 | return advinstAipPath; 216 | } 217 | 218 | public AdvinstInstallation getAdvinstInstallation() { 219 | for (AdvinstInstallation i : getDescriptor().getInstallations()) { 220 | if (mInstallName != null && i.getName().equals(mInstallName)) { 221 | return i; 222 | } 223 | } 224 | return null; 225 | } 226 | 227 | private Node getNodeFromRun(Run run) { 228 | final Executor executor = run.getExecutor(); 229 | if (executor == null) { 230 | return null; 231 | } 232 | final Computer computer = executor.getOwner(); 233 | return computer.getNode(); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/caphyon/jenkins/advinst/AdvinstInstaller.java: -------------------------------------------------------------------------------- 1 | package caphyon.jenkins.advinst; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.URL; 7 | import java.net.URLConnection; 8 | 9 | import javax.servlet.ServletException; 10 | import com.sun.jna.platform.win32.VerRsrc.VS_FIXEDFILEINFO; 11 | import com.sun.jna.platform.win32.VersionUtil; 12 | 13 | import org.kohsuke.stapler.DataBoundConstructor; 14 | import org.kohsuke.stapler.QueryParameter; 15 | 16 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 17 | import hudson.EnvVars; 18 | import hudson.Extension; 19 | import hudson.FilePath; 20 | import hudson.Launcher; 21 | import hudson.Launcher.ProcStarter; 22 | import hudson.Proc; 23 | import hudson.Util; 24 | import hudson.model.Node; 25 | import hudson.model.TaskListener; 26 | import hudson.remoting.VirtualChannel; 27 | import hudson.slaves.EnvironmentVariablesNodeProperty; 28 | import hudson.tools.ToolInstallation; 29 | import hudson.tools.ToolInstaller; 30 | import hudson.tools.ToolInstallerDescriptor; 31 | import hudson.util.ArgumentListBuilder; 32 | import hudson.util.FormValidation; 33 | import hudson.util.Secret; 34 | import hudson.util.VersionNumber; 35 | import jenkins.security.MasterToSlaveCallable; 36 | 37 | public final class AdvinstInstaller extends ToolInstaller { 38 | 39 | private static final String kAdvinstUrlTemplate = "https://www.advancedinstaller.com/downloads/%s/advinst.msi"; 40 | private static final VersionNumber kMinimumWindowsOsVersion = new VersionNumber("6.1"); // Windows 7 41 | private static final VersionNumber kAdvinstRegVersionSwitch = new VersionNumber("14.6"); 42 | private static final String kAdvinstURLEnvVar = "advancedinstaller.url"; 43 | private final String mAdvinstVersion; 44 | private final Secret mAdvinstLicense; 45 | private final boolean mEnablePowerShell; 46 | 47 | @DataBoundConstructor 48 | public AdvinstInstaller(final String label, final String advinstVersion, final Secret advinstLicense, 49 | final boolean advinstEnablePowerShell) { 50 | super(label); 51 | this.mAdvinstVersion = Util.fixEmptyAndTrim(advinstVersion); 52 | this.mAdvinstLicense = advinstLicense; 53 | this.mEnablePowerShell = advinstEnablePowerShell; 54 | } 55 | 56 | public String getAdvinstVersion() { 57 | return mAdvinstVersion; 58 | } 59 | 60 | public Secret getAdvinstLicense() { 61 | return mAdvinstLicense; 62 | } 63 | 64 | public boolean getAdvinstEnablePowerShell() { 65 | return mEnablePowerShell; 66 | } 67 | 68 | @Override 69 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 70 | public FilePath performInstallation(final ToolInstallation tool, final Node node, final TaskListener listener) 71 | throws IOException, InterruptedException { 72 | 73 | AdvinstVersions versions = new AdvinstVersions(); 74 | if (versions.isDeprecated(mAdvinstVersion)) { 75 | throw new InstallationFailedException( 76 | Messages.ERROR_ADVINST_DEPRECATED_VERSION(versions.getMinimumAllowedVersion(), mAdvinstVersion)); 77 | } 78 | 79 | // Gather properties for the node to install on 80 | VirtualChannel channel = node.getChannel(); 81 | if (null == channel) { 82 | throw new InstallationFailedException(Messages.ERR_ADVINST_INSTALL_FAILED()); 83 | } 84 | String[] properties = channel.call(new GetSystemProperties("os.name", "os.version")); 85 | 86 | // Verify the targe os is Windows. 87 | if (!properties[0].toLowerCase().contains("windows")) { 88 | throw new InstallationFailedException(Messages.ERR_ADVINST_UNSUPPORTED_OS()); 89 | } 90 | 91 | // Verify the target OS version is higher that 6.1 92 | if (kMinimumWindowsOsVersion.compareTo(new VersionNumber(properties[1])) > 0) { 93 | throw new InstallationFailedException(Messages.ERR_ADVINST_UNSUPPORTED_OS_VERSION()); 94 | } 95 | 96 | final FilePath advinstRootPath = preferredLocation(tool, node); 97 | if (!isUpToDate(advinstRootPath, node)) { 98 | final String downloadUrl = getAdvinstDownloadUrl(node); 99 | final String message = Messages.MSG_ADVINST_INSTALL(downloadUrl, advinstRootPath, node.getDisplayName()); 100 | listener.getLogger().append(message); 101 | 102 | try (FilePathAutoDeleter advistRootPathDeleter = new FilePathAutoDeleter(advinstRootPath)) { 103 | // Download the advinst.msi in the working dir temp folder. 104 | try (FilePathAutoDeleter tempDownloadDir = new FilePathAutoDeleter( 105 | node.getRootPath().createTempDir("tmpAdvinstDld", null))) { 106 | 107 | FilePath tempDownloadFile = tempDownloadDir.getFilePath().child("advinst.msi"); 108 | 109 | if (!downloadFile(downloadUrl, tempDownloadFile, listener)) { 110 | throw new InstallationFailedException(Messages.ERR_ADVINST_DOWNLOAD_FAILED(downloadUrl, tempDownloadFile)); 111 | } 112 | 113 | if (!extractMSI(tempDownloadFile, advinstRootPath, node, listener)) { 114 | throw new InstallationFailedException(Messages.ERR_ADVINST_EXTRACT_FAILED(downloadUrl, advinstRootPath)); 115 | } 116 | } 117 | advistRootPathDeleter.release(); 118 | } 119 | } 120 | 121 | FilePath advinstComPath = advinstRootPath.child(AdvinstInstallation.advinstComSubPath); 122 | if (advinstComPath.exists()) { 123 | if (!registerAdvinst(advinstComPath, mAdvinstLicense, node, listener)) { 124 | throw new InstallationFailedException(Messages.ERR_ADVINST_REGISTER_FAILED()); 125 | } 126 | 127 | if (!enablePowerShell(advinstComPath, mEnablePowerShell, node, listener)) { 128 | throw new InstallationFailedException(Messages.ERR_ADVINST_REGISTER_COM_FAILED()); 129 | } 130 | 131 | } 132 | return advinstRootPath; 133 | } 134 | 135 | private boolean isUpToDate(final FilePath expectedRoot, final Node node) throws IOException, InterruptedException { 136 | 137 | FilePath advinstComPath = expectedRoot.child(AdvinstInstallation.advinstComSubPath); 138 | // Check if the advinst executable exists. 139 | if (!advinstComPath.exists()) { 140 | return false; 141 | } 142 | 143 | return true; 144 | } 145 | 146 | private boolean downloadFile(final String fileURL, final FilePath targetFile, final TaskListener listener) 147 | throws IOException { 148 | InputStream is = null; 149 | OutputStream os = null; 150 | 151 | try { 152 | final URL url = new URL(fileURL); 153 | final URLConnection conn = url.openConnection(); 154 | 155 | conn.setUseCaches(false); 156 | is = conn.getInputStream(); 157 | listener.getLogger().append(Messages.MSG_ADVINST_DOWNLOAD_PROGRESS(fileURL, targetFile.getRemote())); 158 | os = targetFile.write(); 159 | final int bufferSize = 8192; 160 | final byte[] buf = new byte[bufferSize]; 161 | int i = 0; 162 | while ((i = is.read(buf)) != -1) { 163 | os.write(buf, 0, i); 164 | } 165 | } catch (final Exception e) { 166 | listener.error(Messages.ERR_ADVINST_DOWNLOAD_FAILED(fileURL, e.getMessage())); 167 | return false; 168 | } finally { 169 | if (is != null) { 170 | is.close(); 171 | } 172 | if (os != null) { 173 | os.close(); 174 | } 175 | } 176 | return true; 177 | } 178 | 179 | private boolean extractMSI(final FilePath msiPath, final FilePath targetDir, final Node node, 180 | final TaskListener listener) throws IOException, InterruptedException { 181 | 182 | Launcher launcher = node.createLauncher(listener); 183 | ArgumentListBuilder args = new ArgumentListBuilder(); 184 | 185 | args.add("cmd.exe"); 186 | args.add("/c"); 187 | args.addQuoted(String.format("msiexec /a \"%s\" TARGETDIR=\"%s\" /qn", msiPath.getRemote(), targetDir.getRemote())); 188 | 189 | ProcStarter ps = launcher.new ProcStarter(); 190 | ps = ps.cmds(args).stdout(listener); 191 | ps = ps.masks(null); 192 | Proc proc = launcher.launch(ps); 193 | int retcode = proc.join(); 194 | return retcode == 0; 195 | } 196 | 197 | @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") 198 | private boolean registerAdvinst(final FilePath advinstPath, final Secret licenseID, final Node node, 199 | final TaskListener listener) throws IOException, InterruptedException { 200 | 201 | String plainLicenseId = Secret.toString(licenseID); 202 | if (plainLicenseId.isEmpty()) { 203 | return true; 204 | } 205 | 206 | final FilePath advinstExe = advinstPath.sibling("advinst.exe"); 207 | final VersionNumber advinstVersion = new VersionNumber( 208 | node.getChannel().call(new GetWin32FileVersion(advinstExe.getRemote()))); 209 | 210 | String registerCommand = "/RegisterCI"; 211 | if (advinstVersion.isOlderThan(kAdvinstRegVersionSwitch)) { 212 | registerCommand = "/Register"; 213 | } 214 | 215 | Launcher launcher = node.createLauncher(listener); 216 | ArgumentListBuilder args = new ArgumentListBuilder(); 217 | args.add(advinstPath.getRemote(), registerCommand, plainLicenseId); 218 | ProcStarter ps = launcher.new ProcStarter(); 219 | ps = ps.cmds(args); 220 | ps = ps.masks(false, false, true); 221 | ps = ps.stdout(listener); 222 | Proc proc = launcher.launch(ps); 223 | int retcode = proc.join(); 224 | return retcode == 0; 225 | } 226 | 227 | private String getAdvinstDownloadUrl(final Node node) { 228 | String downloadUrl; 229 | 230 | EnvVars envVars = new EnvVars(); 231 | EnvironmentVariablesNodeProperty env = node.getNodeProperties().get(EnvironmentVariablesNodeProperty.class); 232 | if (env != null) { 233 | envVars.putAll(env.getEnvVars()); 234 | } 235 | if (envVars.containsKey(kAdvinstURLEnvVar)) { 236 | downloadUrl = envVars.get(kAdvinstURLEnvVar); 237 | } else { 238 | downloadUrl = String.format(kAdvinstUrlTemplate, this.mAdvinstVersion); 239 | } 240 | 241 | return downloadUrl; 242 | } 243 | 244 | private boolean enablePowerShell(final FilePath advinstPath, final boolean enablePowerShell, final Node node, 245 | final TaskListener listener) throws IOException, InterruptedException { 246 | 247 | if (!enablePowerShell) { 248 | return true; 249 | } 250 | 251 | String registerCommand = "/REGSERVER"; 252 | Launcher launcher = node.createLauncher(listener); 253 | ArgumentListBuilder args = new ArgumentListBuilder(); 254 | args.add(advinstPath.getRemote(), registerCommand); 255 | ProcStarter ps = launcher.new ProcStarter(); 256 | ps = ps.cmds(args); 257 | ps = ps.stdout(listener); 258 | Proc proc = launcher.launch(ps); 259 | int retcode = proc.join(); 260 | return retcode == 0; 261 | } 262 | 263 | @Extension 264 | public static final class DescriptorImpl extends ToolInstallerDescriptor { 265 | 266 | @Override 267 | public String getDisplayName() { 268 | return Messages.MSG_ADVINST_INSTALL_FROM_WEBSITE(); 269 | } 270 | 271 | @Override 272 | public boolean isApplicable(final Class toolType) { 273 | return toolType == AdvinstInstallation.class; 274 | } 275 | 276 | public FormValidation doCheckAdvinstVersion(final @QueryParameter String value) 277 | throws IOException, ServletException { 278 | if (value == null || value.length() == 0) { 279 | return FormValidation.error(Messages.ERR_REQUIRED()); 280 | } 281 | 282 | return FormValidation.ok(); 283 | } 284 | } 285 | 286 | /** Returns the values of the given Java system properties. */ 287 | private static class GetSystemProperties extends MasterToSlaveCallable { 288 | private static final long serialVersionUID = 1L; 289 | 290 | private final String[] properties; 291 | 292 | GetSystemProperties(final String... properties) { 293 | this.properties = properties; 294 | } 295 | 296 | public String[] call() { 297 | String[] values = new String[properties.length]; 298 | for (int i = 0; i < properties.length; i++) { 299 | values[i] = System.getProperty(properties[i]); 300 | } 301 | return values; 302 | } 303 | } 304 | 305 | private static class GetWin32FileVersion extends MasterToSlaveCallable { 306 | private static final long serialVersionUID = 1L; 307 | private final String filePath; 308 | 309 | GetWin32FileVersion(final String filePath) { 310 | this.filePath = filePath; 311 | } 312 | 313 | public String call() { 314 | VS_FIXEDFILEINFO verInfo = VersionUtil.getFileVersionInfo(this.filePath); 315 | final String verString = String.format("%d.%d.%d.%d", verInfo.getProductVersionMajor(), 316 | verInfo.getProductVersionMinor(), verInfo.getProductVersionRevision(), verInfo.getProductVersionBuild()); 317 | return verString; 318 | } 319 | } 320 | 321 | // Extend IOException so we can throw and stop the build if installation fails 322 | static class InstallationFailedException extends IOException { 323 | 324 | private static final long serialVersionUID = -1714895928033107556L; 325 | 326 | InstallationFailedException(final String message) { 327 | super(message); 328 | } 329 | } 330 | 331 | static class FilePathAutoDeleter implements AutoCloseable { 332 | private FilePath mFilePath; 333 | 334 | FilePath getFilePath() { 335 | return mFilePath; 336 | } 337 | 338 | public void release() { 339 | mFilePath = null; 340 | } 341 | 342 | FilePathAutoDeleter(final FilePath filePath) { 343 | this.mFilePath = filePath; 344 | } 345 | 346 | @Override 347 | public void close() throws IOException, InterruptedException { 348 | 349 | if (null == mFilePath || !mFilePath.exists()) { 350 | return; 351 | } 352 | 353 | if (mFilePath.isDirectory()) { 354 | mFilePath.deleteRecursive(); 355 | } else { 356 | mFilePath.delete(); 357 | } 358 | } 359 | 360 | } 361 | 362 | } 363 | --------------------------------------------------------------------------------