Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions msi/build/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ packages/
tmp/
bin/
obj/
.vs/
8 changes: 8 additions & 0 deletions msi/build/JenkinsCA/CustomAction.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->

<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" />
</startup>
</configuration>
183 changes: 183 additions & 0 deletions msi/build/JenkinsCA/CustomActions.cs
Original file line number Diff line number Diff line change
@@ -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 ? "********" : "<blank>");
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;
}
}
}
39 changes: 39 additions & 0 deletions msi/build/JenkinsCA/ImpersonatedSession.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
19 changes: 19 additions & 0 deletions msi/build/JenkinsCA/JenkinsCA.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<Description>Jenkins Custom Actions for Wix Toolset</Description>
</PropertyGroup>

<ItemGroup>
<Content Include="CustomAction.config" CopyToOutputDirectory="PreserveNewest" />
<CustomActionContents Include="CustomActions.cs;ImpersonatedSession.cs;Utilities.cs;Windows.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="6.0.*"/>
</ItemGroup>

<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
</Project>
100 changes: 100 additions & 0 deletions msi/build/JenkinsCA/Utilities.cs
Original file line number Diff line number Diff line change
@@ -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<T>(Session session, string property, T @default) where T : struct, Enum {
string value = session[property];
if(value.IndexOf('|') >= 0) {
CheckHasFlags<T>();
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;
}

/// <summary>Determines whether the enumerated flag value has the specified flag set.</summary>
/// <typeparam name="T">The enumerated type.</typeparam>
/// <param name="flags">The enumerated flag value.</param>
/// <param name="flag">The flag value to check.</param>
/// <returns><c>true</c> if is flag set; otherwise, <c>false</c>.</returns>
public static bool IsFlagSet<T>(this T flags, T flag) where T : struct, Enum {
CheckHasFlags<T>();
var flagValue = Convert.ToInt64(flag);
return (Convert.ToInt64(flags) & flagValue) == flagValue;
}

/// <summary>Checks if <typeparamref name="T"/> represents an enumeration and throws an exception if not.</summary>
/// <typeparam name="T">The <see cref="Type"/> to validate.</typeparam>
/// <exception cref="System.ArgumentException"></exception>
private static void CheckHasFlags<T>() where T : struct, Enum {
if (!IsFlags<T>())
throw new ArgumentException($"Type '{typeof(T).FullName}' doesn't have the 'Flags' attribute");
}

/// <summary>Determines whether this enumerations has the <see cref="FlagsAttribute"/> set.</summary>
/// <typeparam name="T">The enumerated type.</typeparam>
/// <returns><c>true</c> if this instance has the <see cref="FlagsAttribute"/> set; otherwise, <c>false</c>.</returns>
private static bool IsFlags<T>() where T : struct, Enum {
return Attribute.IsDefined(typeof(T), typeof(FlagsAttribute));
}

/// <summary>Adds an offset to the value of a pointer.</summary>
/// <param name="pointer">The pointer to add the offset to.</param>
/// <param name="offset">The offset to add.</param>
/// <returns>A new pointer that reflects the addition of <paramref name="offset"/> to <paramref name="pointer"/>.</returns>
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);
}
}
}
Loading