();
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 | 
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 | 
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 extends AbstractProject> 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 extends ToolInstallation> 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 |
--------------------------------------------------------------------------------