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 - - - "21" AND JAVA_EXE_VERSION <> "25")]]> - "21" AND JAVA_EXE_VERSION <> "25")]]> - "21" AND JAVA_EXE_VERSION <> "25")]]> - 1 - - - - 1 - - - - - - - - - - 1 - 1 - - - - - - - 1 - - - 1 - 1 - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - 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 @@ - - - -