diff --git a/msi/build/.gitignore b/msi/build/.gitignore
index 04844604..ca8c0f5b 100644
--- a/msi/build/.gitignore
+++ b/msi/build/.gitignore
@@ -4,3 +4,4 @@ packages/
tmp/
bin/
obj/
+.vs/
diff --git a/msi/build/Update-JenkinsVersion.ps1 b/msi/build/Additional/Update-JenkinsVersion.ps1
similarity index 100%
rename from msi/build/Update-JenkinsVersion.ps1
rename to msi/build/Additional/Update-JenkinsVersion.ps1
diff --git a/msi/build/JenkinsCA/CustomAction.config b/msi/build/JenkinsCA/CustomAction.config
new file mode 100644
index 00000000..8894c671
--- /dev/null
+++ b/msi/build/JenkinsCA/CustomAction.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/msi/build/JenkinsCA/CustomActions.cs b/msi/build/JenkinsCA/CustomActions.cs
new file mode 100644
index 00000000..c2a17adb
--- /dev/null
+++ b/msi/build/JenkinsCA/CustomActions.cs
@@ -0,0 +1,183 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Security;
+using System.Security.Principal;
+using System.Text.RegularExpressions;
+using WixToolset.Dtf.WindowsInstaller;
+
+namespace JenkinsCA {
+ public static class CustomActions {
+ private static ActionResult LogException(Session session, Exception ex) {
+ using (Record record = new Record()) {
+ record.FormatString = "Error occurred during installer: [0]";
+ record.SetString(1, ex.Message);
+ session.Message(InstallMessage.FatalExit, record);
+ }
+ return ActionResult.Failure;
+ }
+
+ [CustomAction]
+ public static ActionResult BackupJenkinsXmlFile(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ DirectoryInfo jenkinsDirPath = new DirectoryInfo(session["JENKINSDIR"]);
+ if (jenkinsDirPath.Exists) {
+ FileInfo srcPath = new FileInfo(Path.Combine(jenkinsDirPath.FullName, "jenkins.xml"));
+ if (srcPath.Exists) {
+ FileInfo dstPath = new FileInfo(srcPath.FullName + ".backup");
+ int suffix = 0;
+ while (dstPath.Exists) {
+ dstPath = new FileInfo(srcPath.FullName + string.Format(".backup_{0}", suffix));
+ suffix++;
+ }
+ srcPath.CopyTo(dstPath.FullName, true);
+ }
+ }
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult ValidateJavaHome(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ DirectoryInfo javaHome = new DirectoryInfo(session["JAVA_HOME"]);
+ session["JAVA_EXE_FOUND"] = "0";
+ session["JAVA_EXE_VERSION"] = "";
+ if (javaHome.Exists) {
+ FileInfo javaExe = new FileInfo(Path.Combine(javaHome.FullName, Path.Combine("bin", "java.exe")));
+ if (javaExe.Exists) {
+ session["JAVA_EXE_FOUND"] = "1";
+ FileVersionInfo javaExeVersionInfo = FileVersionInfo.GetVersionInfo(javaExe.FullName);
+ string javaExeVersion = javaExeVersionInfo.FileVersion;
+ session["JAVA_EXE_VERSION"] = javaExeVersion.Split(new char[] { '.' })[0];
+ }
+ }
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult StripJenkinsDir(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ session["JENKINSDIR_STRIPPED"] = session["JENKINSDIR"].TrimEnd('\\');
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult StringTrim(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ string whiteSpaces = session["STRING_TRIM_WHITESPACES"];
+ if (string.IsNullOrEmpty(whiteSpaces)) {
+ whiteSpaces = " \t";
+ }
+ session["STRING_TRIM_RESULT"] = session["STRING_TRIM_INPUT"].Trim(whiteSpaces.ToCharArray());
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult RegexMatch(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ string inputString = session["REGEX_MATCH_INPUT_STRING"];
+ string patternString = session["REGEX_MATCH_EXPRESSION"];
+ session["REGEX_MATCH_RESULT"] = Regex.IsMatch(inputString, patternString, RegexOptions.None) ? "1" : "0";
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult CheckCredentials(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ using (ImpersonatedSession impersonatedSession = new ImpersonatedSession(session)) {
+ session["LOGON_VALID"] = "0";
+ session["LOGON_ERROR"] = "";
+ session.Log("Checking credentials");
+
+ string username = session["LOGON_USERNAME"];
+ string domain = Utilities.SplitUsername(ref username);
+ session.Log(string.Format("username={0}, domain={1}", username, domain));
+ SecureString password = Utilities.SecureStringFromString(session["LOGON_PASSWORD"]);
+ Windows.LOGON32_LOGON_TYPE logonType = Utilities.GetPropertyValue(session, "LOGON_TYPE", Windows.LOGON32_LOGON_TYPE.LOGON32_LOGON_NETWORK);
+
+ Utilities.LogInfo(session, "CheckCredentials", "Userame: {0}", username);
+ Utilities.LogInfo(session, "CheckCredentials", "Password: {0}", password.Length > 0 ? "********" : "");
+ try {
+ Windows.LogonUser(domain, username, password, logonType);
+ session["LOGON_VALID"] = "1";
+ } catch (Win32Exception ex) {
+ session["LOGON_ERROR"] = ex.Message;
+ }
+ }
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult CheckMembership(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ using (ImpersonatedSession impersonatedSession = new ImpersonatedSession(session)) {
+ Utilities.LogInfo(session, "CheckMembership", "Checking membership");
+ session["LOGON_IS_MEMBER"] = "0";
+
+ SecurityIdentifier sid = new SecurityIdentifier(session["SID"]);
+ session["LOGON_IS_MEMBER"] = Windows.IsMember(sid) ? "1" : "0";
+ }
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+
+ [CustomAction]
+ public static ActionResult BindSocket(Session session) {
+ ActionResult result = ActionResult.Success;
+ try {
+ session["TCPIP_BIND_SUCCEEDED"] = "0";
+ int port = int.Parse(session["TCP_PORT"]);
+ Utilities.CheckBool(port >= 0 && port <= 65536, "Invalid port specified");
+ string ipAddress = session["TCP_IPADDRESS"];
+ if (string.IsNullOrEmpty(ipAddress)) {
+ ipAddress = "127.0.0.1";
+ }
+
+ // we may have a hostname instead
+ if (!IPAddress.TryParse(ipAddress, out IPAddress address)) {
+ IPHostEntry entry = Dns.GetHostEntry(ipAddress);
+ address = entry.AddressList[0];
+ }
+
+ IPEndPoint endPoint = new IPEndPoint(address, port);
+ using (Socket socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.IP)) {
+ socket.Bind(endPoint);
+ session["TCPIP_BIND_SUCCEEDED"] = "1";
+ }
+ } catch (Exception ex) {
+ result = LogException(session, ex);
+ }
+ return result;
+ }
+ }
+}
diff --git a/msi/build/JenkinsCA/ImpersonatedSession.cs b/msi/build/JenkinsCA/ImpersonatedSession.cs
new file mode 100644
index 00000000..058d30d3
--- /dev/null
+++ b/msi/build/JenkinsCA/ImpersonatedSession.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Security;
+using WixToolset.Dtf.WindowsInstaller;
+
+namespace JenkinsCA {
+ public class ImpersonatedSession : IDisposable {
+ Windows.WindowsImpersonation windowsImpersonation;
+ readonly Session session;
+
+ public ImpersonatedSession(Session session) {
+ this.session = session;
+ string username = session["LOGON_USERNAME"];
+ string domain = Utilities.SplitUsername(ref username);
+ SecureString password = new SecureString();
+ foreach(char val in session["LOGON_PASSWORD"]) {
+ password.AppendChar(val);
+ }
+ Windows.LOGON32_LOGON_TYPE logonType = Utilities.GetPropertyValue(session, "LOGON_TYPE", Windows.LOGON32_LOGON_TYPE.LOGON32_LOGON_NETWORK);
+ windowsImpersonation = new Windows.WindowsImpersonation(domain, username, password, logonType);
+ windowsImpersonation.ImpersonateUser();
+ }
+
+ public string this[string property] {
+ get {
+ return session[property];
+ }
+ set {
+ session[property] = value;
+ }
+ }
+
+ public void Dispose() {
+ if(windowsImpersonation != null) {
+ windowsImpersonation.Dispose();
+ windowsImpersonation = null;
+ }
+ }
+ }
+}
diff --git a/msi/build/JenkinsCA/JenkinsCA.csproj b/msi/build/JenkinsCA/JenkinsCA.csproj
new file mode 100644
index 00000000..130f00a8
--- /dev/null
+++ b/msi/build/JenkinsCA/JenkinsCA.csproj
@@ -0,0 +1,19 @@
+
+
+ net48
+ Jenkins Custom Actions for Wix Toolset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/msi/build/JenkinsCA/Utilities.cs b/msi/build/JenkinsCA/Utilities.cs
new file mode 100644
index 00000000..823acff2
--- /dev/null
+++ b/msi/build/JenkinsCA/Utilities.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Security;
+using System.Collections.Generic;
+using WixToolset.Dtf.WindowsInstaller;
+
+namespace JenkinsCA {
+ public static class Utilities {
+
+ public static T GetPropertyValue(Session session, string property, T @default) where T : struct, Enum {
+ string value = session[property];
+ if(value.IndexOf('|') >= 0) {
+ CheckHasFlags();
+ string[] items = value.Split('|');
+ T res = default;
+ foreach(string item in items) {
+ if(Enum.TryParse(item, out T curr)) {
+ uint val = Convert.ToUInt32(res) | Convert.ToUInt32(curr);
+ res = (T)Convert.ChangeType(val, typeof(T));
+ }
+ }
+ return res;
+ } else {
+ if(Enum.TryParse(value, out T result)) {
+ return result;
+ }
+ }
+ return @default;
+ }
+
+ public static void CheckBool(bool condition, string message) {
+ if(!condition) {
+ throw new Exception(message);
+ }
+ }
+
+ public static void LogInfo(Session session, string category, string format, params object[] args) {
+
+ }
+
+ public static SecureString SecureStringFromString(string input) {
+ SecureString res = new SecureString();
+ foreach(char c in input) {
+ res.AppendChar(c);
+ }
+ return res;
+ }
+
+ public static string SplitUsername(ref string username) {
+ string domain = ".";
+ if(username.IndexOf('\\') >= 0) {
+ string[] items = username.Split('\\');
+ domain = items[0];
+ username = items[1];
+ } else if(username.IndexOf('@') >= 0) {
+ string[] items = username.Split('@');
+ domain = items[1];
+ username = items[0];
+ }
+ return domain;
+ }
+
+ /// Determines whether the enumerated flag value has the specified flag set.
+ /// The enumerated type.
+ /// The enumerated flag value.
+ /// The flag value to check.
+ /// true if is flag set; otherwise, false.
+ public static bool IsFlagSet(this T flags, T flag) where T : struct, Enum {
+ CheckHasFlags();
+ var flagValue = Convert.ToInt64(flag);
+ return (Convert.ToInt64(flags) & flagValue) == flagValue;
+ }
+
+ /// Checks if represents an enumeration and throws an exception if not.
+ /// The to validate.
+ ///
+ private static void CheckHasFlags() where T : struct, Enum {
+ if (!IsFlags())
+ throw new ArgumentException($"Type '{typeof(T).FullName}' doesn't have the 'Flags' attribute");
+ }
+
+ /// Determines whether this enumerations has the set.
+ /// The enumerated type.
+ /// true if this instance has the set; otherwise, false.
+ private static bool IsFlags() where T : struct, Enum {
+ return Attribute.IsDefined(typeof(T), typeof(FlagsAttribute));
+ }
+
+ /// Adds an offset to the value of a pointer.
+ /// The pointer to add the offset to.
+ /// The offset to add.
+ /// A new pointer that reflects the addition of to .
+ public static IntPtr OffsetWith(this IntPtr pointer, long offset) {
+ // On 64bits computer, we need ToInt64() to prevent exceptions when process is using more than int.MaxValue of memory.
+ //
+ // On 32bits computer, the use of ToInt64() has no effect except when the pointer moved by offset would make it past the
+ // int.MaxValue barrier. We still need to fail in that case so let's make the IntPtr constructor fail with a meaningful error message.
+ return new IntPtr(pointer.ToInt64() + offset);
+ }
+ }
+}
diff --git a/msi/build/JenkinsCA/Windows.cs b/msi/build/JenkinsCA/Windows.cs
new file mode 100644
index 00000000..b1aad4a6
--- /dev/null
+++ b/msi/build/JenkinsCA/Windows.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Diagnostics;
+using System.Security;
+using System.Security.Principal;
+using System.Security.Permissions;
+using System.Runtime.InteropServices;
+using System.ComponentModel;
+
+namespace JenkinsCA
+{
+ public class Windows
+ {
+ // constants from winbase.h
+ public enum LOGON32_LOGON_TYPE : int {
+ LOGON32_LOGON_INTERACTIVE = 2,
+ LOGON32_LOGON_NETWORK = 3,
+ LOGON32_LOGON_BATCH = 4,
+ LOGON32_LOGON_SERVICE = 5,
+ LOGON32_LOGON_UNLOCK = 7,
+ LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
+ LOGON32_LOGON_NEW_CREDENTIALS = 9,
+ }
+
+ internal enum LOGON32_PROVIDER_TYPE : int {
+ LOGON32_PROVIDER_DEFAULT = 0,
+ LOGON32_PROVIDER_WINNT35 = 1,
+ LOGON32_PROVIDER_WINNT40 = 2,
+ LOGON32_PROVIDER_WINNT50 = 3,
+ }
+
+ private enum SECURITY_IMPERSONATION_LEVEL : int
+ {
+ SecurityAnonymous = 0,
+ SecurityIdentification = 1,
+ SecurityImpersonation = 2,
+ SecurityDelegation = 3,
+ }
+
+ [DllImport("advapi32", EntryPoint = "LogonUserW", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern int LogonUser(string username, string domain, IntPtr password, LOGON32_LOGON_TYPE logonType, LOGON32_PROVIDER_TYPE logonProvider, ref IntPtr token);
+
+ [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern int DuplicateToken(IntPtr existingTokenHandle, SECURITY_IMPERSONATION_LEVEL impersonationLevel, ref IntPtr duplicateTokenHandle);
+
+ [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern bool RevertToSelf();
+
+ [DllImport("kernel32", CharSet = CharSet.Auto)]
+ private static extern bool CloseHandle(IntPtr handle);
+
+ [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ private static extern bool CheckTokenMembership(IntPtr tokenHandle, byte[] sidToCheck, ref bool isMember);
+
+ ///
+ ///
+ [DllImport("kernel32", SetLastError = true)]
+ public static extern IntPtr GlobalLock(IntPtr hMem);
+
+ /// The GlobalUnlock function decrements the lock count associated with a memory object.
+ ///
+ ///
+ [DllImport("kernel32", SetLastError = true)]
+ public static extern bool GlobalUnlock(IntPtr hMem);
+
+ [DebuggerNonUserCode]
+ public static void LogonUser(string domain, string username, SecureString password, LOGON32_LOGON_TYPE logonType) {
+ IntPtr token = IntPtr.Zero;
+ IntPtr passwordHandle = Marshal.SecureStringToGlobalAllocUnicode(password);
+ if (LogonUser(username, domain, passwordHandle, logonType, LOGON32_PROVIDER_TYPE.LOGON32_PROVIDER_DEFAULT, ref token) == 0) {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ }
+
+ public static bool IsMember(SecurityIdentifier sid) {
+ byte[] binaryForm = new byte[sid.BinaryLength];
+ sid.GetBinaryForm(binaryForm, 0);
+ bool isMember = false;
+ if (!CheckTokenMembership(IntPtr.Zero, binaryForm, ref isMember)) {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ return isMember;
+ }
+
+ public class WindowsImpersonation : IDisposable {
+ private WindowsImpersonationContext impersonationContext;
+
+ public WindowsImpersonation(string domain, string username, SecureString password, LOGON32_LOGON_TYPE logonType = LOGON32_LOGON_TYPE.LOGON32_LOGON_NETWORK) {
+ Domain = domain;
+ Username = username;
+ Password = password;
+ LogonType = logonType;
+ }
+
+ public string Username { get; set; }
+
+ public string Domain { get; set; }
+
+ [DebuggerNonUserCode]
+ public SecureString Password { private get; set; }
+
+ public LOGON32_LOGON_TYPE LogonType { get; set; }
+
+ [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
+ public bool ImpersonateUser() {
+ bool result = false;
+ IntPtr token = IntPtr.Zero;
+ IntPtr tokenDuplicate = IntPtr.Zero;
+ ImpersonationException exception = null;
+
+ if (RevertToSelf()) {
+ IntPtr password = Marshal.SecureStringToGlobalAllocUnicode(Password);
+ int rc = LogonUser(Username, Domain, password, LOGON32_LOGON_TYPE.LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_TYPE.LOGON32_PROVIDER_DEFAULT, ref token);
+ if (rc != 0) {
+ if (DuplicateToken(token, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, ref tokenDuplicate) != 0) {
+ impersonationContext = new WindowsIdentity(tokenDuplicate).Impersonate();
+ if (impersonationContext != null) {
+ result = true;
+ }
+ }
+ } else {
+ exception = new ImpersonationException(Domain, Username);
+ }
+
+ if (password != IntPtr.Zero) {
+ Marshal.ZeroFreeGlobalAllocUnicode(password);
+ }
+ }
+
+ if (tokenDuplicate != IntPtr.Zero) {
+ CloseHandle(tokenDuplicate);
+ tokenDuplicate = IntPtr.Zero;
+ }
+
+ if (token != IntPtr.Zero) {
+ CloseHandle(token);
+ token = IntPtr.Zero;
+ }
+
+ if (exception != null) {
+ throw exception;
+ }
+ return result;
+ }
+
+ [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
+ [DebuggerNonUserCode]
+ public void Undo() {
+ impersonationContext?.Undo();
+ impersonationContext = null;
+ }
+
+ [DebuggerNonUserCode]
+ public static object InvokeAsUser(string domain, string username, SecureString password, Delegate methodToCall, params object[] args) {
+ object result = null;
+ using (WindowsImpersonation impersonation = new WindowsImpersonation(domain, username, password)) {
+ impersonation.ImpersonateUser();
+ result = methodToCall.DynamicInvoke(args);
+ }
+ return result;
+ }
+
+ ~WindowsImpersonation() {
+ Dispose(false);
+ }
+
+ public void Dispose() {
+ Dispose(true);
+ System.GC.SuppressFinalize(this);
+ }
+
+ private bool disposed;
+
+ protected void Dispose(bool disposing) {
+ if (!disposed) {
+ if (disposing) {
+ if (impersonationContext != null) {
+ impersonationContext.Undo();
+ impersonationContext.Dispose();
+ }
+ }
+ impersonationContext = null;
+ }
+ disposed = true;
+ }
+
+
+ public class ImpersonationException : Exception {
+ public string Username { get; private set; }
+
+ public string Domain { get; private set; }
+
+ public ImpersonationException(string domain, string username) : base(string.Format("Impersonation failure: {0}\\{1}", domain, username)) {
+ Domain = domain;
+ Username = username;
+ }
+ }
+ }
+ }
+}
diff --git a/msi/build/Setup/GenericErrorDlg.wxs b/msi/build/Setup/GenericErrorDlg.wxs
new file mode 100644
index 00000000..8ea2b62b
--- /dev/null
+++ b/msi/build/Setup/GenericErrorDlg.wxs
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/msi/build/Setup/JavaBrowseDlg.wxs b/msi/build/Setup/JavaBrowseDlg.wxs
new file mode 100644
index 00000000..658c36a0
--- /dev/null
+++ b/msi/build/Setup/JavaBrowseDlg.wxs
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/msi/build/Setup/JavaHomeDlg.wxs b/msi/build/Setup/JavaHomeDlg.wxs
new file mode 100644
index 00000000..05437c86
--- /dev/null
+++ b/msi/build/Setup/JavaHomeDlg.wxs
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/msi/build/Setup/Jenkins.wxs b/msi/build/Setup/Jenkins.wxs
new file mode 100644
index 00000000..edcec87c
--- /dev/null
+++ b/msi/build/Setup/Jenkins.wxs
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/msi/build/License.rtf b/msi/build/Setup/License.rtf
similarity index 100%
rename from msi/build/License.rtf
rename to msi/build/Setup/License.rtf
diff --git a/msi/build/Setup/ServiceCredDlg.wxs b/msi/build/Setup/ServiceCredDlg.wxs
new file mode 100644
index 00000000..0350c4aa
--- /dev/null
+++ b/msi/build/Setup/ServiceCredDlg.wxs
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/msi/build/Setup/ServicePortDlg.wxs b/msi/build/Setup/ServicePortDlg.wxs
new file mode 100644
index 00000000..5fc60ab3
--- /dev/null
+++ b/msi/build/Setup/ServicePortDlg.wxs
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/msi/build/jenkins.wixproj b/msi/build/Setup/Setup.wixproj
similarity index 58%
rename from msi/build/jenkins.wixproj
rename to msi/build/Setup/Setup.wixproj
index 27ba8c9b..5530cac0 100644
--- a/msi/build/jenkins.wixproj
+++ b/msi/build/Setup/Setup.wixproj
@@ -1,62 +1,51 @@
-
-
-
+
- Debug
- x64
- 3.5
- {49c7ae2b-d9d1-4b32-9d11-474f1be86658}
- 2.0
+ x64;arm64
False
jenkins-$(DisplayVersion)
jenkins-$(DisplayVersion)-stable
+ 2.455
Package
- true
True
- false
- 1076
en-US
+ ICE63
-
-
+
+
-
- 1 && int.TryParse(items[1], out minor) && minor > 255) {
- EncodedVersion = string.Format("{0}.255.{1}", items[0], minor * 10);
- } else {
- EncodedVersion = string.Format("{0}.0", DisplayVersion);
- }
- } else {
- int minor = 0;
- if(int.TryParse(items[1], out minor) && minor > 255) {
- EncodedVersion = string.Format("{0}.255.{1}", items[0], (minor * 10) + int.Parse(items[2]));
+
+ 1 && int.TryParse(items[1], out minor) && minor > 255) {
+ EncodedVersion = string.Format("{0}.255.{1}", items[0], minor * 10);
+ } else {
+ EncodedVersion = string.Format("{0}.0", DisplayVersion);
+ }
} else {
- EncodedVersion = string.Format("{0}.{1}.{2}", items[0], items[1], int.Parse(items[2]));
+ int minor = 0;
+ if(int.TryParse(items[1], out minor) && minor > 255) {
+ EncodedVersion = string.Format("{0}.255.{1}", items[0], (minor * 10) + int.Parse(items[2]));
+ } else {
+ EncodedVersion = string.Format("{0}.{1}.{2}", items[0], items[1], int.Parse(items[2]));
+ }
}
- }
- ]]>
-
-
+ ]]>
+
+
-
-
-
-
-
-
-
+
+
-
+
@@ -69,55 +58,55 @@
$(DisplayVersion)
- tmp\jenkins.war
+ $(SolutionDir)\tmp\jenkins.war
$(WAR)
-
+
Jenkins
$(ProductName)
-
+
Jenkins Automation Server
$(ProductSummary)
-
+
Jenkins Project
$(ProductVendor)
-
+
Jenkins
$(ArtifactName)
-
-
+
+
jenkins.ico
$(InstallerIco)
-
+
jenkins.bmp
$(DialogBmp)
-
+
banner.bmp
@@ -140,39 +129,14 @@
- bin\$(Configuration)\
- obj\$(Configuration)\
+ bin\$(Configuration)\$(Platform)\
+ obj\$(Configuration)\$(Platform)\
-
+
+
+
-
-
-
-
-
-
-
- $(WixExtDir)\WixUIExtension.dll
- WixUIExtension
-
-
- $(WixExtDir)\WixNetFxExtension.dll
- WixNetFxExtension
-
-
- $(WixExtDir)\WixUtilExtension.dll
- WixUtilExtension
-
-
- .\msiext-1.5\WixExtensions\WixCommonUIExtension.dll
- WixCommonUIExtension
-
-
- $(WixExtDir)\WixFirewallExtension.dll
- WixCommonUIExtension
-
-
-
+
diff --git a/msi/build/Setup/WixUI_Jenkins_InstallDir.wxs b/msi/build/Setup/WixUI_Jenkins_InstallDir.wxs
new file mode 100644
index 00000000..8e68a2ce
--- /dev/null
+++ b/msi/build/Setup/WixUI_Jenkins_InstallDir.wxs
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/msi/build/banner.bmp b/msi/build/Setup/banner.bmp
similarity index 100%
rename from msi/build/banner.bmp
rename to msi/build/Setup/banner.bmp
diff --git a/msi/build/Setup/bitmaps/Error.ico b/msi/build/Setup/bitmaps/Error.ico
new file mode 100644
index 00000000..752dcea7
Binary files /dev/null and b/msi/build/Setup/bitmaps/Error.ico differ
diff --git a/msi/build/Setup/bitmaps/Success.ico b/msi/build/Setup/bitmaps/Success.ico
new file mode 100644
index 00000000..ec39576b
Binary files /dev/null and b/msi/build/Setup/bitmaps/Success.ico differ
diff --git a/msi/build/Setup/en-US.wxl b/msi/build/Setup/en-US.wxl
new file mode 100644
index 00000000..a0edbdb4
--- /dev/null
+++ b/msi/build/Setup/en-US.wxl
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/msi/build/jenkins.bmp b/msi/build/Setup/jenkins.bmp
similarity index 100%
rename from msi/build/jenkins.bmp
rename to msi/build/Setup/jenkins.bmp
diff --git a/msi/build/jenkins.exe.config b/msi/build/Setup/jenkins.exe.config
similarity index 100%
rename from msi/build/jenkins.exe.config
rename to msi/build/Setup/jenkins.exe.config
diff --git a/msi/build/jenkins.ico b/msi/build/Setup/jenkins.ico
similarity index 100%
rename from msi/build/jenkins.ico
rename to msi/build/Setup/jenkins.ico
diff --git a/msi/build/build.ps1 b/msi/build/build.ps1
index a42187e2..4e7a8011 100644
--- a/msi/build/build.ps1
+++ b/msi/build/build.ps1
@@ -9,7 +9,8 @@ Param(
[String] $ArtifactName = $env:ARTIFACTNAME,
[String] $BannerBmp = '',
[String] $DialogBmp = '',
- [String] $InstallerIco = ''
+ [String] $InstallerIco = '',
+ [String[]] $Platforms = @('x64', 'arm64')
)
function Set-CodeSigningSignature {
@@ -75,11 +76,6 @@ if(!(Test-Path $tmpDir)) {
Get-ChildItem tmp\* | Remove-Item -Force
}
-if(!(Test-Path (Join-Path $PSScriptRoot 'msiext-1.5/WixExtensions/WixCommonUiExtension.dll'))) {
- Invoke-WebRequest -Uri "https://github.com/dblock/msiext/releases/download/1.5/msiext-1.5.zip" -OutFile (Join-Path $PSScriptRoot 'msiext-1.5.zip') -UseBasicParsing
- [IO.Compression.ZipFile]::ExtractToDirectory((Join-Path $PSScriptRoot 'msiext-1.5.zip'), $PSScriptRoot)
-}
-
Write-Host "Extracting components"
if($UseTracing) { Set-PSDebug -Trace 0 }
# get the components we need from the war file
@@ -105,10 +101,6 @@ if($UseTracing) { Set-PSDebug -Trace 1 }
$isLts = $JenkinsVersion.Split('.').Length -gt 2
-Write-Host "Restoring packages before build"
-# restore the Wix package
-& "./nuget.exe" restore -PackagesDirectory "packages"
-
Write-Host "Building MSI"
if($MSBuildPath -ne '') {
if($MSBuildPath.ToLower().EndsWith('msbuild.exe')) {
@@ -125,12 +117,14 @@ if($MSBuildPath -ne '') {
}
# Sign the Update-JenkinsVersion.ps1 script if we have PKCS files
-Copy-Item -Force -Path .\Update-JenkinsVersion.ps1 -Destination tmp
+Copy-Item -Force -Path .\Additional\Update-JenkinsVersion.ps1 -Destination tmp
Set-CodeSigningSignature -Path .\tmp\Update-JenkinsVersion.ps1 -JenkinsVersion $JenkinsVersion
-msbuild "jenkins.wixproj" /p:Stable="${isLts}" /p:WAR="${War}" /p:Configuration=Release /p:DisplayVersion=$JenkinsVersion /p:ProductName="${ProductName}" /p:ProductSummary="${ProductSummary}" /p:ProductVendor="${ProductVendor}" /p:ArtifactName="${ArtifactName}" /p:BannerBmp="${BannerBmp}" /p:DialogBmp="${DialogBmp}" /p:InstallerIco="${InstallerIco}"
+$Platforms | ForEach-Object {
+ msbuild "jenkins.sln" /p:Stable="${isLts}" /p:WAR="${War}" /p:Configuration=Release /p:DisplayVersion=$JenkinsVersion /p:ProductName="${ProductName}" /p:ProductSummary="${ProductSummary}" /p:ProductVendor="${ProductVendor}" /p:ArtifactName="${ArtifactName}" /p:BannerBmp="${BannerBmp}" /p:DialogBmp="${DialogBmp}" /p:InstallerIco="${InstallerIco}" /t:Restore /t:Build /p:Platform="$_"
+}
-Get-ChildItem .\bin\Release -Filter *.msi -Recurse |
+Get-ChildItem .\Setup\bin\Release -Filter *.msi -Recurse |
Foreach-Object {
Set-CodeSigningSignature -Path $($_.FullName) -JenkinsVersion $JenkinsVersion
diff --git a/msi/build/jenkins.sln b/msi/build/jenkins.sln
new file mode 100644
index 00000000..f13a0546
--- /dev/null
+++ b/msi/build/jenkins.sln
@@ -0,0 +1,51 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35527.113
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Setup", "Setup\Setup.wixproj", "{2C1A2B70-0C0C-48A9-8320-932578E4FFC5}"
+ ProjectSection(ProjectDependencies) = postProject
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67} = {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JenkinsCA", "JenkinsCA\JenkinsCA.csproj", "{5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|arm64 = Debug|arm64
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|arm64 = Release|arm64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Debug|Any CPU.Build.0 = Debug|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Debug|x64.ActiveCfg = Debug|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Debug|x64.Build.0 = Debug|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Debug|arm64.ActiveCfg = Debug|arm64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Debug|arm64.Build.0 = Debug|arm64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Release|Any CPU.ActiveCfg = Release|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Release|Any CPU.Build.0 = Release|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Release|x64.ActiveCfg = Release|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Release|x64.Build.0 = Release|x64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Release|arm64.ActiveCfg = Release|arm64
+ {2C1A2B70-0C0C-48A9-8320-932578E4FFC5}.Release|arm64.Build.0 = Release|arm64
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Debug|x64.Build.0 = Debug|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Debug|arm64.Build.0 = Debug|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Release|x64.ActiveCfg = Release|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Release|x64.Build.0 = Release|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Release|arm64.ActiveCfg = Release|Any CPU
+ {5303BC9B-4CE8-42FF-87E9-4E8D41AC6F67}.Release|arm64.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/msi/build/jenkins.wxs b/msi/build/jenkins.wxs
deleted file mode 100644
index ed6688e8..00000000
--- a/msi/build/jenkins.wxs
+++ /dev/null
@@ -1,486 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CMDLINE_INSTALLDIR
-
-
-
-
-
- CMDLINE_INSTALLDIR
-
-
-
-
-
-
-
-
-
-
- CMDLINE_PORT
-
-
-
-
-
- CMDLINE_PORT
-
-
-
-
-
-
-
-
-
-
- CMDLINE_JAVA_HOME
-
-
-
-
-
- CMDLINE_JAVA_HOME
-
-
-
-
-
-
-
-
-
-
- CMDLINE_SERVICE_USERNAME
-
-
-
-
-
- CMDLINE_SERVICE_USERNAME
-
-
-
-
-
-
-
-
-
-
- CMDLINE_SERVICE_PASSWORD
-
-
-
-
-
- CMDLINE_SERVICE_PASSWORD
-
-
-
-
-
-
-
-
-
-
-
-
-
- NOT PORTNUMBER
-
-
-
-
-
- NOT JENKINS_ROOT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- SERVICE_PASSWORD_ENC
- SERVICE_PASSWORD_ENC
- SERVICE_PASSWORD_ENC
-
- SERVICE_PASSWORD
- SERVICE_PASSWORD
-
- NOT REMOVE
-
- "ServiceLocalSystem" AND NOT REMOVE]]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- "1"]]>
-
- 1
-
- NOT Installed
-
- 1
- 1
- NOT WIXUI_DONTVALIDATEPATH
- "1"]]>
- 1
-
- WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"
- 1
- 1
-
-
-
- NOT Installed
- NOT Installed
-
- NOT Installed
- NOT Installed
-
- NOT Installed
- NOT Installed
-
- NOT Installed
- 1
-
- NOT Installed
- Installed
-
- 1
-
- 1
- 1
- 1
-
- NOT Installed AND NOT REMOVE
- < "StartService" AND NOT Installed AND NOT REMOVE]]>
-
-
-
-
-
-
- NOT JAVA_HOME
-
- NOT JAVA_HOME
-
-
-
-
diff --git a/msi/build/jenkins_en-US.wxl b/msi/build/jenkins_en-US.wxl
deleted file mode 100644
index e3ab6fe2..00000000
--- a/msi/build/jenkins_en-US.wxl
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- [ProductName] Setup
- Select Java home directory (JDK or JRE)
- Please select the path of a Java Development Kit or Java Runtime Environment. Only Java 21 and 25 are supported by Jenkins.
- &Change...
- Invalid Java Directory
- Failed to find compatible Java version (21 or 25) in [JAVA_HOME]
-
-
- Browse to the Java Home directory
- {\WixUI_Font_Title}Select Java folder
-
-
- Run service as LocalSystem (not recommended)
- Enables a firewall exception for the Java running Jenkins on port [PORTNUMBER] (not recommended).
- Starts the Jenkins service after install.
-
diff --git a/msi/build/nuget.exe b/msi/build/nuget.exe
deleted file mode 100644
index ccb2979c..00000000
Binary files a/msi/build/nuget.exe and /dev/null differ
diff --git a/msi/build/packages.config b/msi/build/packages.config
deleted file mode 100644
index 23f34ef1..00000000
--- a/msi/build/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-