diff --git a/JexusManager.Features.Certificates/CertificatesFeature.cs b/JexusManager.Features.Certificates/CertificatesFeature.cs
index 44c3f959..f7c29fb3 100644
--- a/JexusManager.Features.Certificates/CertificatesFeature.cs
+++ b/JexusManager.Features.Certificates/CertificatesFeature.cs
@@ -10,35 +10,31 @@
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
+using System;
using System.ComponentModel;
+using System.Security.Cryptography;
+using System.Windows.Forms;
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using System.Reflection;
+using System.Collections;
+using Microsoft.Web.Management.Client.Win32;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Web.Management.Client;
+using System.Resources;
+using System.Collections.Generic;
+using JexusManager.Services;
+using Microsoft.Web.Administration;
+using System.Diagnostics;
+using JexusManager.Features.Certificates.Wizards.CertificateRequestWizard;
+using JexusManager.Features.Certificates.Wizards.CertificateRenewWizard;
namespace JexusManager.Features.Certificates
{
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Reflection;
- using System.Resources;
- using System.Security.Cryptography;
- using System.Security.Cryptography.X509Certificates;
- using System.Windows.Forms;
-
- using Services;
- using Wizards.CertificateRenewWizard;
- using Wizards.CertificateRequestWizard;
-
- using Microsoft.Web.Administration;
- using Microsoft.Web.Management.Client;
- using Microsoft.Web.Management.Client.Win32;
-
- using Module = Microsoft.Web.Management.Client.Module;
-
- ///
- /// Description of DefaultDocumentFeature.
- ///
- internal class CertificatesFeature
+ public class CertificatesFeature
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("CertificatesFeature");
+
private sealed class FeatureTaskList : DefaultTaskList
{
private readonly CertificatesFeature _owner;
@@ -108,11 +104,10 @@ public override ICollection GetTaskItems()
}
catch (CryptographicException ex)
{
- if (ex.HResult != NativeMethods.BadKeySet)
+ if (ex.HResult != Microsoft.Web.Administration.NativeMethods.BadKeySet)
{
// TODO: add CNG support.
- // throw;
- Debug.WriteLine($"ate exception in certificates feature {ex}");
+ _logger.LogError(ex, "Cryptographic error in certificates feature. HResult: {HResult}", ex.HResult);
}
}
}
@@ -218,7 +213,7 @@ public void Trust()
}
}
- public CertificatesFeature(Module module)
+ public CertificatesFeature(Microsoft.Web.Management.Client.Module module)
{
Module = module;
}
@@ -285,9 +280,9 @@ public void Load()
}
catch (CryptographicException ex)
{
- if (ex.HResult != NativeMethods.NonExistingStore)
+ if (ex.HResult != Microsoft.Web.Administration.NativeMethods.NonExistingStore)
{
- Debug.WriteLine($"CryptographicException {ex.HResult} from CertificatesFeature.");
+ _logger.LogError(ex, "CryptographicException {HResult} from CertificatesFeature.", ex.HResult);
throw;
}
}
@@ -324,6 +319,11 @@ public void Remove()
return;
}
+ DeleteCertificate();
+ }
+
+ private void DeleteCertificate()
+ {
try
{
// remove certificate and mapping
@@ -348,22 +348,20 @@ public void Remove()
}
catch (Win32Exception ex)
{
- // elevation is cancelled.
- var message = NativeMethods.KnownCases(ex.NativeErrorCode);
+ var message = Microsoft.Web.Administration.NativeMethods.KnownCases(ex.NativeErrorCode);
if (string.IsNullOrEmpty(message))
- {
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
+ {
+ _logger.LogError(ex, "Win32 error deleting certificate. Native error code: {Code}", ex.NativeErrorCode);
}
else
{
+ var dialog = (IManagementUIService)GetService(typeof(IManagementUIService));
dialog.ShowError(ex, message, Name, false);
}
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error deleting certificate");
}
}
@@ -457,7 +455,7 @@ private void Trust()
}
catch (CryptographicException ex)
{
- if (ex.HResult != NativeMethods.UserCancelled)
+ if (ex.HResult != Microsoft.Web.Administration.NativeMethods.UserCancelled)
{
var dialog = (IManagementUIService)GetService(typeof(IManagementUIService));
dialog.ShowMessage($"An unexpected error happened. HResult is {ex.HResult}. Contact your system administrator.", Name,
@@ -488,7 +486,7 @@ public virtual Version MinimumFrameworkVersion
get { return FxVersionNotRequired; }
}
- public Module Module { get; }
+ public Microsoft.Web.Management.Client.Module Module { get; }
public string Name
{
diff --git a/JexusManager.Features.Certificates/CertificatesItem.cs b/JexusManager.Features.Certificates/CertificatesItem.cs
index 9688bc10..d638a532 100644
--- a/JexusManager.Features.Certificates/CertificatesItem.cs
+++ b/JexusManager.Features.Certificates/CertificatesItem.cs
@@ -7,7 +7,7 @@ namespace JexusManager.Features.Certificates
using System;
using System.Security.Cryptography.X509Certificates;
- internal class CertificatesItem : IEquatable
+ public class CertificatesItem : IEquatable
{
public CertificatesItem(X509Certificate2 certificate, string store, CertificatesFeature feature)
{
@@ -25,4 +25,4 @@ public bool Equals(CertificatesItem other)
return other != null && other.Certificate.GetCertHashString() == Certificate.GetCertHashString();
}
}
-}
\ No newline at end of file
+}
diff --git a/JexusManager.Features.Certificates/CompleteRequestDialog.cs b/JexusManager.Features.Certificates/CompleteRequestDialog.cs
index 05a19a7d..433e1430 100644
--- a/JexusManager.Features.Certificates/CompleteRequestDialog.cs
+++ b/JexusManager.Features.Certificates/CompleteRequestDialog.cs
@@ -9,19 +9,23 @@ namespace JexusManager.Features.Certificates
{
using System;
using System.ComponentModel;
- using System.Diagnostics;
- using System.IO;
- using System.Reactive.Disposables;
- using System.Reactive.Linq;
- using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms;
+ using Microsoft.Extensions.Logging;
+ using JexusManager;
using Microsoft.Web.Management.Client.Win32;
using Mono.Security.Authenticode;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Reactive.Disposables;
+ using System.Reactive.Linq;
+ using System.IO;
+ using System.Diagnostics;
- internal partial class CompleteRequestDialog : DialogForm
+ public partial class CompleteRequestDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("CompleteRequestDialog");
+
public CompleteRequestDialog(IServiceProvider serviceProvider, CertificatesFeature feature)
: base(serviceProvider)
{
@@ -102,48 +106,7 @@ public CompleteRequestDialog(IServiceProvider serviceProvider, CertificatesFeatu
Store = cbStore.SelectedIndex == 0 ? "Personal" : "WebHosting";
- try
- {
- // add certificate
- using var process = new Process();
- var start = process.StartInfo;
- start.Verb = "runas";
- start.UseShellExecute = true;
- start.FileName = "cmd";
- start.Arguments = $"/c \"\"{CertificateInstallerLocator.FileName}\" /f:\"{p12File}\" /p:{p12pwd} /n:\"{txtName.Text}\" /s:{(cbStore.SelectedIndex == 0 ? "MY" : "WebHosting")}\"";
- start.CreateNoWindow = true;
- start.WindowStyle = ProcessWindowStyle.Hidden;
- process.Start();
- process.WaitForExit();
- File.Delete(p12File);
- if (process.ExitCode == 0)
- {
- DialogResult = DialogResult.OK;
- }
- else
- {
- MessageBox.Show(process.ExitCode.ToString());
- }
- }
- catch (Win32Exception ex)
- {
- // elevation is cancelled.
- var message = Microsoft.Web.Administration.NativeMethods.KnownCases(ex.NativeErrorCode);
- if (string.IsNullOrEmpty(message))
- {
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
- }
- else
- {
- ShowError(ex, message, false);
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine(ex);
- }
+ Install(p12File, p12pwd);
}));
container.Add(
@@ -166,5 +129,44 @@ public CompleteRequestDialog(IServiceProvider serviceProvider, CertificatesFeatu
public string Store { get; set; }
public X509Certificate2 Item { get; set; }
+
+ private void Install(string p12File, string p12pwd)
+ {
+ try
+ {
+ // add certificate
+ using var process = new Process();
+ var start = process.StartInfo;
+ start.Verb = "runas";
+ start.UseShellExecute = true;
+ start.FileName = "cmd";
+ start.Arguments = $"/c \"\"{CertificateInstallerLocator.FileName}\" /f:\"{p12File}\" /p:{p12pwd} /n:\"{txtName.Text}\" /s:{(cbStore.SelectedIndex == 0 ? "MY" : "WebHosting")}\"";
+ start.CreateNoWindow = true;
+ start.WindowStyle = ProcessWindowStyle.Hidden;
+ process.Start();
+ process.WaitForExit();
+ File.Delete(p12File);
+ if (process.ExitCode == 0)
+ {
+ DialogResult = DialogResult.OK;
+ }
+ else
+ {
+ MessageBox.Show(process.ExitCode.ToString());
+ }
+ }
+ catch (Win32Exception ex)
+ {
+ // elevation is cancelled.
+ if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
+ {
+ _logger.LogError(ex, "Win32 error during certificate installation. Native error code: {Code}", ex.NativeErrorCode);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error during certificate installation");
+ }
+ }
}
}
diff --git a/JexusManager.Features.Certificates/ImportCertificateDialog.cs b/JexusManager.Features.Certificates/ImportCertificateDialog.cs
index 9eda4897..1036ebd1 100644
--- a/JexusManager.Features.Certificates/ImportCertificateDialog.cs
+++ b/JexusManager.Features.Certificates/ImportCertificateDialog.cs
@@ -8,14 +8,9 @@ namespace JexusManager.Features.Certificates
{
using System;
using System.ComponentModel;
- using System.Diagnostics;
- using System.IO;
- using System.Reactive.Disposables;
- using System.Reactive.Linq;
- using System.Security.Cryptography;
- using System.Security.Cryptography.X509Certificates;
- using System.Text;
using System.Windows.Forms;
+ using Microsoft.Extensions.Logging;
+ using JexusManager;
using Services;
@@ -26,9 +21,17 @@ namespace JexusManager.Features.Certificates
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Diagnostics;
+ using System.Reactive.Linq;
+ using System.Reactive.Disposables;
+ using System.Text;
+ using System.IO;
internal partial class ImportCertificateDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("ImportCertificateDialog");
+
public ImportCertificateDialog(IServiceProvider serviceProvider, CertificatesFeature feature)
: base(serviceProvider)
{
@@ -108,47 +111,7 @@ public ImportCertificateDialog(IServiceProvider serviceProvider, CertificatesFea
}
else
{
- try
- {
- using var process = new Process();
- // add certificate
- var start = process.StartInfo;
- start.Verb = "runas";
- start.UseShellExecute = true;
- start.FileName = "cmd";
- start.Arguments = $"/c \"\"{CertificateInstallerLocator.FileName}\" /f:\"{txtFile.Text}\" /p:{txtPassword.Text} /n:\"{Item.FriendlyName}\" /s:{(cbStore.SelectedIndex == 0 ? "MY" : "WebHosting")}\"";
- start.CreateNoWindow = true;
- start.WindowStyle = ProcessWindowStyle.Hidden;
- process.Start();
- process.WaitForExit();
- if (process.ExitCode == 0)
- {
- DialogResult = DialogResult.OK;
- }
- else
- {
- MessageBox.Show(process.ExitCode.ToString());
- }
- }
- catch (Win32Exception ex)
- {
- // elevation is cancelled.
- var message = NativeMethods.KnownCases(ex.NativeErrorCode);
- if (string.IsNullOrEmpty(message))
- {
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
- }
- else
- {
- ShowError(ex, message, false);
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine(ex);
- }
+ Install(txtFile.Text, txtPassword.Text);
}
}
catch (Exception ex)
@@ -169,5 +132,40 @@ public ImportCertificateDialog(IServiceProvider serviceProvider, CertificatesFea
public string Store { get; set; }
public X509Certificate2 Item { get; set; }
+
+ private void Install(string fileName, string password)
+ {
+ try
+ {
+ // install certificate
+ using var process = new Process();
+ var start = process.StartInfo;
+ start.Verb = "runas";
+ start.UseShellExecute = true;
+ start.FileName = "cmd";
+ start.Arguments = $"/c \"\"{CertificateInstallerLocator.FileName}\" /f:\"{fileName}\" /p:\"{password}\" /n:\"{txtName.Text}\" /s:{(cbStore.SelectedIndex == 0 ? "MY" : "WebHosting")}\"";
+ start.CreateNoWindow = true;
+ start.WindowStyle = ProcessWindowStyle.Hidden;
+ process.Start();
+ process.WaitForExit();
+
+ if (process.ExitCode == 0)
+ {
+ DialogResult = DialogResult.OK;
+ }
+ }
+ catch (Win32Exception ex)
+ {
+ // elevation is cancelled.
+ if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
+ {
+ _logger.LogError(ex, "Win32 error during certificate installation. Native error code: {Code}", ex.NativeErrorCode);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error during certificate installation");
+ }
+ }
}
}
diff --git a/JexusManager.Features.Certificates/SelfCertificateDialog.cs b/JexusManager.Features.Certificates/SelfCertificateDialog.cs
index 684d61d5..d67bac46 100644
--- a/JexusManager.Features.Certificates/SelfCertificateDialog.cs
+++ b/JexusManager.Features.Certificates/SelfCertificateDialog.cs
@@ -16,7 +16,7 @@ namespace JexusManager.Features.Certificates
using System.Reactive.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms;
-
+ using Microsoft.Extensions.Logging;
using Microsoft.Web.Management.Client.Win32;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
@@ -33,6 +33,8 @@ namespace JexusManager.Features.Certificates
internal partial class SelfCertificateDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("SelfCertificateDialog");
+
public SelfCertificateDialog(IServiceProvider serviceProvider, CertificatesFeature feature)
: base(serviceProvider)
{
@@ -183,9 +185,7 @@ public SelfCertificateDialog(IServiceProvider serviceProvider, CertificatesFeatu
var message = Microsoft.Web.Administration.NativeMethods.KnownCases(ex.NativeErrorCode);
if (string.IsNullOrEmpty(message))
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
+ _logger.LogError(ex, "Win32 error installing certificate. Native error code: {Code}", ex.NativeErrorCode);
}
else
{
@@ -195,7 +195,7 @@ public SelfCertificateDialog(IServiceProvider serviceProvider, CertificatesFeatu
catch (Exception ex)
{
ShowError(ex, $"unexpected exception", false);
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error installing certificate");
}
finally
{
@@ -209,8 +209,8 @@ public SelfCertificateDialog(IServiceProvider serviceProvider, CertificatesFeatu
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
ShowError(ex, Text, false);
+ _logger.LogError(ex, "Error validating certificate");
return;
}
}));
diff --git a/JexusManager.Features.HttpApi/HttpApiFeature.cs b/JexusManager.Features.HttpApi/HttpApiFeature.cs
index 131468c5..67528a50 100644
--- a/JexusManager.Features.HttpApi/HttpApiFeature.cs
+++ b/JexusManager.Features.HttpApi/HttpApiFeature.cs
@@ -7,7 +7,7 @@
namespace JexusManager.Features.HttpApi
{
- internal abstract class HttpApiFeature : FeatureBase, IHttpApiFeature
+ public abstract class HttpApiFeature : FeatureBase, IHttpApiFeature
where T : class, IItem
{
protected HttpApiFeature(Module module)
diff --git a/JexusManager.Features.HttpApi/IpMappingFeature.cs b/JexusManager.Features.HttpApi/IpMappingFeature.cs
index 659c8110..7c32ab00 100644
--- a/JexusManager.Features.HttpApi/IpMappingFeature.cs
+++ b/JexusManager.Features.HttpApi/IpMappingFeature.cs
@@ -3,26 +3,28 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
-using JexusManager.Services;
-using Microsoft.Web.Administration;
-using Microsoft.Web.Management.Client;
-using Microsoft.Web.Management.Client.Win32;
-using System.Collections;
-using System.Collections.Generic;
using System.ComponentModel;
-using System.Diagnostics;
-using System.IO;
-using System.Reflection;
using System.Windows.Forms;
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using System.Reflection;
+using System.Collections;
+using Microsoft.Web.Management.Client.Win32;
+using Microsoft.Web.Management.Client;
+using System.Collections.Generic;
using Org.BouncyCastle.Utilities.Encoders;
+using System.Diagnostics;
+using Microsoft.Web.Administration;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
-using Exception = System.Exception;
+using JexusManager.Services;
namespace JexusManager.Features.HttpApi
{
internal class IpMappingFeature : HttpApiFeature
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("IpMappingFeature");
+
private sealed class FeatureTaskList : DefaultTaskList
{
private readonly IpMappingFeature _owner;
@@ -93,6 +95,11 @@ public void Remove()
return;
}
+ DeleteMapping();
+ }
+
+ private void DeleteMapping()
+ {
try
{
// remove IP mapping
@@ -118,21 +125,14 @@ public void Remove()
catch (Win32Exception ex)
{
// elevation is cancelled.
- var message = Microsoft.Web.Administration.NativeMethods.KnownCases(ex.NativeErrorCode);
- if (string.IsNullOrEmpty(message))
- {
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
- }
- else
+ if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
{
- dialog.ShowError(ex, message, Name, false);
+ _logger.LogError(ex, "Win32 error deleting IP mapping. Native error code: {Code}", ex.NativeErrorCode);
}
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error deleting IP mapping");
}
}
diff --git a/JexusManager.Features.HttpApi/ReservedUrlsFeature.cs b/JexusManager.Features.HttpApi/ReservedUrlsFeature.cs
index a2acc616..868df014 100644
--- a/JexusManager.Features.HttpApi/ReservedUrlsFeature.cs
+++ b/JexusManager.Features.HttpApi/ReservedUrlsFeature.cs
@@ -10,16 +10,18 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Diagnostics;
-using System.IO;
-using System.Reflection;
using System.Windows.Forms;
-using Exception = System.Exception;
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using System.Reflection;
+using System.Diagnostics;
namespace JexusManager.Features.HttpApi
{
- internal class ReservedUrlsFeature : HttpApiFeature
+ public class ReservedUrlsFeature : HttpApiFeature
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("ReservedUrlsFeature");
+
private sealed class FeatureTaskList : DefaultTaskList
{
private readonly ReservedUrlsFeature _owner;
@@ -94,6 +96,11 @@ public void Remove()
return;
}
+ DeleteReservedUrl();
+ }
+
+ private void DeleteReservedUrl()
+ {
try
{
// remove reserved URL
@@ -118,20 +125,14 @@ public void Remove()
catch (Win32Exception ex)
{
// elevation is cancelled.
- var message = Microsoft.Web.Administration.NativeMethods.KnownCases(ex.NativeErrorCode);
- if (string.IsNullOrEmpty(message))
- {
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- }
- else
+ if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
{
- dialog.ShowError(ex, message, Name, false);
+ _logger.LogError(ex, "Win32 error deleting reserved URL. Native error code: {Code}", ex.NativeErrorCode);
}
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error deleting reserved URL");
}
}
diff --git a/JexusManager.Features.HttpApi/ReservedUrlsItem.cs b/JexusManager.Features.HttpApi/ReservedUrlsItem.cs
index 438f4909..1be8981c 100644
--- a/JexusManager.Features.HttpApi/ReservedUrlsItem.cs
+++ b/JexusManager.Features.HttpApi/ReservedUrlsItem.cs
@@ -6,7 +6,7 @@
namespace JexusManager.Features.HttpApi
{
- internal class ReservedUrlsItem : IItem
+ public class ReservedUrlsItem : IItem
{
public string UrlPrefix { get; set; }
public string SecurityDescriptor { get; set; }
diff --git a/JexusManager.Features.HttpApi/SniMappingFeature.cs b/JexusManager.Features.HttpApi/SniMappingFeature.cs
index 2ad54f83..774f331f 100644
--- a/JexusManager.Features.HttpApi/SniMappingFeature.cs
+++ b/JexusManager.Features.HttpApi/SniMappingFeature.cs
@@ -10,8 +10,11 @@
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
+using System;
using System.ComponentModel;
-using System.IO;
+using System.Windows.Forms;
+using Microsoft.Extensions.Logging;
+using JexusManager;
namespace JexusManager.Features.HttpApi
{
@@ -37,6 +40,8 @@ namespace JexusManager.Features.HttpApi
///
internal class SniMappingFeature : HttpApiFeature
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("SniMappingFeature");
+
private sealed class FeatureTaskList : DefaultTaskList
{
private readonly SniMappingFeature _owner;
@@ -110,6 +115,11 @@ public void Remove()
return;
}
+ DeleteMapping();
+ }
+
+ private void DeleteMapping()
+ {
try
{
// remove certificate and mapping
@@ -135,21 +145,14 @@ public void Remove()
catch (Win32Exception ex)
{
// elevation is cancelled.
- var message = NativeMethods.KnownCases(ex.NativeErrorCode);
- if (string.IsNullOrEmpty(message))
- {
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
- }
- else
+ if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
{
- dialog.ShowError(ex, message, Name, false);
+ _logger.LogError(ex, "Win32 error deleting SNI mapping. Native error code: {Code}", ex.NativeErrorCode);
}
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error deleting SNI mapping");
}
}
diff --git a/JexusManager.Shared/BindingUtility.cs b/JexusManager.Shared/BindingUtility.cs
index 59fff578..4524c3fa 100644
--- a/JexusManager.Shared/BindingUtility.cs
+++ b/JexusManager.Shared/BindingUtility.cs
@@ -15,7 +15,7 @@ namespace Microsoft.Web.Administration
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms;
-
+ using Microsoft.Extensions.Logging;
using JexusManager;
using Org.BouncyCastle.Utilities.Encoders;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
@@ -35,6 +35,8 @@ public enum CertificateMappingState
public static class BindingUtility
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("BindingUtility");
+
private const string AppIdIisExpress = "214124cd-d05b-4309-9af9-9caa44b2b74a";
private const string AppIdIis = "4dc3e181-e14b-4a21-b022-59fc669b0914";
@@ -156,8 +158,7 @@ private static (CertificateMappingState state, string message) ManipulateCertifi
var message = NativeMethods.KnownCases(ex.NativeErrorCode);
if (string.IsNullOrEmpty(message))
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
+ _logger.LogWarning(ex, "Win32 error during certificate mapping. Native error code: {Code}", ex.NativeErrorCode);
return (CertificateMappingState.Win32ErrorOccurred, $"Register new certificate failed: unknown (native {ex.NativeErrorCode})");
}
@@ -165,7 +166,7 @@ private static (CertificateMappingState state, string message) ManipulateCertifi
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Unhandled error during certificate mapping");
return (CertificateMappingState.GenericErrorOccurred, $"Register new certificate failed: unknown ({ex.Message})");
}
}
@@ -275,8 +276,7 @@ private static string RemoveMapping(this Binding binding, bool sni)
var message = NativeMethods.KnownCases(ex.NativeErrorCode);
if (string.IsNullOrEmpty(message))
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
+ _logger.LogWarning(ex, "Win32 error during mapping removal. Native error code: {Code}", ex.NativeErrorCode);
return $"Remove SNI certificate failed: unknown (native {ex.NativeErrorCode})";
}
@@ -284,13 +284,13 @@ private static string RemoveMapping(this Binding binding, bool sni)
}
catch (NullReferenceException ex)
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"binding {binding.ToString()} endpointNull {binding.EndPoint == null}");
+ _logger.LogError(ex, "Null reference during mapping removal. Binding: {Binding}, EndPoint null: {IsNull}",
+ binding.ToString(), binding.EndPoint == null);
return $"Remove SNI certificate failed: unknown ({ex.Message})";
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Unhandled error during mapping removal");
return $"Remove SNI certificate failed: unknown ({ex.Message})";
}
}
@@ -332,8 +332,7 @@ public static string AddReservedUrl(string url)
var message = NativeMethods.KnownCases(ex.NativeErrorCode);
if (string.IsNullOrEmpty(message))
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
+ _logger.LogWarning(ex, "Win32 error adding reserved URL. Native error code: {Code}", ex.NativeErrorCode);
return $"failed to add reserved URL: unknown ({message})";
}
else
@@ -343,7 +342,7 @@ public static string AddReservedUrl(string url)
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error adding reserved URL: {Url}", url);
return $"failed to add reserved URL: unknown ({ex.Message})";
}
}
diff --git a/JexusManager.Shared/DialogHelper.cs b/JexusManager.Shared/DialogHelper.cs
index 4d604e65..c37be0f9 100644
--- a/JexusManager.Shared/DialogHelper.cs
+++ b/JexusManager.Shared/DialogHelper.cs
@@ -9,18 +9,21 @@ namespace JexusManager
using System;
using System.ComponentModel;
using System.Diagnostics;
- using System.Drawing;
using System.IO;
using System.Linq;
- using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Windows.Forms;
+ using Microsoft.Extensions.Logging;
using Tulpep.NotificationWindow;
+ using System.Net;
+ using System.Drawing;
public static class DialogHelper
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("DialogHelper");
+
public static string[] Conditions = new[]
{
"ALL_HTTP",
@@ -274,7 +277,7 @@ public static void LoadCertificates(ComboBox comboBox, byte[] hash, string store
{
if (ex.HResult != Microsoft.Web.Administration.NativeMethods.NonExistingStore)
{
- Debug.WriteLine($"CryptographicException {ex.HResult} from LoadCertificates MY");
+ _logger.LogWarning(ex, "Error accessing MY certificate store. HResult: {HResult}", ex.HResult);
throw;
}
}
@@ -307,7 +310,7 @@ public static void LoadCertificates(ComboBox comboBox, byte[] hash, string store
{
if (ex.HResult != Microsoft.Web.Administration.NativeMethods.NonExistingStore)
{
- Debug.WriteLine($"CryptographicException {ex.HResult} from LoadCertificates WebHosting");
+ _logger.LogWarning(ex, "Error accessing WebHosting certificate store. HResult: {HResult}", ex.HResult);
throw;
}
}
@@ -343,7 +346,7 @@ private static string GetSpecialFolder(string name, string file)
}
catch (IOException ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error creating directory {Path}", result);
}
}
@@ -393,7 +396,7 @@ public static void BrowseFile(string file)
return;
}
- MessageBoxShow($"Windows Explorer cannot be located.", true);
+ _logger.LogWarning("Windows Explorer not found at {Path}", path);
}
public static void SiteStart(Site site)
diff --git a/JexusManager.Shared/Features/TaskItemExtensions.cs b/JexusManager.Shared/Features/TaskItemExtensions.cs
index d5283eaa..68989b56 100644
--- a/JexusManager.Shared/Features/TaskItemExtensions.cs
+++ b/JexusManager.Shared/Features/TaskItemExtensions.cs
@@ -4,21 +4,20 @@
using System;
using System.Reflection;
-
-namespace JexusManager.Features
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using Microsoft.Web.Management.Client.Win32;
+using System.Windows.Forms;
+using System.Collections;
+using JexusManager.Properties;
+using System.Drawing;
+
+namespace Microsoft.Web.Management.Client
{
- using System.Collections;
- using System.Diagnostics;
- using System.Drawing;
- using System.Windows.Forms;
-
- using JexusManager.Properties;
-
- using Microsoft.Web.Management.Client;
- using Microsoft.Web.Management.Client.Win32;
-
public static class TaskItemExtensions
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("TaskItemExtensions");
+
public static void Fill(this TaskListCollection tasks, ToolStrip actionPanel, ContextMenuStrip actionMenu, TaskList extra = null)
{
actionPanel.Items.Clear();
@@ -91,11 +90,11 @@ private static void TaskItemFill(this TaskItem item, TaskList list, ToolStrip ac
list.InvokeMethod(method.MethodName, method.UserData);
}
catch (TargetInvocationException ex)
- {
+ {
if (ex.InnerException is UnauthorizedAccessException)
{
+ _logger.LogError(ex.InnerException, "Error invoking task method {Method}", method.MethodName);
MessageBox.Show($"{ex.InnerException.Message}. Running Jexus Manager as administrator might resolve it.", "Jexus Manager", MessageBoxButtons.OK, MessageBoxIcon.Error);
- Debug.WriteLine(ex.InnerException);
return;
}
@@ -120,11 +119,11 @@ private static void TaskItemFill(this TaskItem item, TaskList list, ToolStrip ac
list.InvokeMethod(method.MethodName, method.UserData);
}
catch (TargetInvocationException ex)
- {
+ {
if (ex.InnerException is UnauthorizedAccessException)
{
+ _logger.LogError(ex.InnerException, "Error invoking task method {Method}", method.MethodName);
MessageBox.Show($"{ex.InnerException.Message}. Running Jexus Manager as administrator might resolve it.", "Jexus Manager", MessageBoxButtons.OK, MessageBoxIcon.Error);
- Debug.WriteLine(ex.InnerException);
return;
}
diff --git a/JexusManager/Features/Main/BindingDiagDialog.cs b/JexusManager/Features/Main/BindingDiagDialog.cs
index 4269af75..69a39315 100644
--- a/JexusManager/Features/Main/BindingDiagDialog.cs
+++ b/JexusManager/Features/Main/BindingDiagDialog.cs
@@ -7,24 +7,25 @@
using System;
using System.Linq;
using System.Security.Cryptography;
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.IO;
+using System.Drawing;
+using Microsoft.Win32;
+using EnumsNET;
+using System.Net;
+using System.Net.Sockets;
+using JexusManager.Features.HttpApi;
+using Microsoft.Web.Management.Client;
namespace JexusManager.Features.Main
{
- using System.Drawing;
- using System.IO;
- using Microsoft.Win32;
-
- using System.Reactive.Disposables;
- using System.Reactive.Linq;
- using System.Collections.Generic;
- using System.Net;
- using System.Net.Sockets;
- using EnumsNET;
- using JexusManager.Features.HttpApi;
- using Microsoft.Web.Management.Client;
-
public partial class BindingDiagDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("BindingDiagDialog");
+
public BindingDiagDialog(IServiceProvider provider, Site site)
: base(provider)
{
@@ -39,197 +40,7 @@ public BindingDiagDialog(IServiceProvider provider, Site site)
.Subscribe(evt =>
{
txtResult.Clear();
- try
- {
- Warn("IMPORTANT: This report might contain confidential information. Mask such before sharing with others.");
- Warn("-----");
- Debug($"System Time: {DateTime.Now}");
- Debug($"Processor Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}");
- Debug($"OS: {Environment.OSVersion}");
- Debug($"Server Type: {site.Server.Mode.AsString(EnumFormat.Description)}");
- Debug("-----");
-
- var adapters = Dns.GetHostEntry(string.Empty).AddressList.Where(address => !address.IsIPv6LinkLocal).ToList();
- if (adapters.Count == 0)
- {
- Warn("This machine has no suitable IP address to accept external traffic.");
- }
- else
- {
- Info($"This machine has {adapters.Count} IP addresses to take external traffic.");
- foreach (IPAddress address in adapters)
- {
- Info(address.AddressFamily == AddressFamily.InterNetworkV6
- ? $"* [{address}]."
- : $"* {address}.");
- }
- }
-
- Debug("-----");
-
- Debug($"[W3SVC/{site.Id}]");
- Debug($"ServerComment : {site.Name}");
- Debug($"ServerAutoStart: {site.ServerAutoStart}");
- Debug($"ServerState : {site.State}");
- Debug(string.Empty);
- var feature = new ReservedUrlsFeature((Module)provider);
- feature.Load();
- foreach (Binding binding in site.Bindings)
- {
- Debug($"BINDING: {binding.Protocol} {binding}");
- if (binding.Protocol != "https" && binding.Protocol != "http")
- {
- Warn("This prototol is not analyzed.");
- Debug(string.Empty);
- continue;
- }
-
- if (PublicNativeMethods.IsProcessElevated)
- {
- var found = Microsoft.Web.Administration.NativeMethods.FoundReserved((ushort)binding.EndPoint.Port);
- if (found)
- {
- Error("Found a conflicting TCP reserved port range. Please run \"netsh int ipv4 show excludedportrange protocol=tcp\" at command prompt to troubleshoot.");
- }
- else
- {
- Info("No conflicting TCP reserved port range is found.");
- }
- }
- else
- {
- Warn("Jexus Manager is not running as administrator, so TCP reserved port range is not verified. Please run \"netsh int ipv4 show excludedportrange protocol=tcp\" at command prompt to see if any conflict exists.");
- }
-
- if (binding.Host != "localhost")
- {
- if (site.Server.Mode == WorkingMode.IisExpress)
- {
- var reservation = binding.ToUrlPrefix();
- if (!feature.Items.Any(item => item.UrlPrefix == reservation))
- {
- Warn($"URL reservation {reservation} is missing. So this binding only works if IIS Express runs as administrator.");
- }
- }
-
- Info($"This site can take external traffic if,");
- Info($" * TCP port {binding.EndPoint.Port} must be opened on Windows Firewall (or any other installed firewall products).");
- }
-
- if (binding.EndPoint.Address.Equals(IPAddress.Any))
- {
- if (binding.Host != "localhost")
- {
- Info($" * Requests from web browsers must be routed to following end points on this machine,");
- foreach (IPAddress address in adapters)
- {
- Info(address.AddressFamily == AddressFamily.InterNetworkV6
- ? $" * [{address}]:{binding.EndPoint.Port}."
- : $" * {address}:{binding.EndPoint.Port}.");
- }
- }
-
- if (Socket.OSSupportsIPv4)
- {
- Debug($"This site can take local traffic at {IPAddress.Loopback}:{binding.EndPoint.Port}.");
- }
-
- if (Socket.OSSupportsIPv6)
- {
- Debug($"This site can take local traffic at [{IPAddress.IPv6Loopback}]:{binding.EndPoint.Port}.");
- }
- }
- else
- {
- Info($" * The networking must be properly set up to forward requests from web browsers to {binding.EndPoint} on this machine.");
- }
-
- if (binding.Host == "*" || binding.Host == string.Empty)
- {
- if (binding.EndPoint.Address.Equals(IPAddress.Any))
- {
- Info($" * Web browsers can use several URLs, such as");
- foreach (IPAddress address in adapters)
- {
- Debug(address.AddressFamily == AddressFamily.InterNetworkV6
- ? $" * {binding.Protocol}://[{address}]:{binding.EndPoint.Port}."
- : $" * {binding.Protocol}://{address}:{binding.EndPoint.Port}.");
- }
-
- Info($" * {binding.Protocol}://localhost:{binding.EndPoint.Port}.");
- Info($" * {binding.Protocol}://{IPAddress.Loopback}:{binding.EndPoint.Port}.");
- Info($" * {binding.Protocol}://[{IPAddress.IPv6Loopback}]:{binding.EndPoint.Port}.");
- }
- else
- {
- Info($" * Web browsers should use URL {binding.Protocol}://{binding.EndPoint.Address}:{binding.EndPoint.Port}.");
- }
- }
- else
- {
- Info($" * Web browsers should use URL {binding.Protocol}://{binding.Host}:{binding.EndPoint.Port}. Requests must have Host header of \"{binding.Host}\".");
- if (!binding.Host.IsWildcard())
- {
- Info($" Start DNS query for {binding.Host}.");
- // IMPORTANT: wildcard host is not supported.
- try
- {
- var entry = Dns.GetHostEntry(binding.Host);
- var list = entry.AddressList;
- Info($" DNS Query returns {list.Length} result(s).");
- var found = false;
- foreach (var address in list)
- {
- Info(address.AddressFamily == AddressFamily.InterNetworkV6
- ? $" * [{address}]"
- : $" * {address}");
- if (adapters.Any(item => address.Equals(item)))
- {
- found = true;
- break;
- }
-
- if (address.Equals(IPAddress.Loopback))
- {
- found = true;
- }
- }
-
- if (!found)
- {
- Warn($" DNS query of \"{binding.Host}\" does not return a known IP address for any network adapter of this machine.");
- Warn(" The server usually uses private IP addresses, and DNS query returns public IP addresses.");
- Warn(" If packets are forwarded from public IP to private IP properly, this warning can be ignored.");
- Warn(" Otherwise, please review DNS settings (or modify the hosts file to emulate DNS).");
- }
- }
- catch (SocketException ex)
- {
- Error($"DNS query failed: {ex.Message}.");
- Error($"Please review the host name {binding.Host}.");
- }
- }
- }
-
- if (binding.Protocol == "https")
- {
- Warn("Binding Diagnostics does not verify certificates and other SSL/TLS related settings.");
- Warn($"Please run SSL Diagnostics at server level to analyze SSL/TLS configuration. More information can be found at https://docs.jexusmanager.com/tutorials/ssl-diagnostics.html.");
- }
-
- Debug(string.Empty);
- }
- }
- catch (CryptographicException ex)
- {
- Debug(ex.ToString());
- System.Diagnostics.Debug.WriteLine(ex);
- System.Diagnostics.Debug.WriteLine($"hResult {ex.HResult}");
- }
- catch (Exception ex)
- {
- Debug(ex.ToString());
- }
+ AnalyzeBinding(site, (Module)provider);
}));
container.Add(
@@ -328,5 +139,198 @@ private void BindingDiagDialogHelpButtonClicked(object sender, System.ComponentM
{
BtnHelpClick(null, EventArgs.Empty);
}
+
+ private void AnalyzeBinding(Site site, Module provider)
+ {
+ try
+ {
+ Warn("IMPORTANT: This report might contain confidential information. Mask such before sharing with others.");
+ Warn("-----");
+ Debug($"System Time: {DateTime.Now}");
+ Debug($"Processor Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}");
+ Debug($"OS: {Environment.OSVersion}");
+ Debug($"Server Type: {site.Server.Mode.AsString(EnumFormat.Description)}");
+ Debug("-----");
+
+ var adapters = Dns.GetHostEntry(string.Empty).AddressList.Where(address => !address.IsIPv6LinkLocal).ToList();
+ if (adapters.Count == 0)
+ {
+ Warn("This machine has no suitable IP address to accept external traffic.");
+ }
+ else
+ {
+ Info($"This machine has {adapters.Count} IP addresses to take external traffic.");
+ foreach (IPAddress address in adapters)
+ {
+ Info(address.AddressFamily == AddressFamily.InterNetworkV6
+ ? $"* [{address}]."
+ : $"* {address}.");
+ }
+ }
+
+ Debug("-----");
+
+ Debug($"[W3SVC/{site.Id}]");
+ Debug($"ServerComment : {site.Name}");
+ Debug($"ServerAutoStart: {site.ServerAutoStart}");
+ Debug($"ServerState : {site.State}");
+ Debug(string.Empty);
+ var feature = new ReservedUrlsFeature(provider);
+ feature.Load();
+ foreach (Binding binding in site.Bindings)
+ {
+ Debug($"BINDING: {binding.Protocol} {binding}");
+ if (binding.Protocol != "https" && binding.Protocol != "http")
+ {
+ Warn("This prototol is not analyzed.");
+ Debug(string.Empty);
+ continue;
+ }
+
+ if (PublicNativeMethods.IsProcessElevated)
+ {
+ var found = Microsoft.Web.Administration.NativeMethods.FoundReserved((ushort)binding.EndPoint.Port);
+ if (found)
+ {
+ Error("Found a conflicting TCP reserved port range. Please run \"netsh int ipv4 show excludedportrange protocol=tcp\" at command prompt to troubleshoot.");
+ }
+ else
+ {
+ Info("No conflicting TCP reserved port range is found.");
+ }
+ }
+ else
+ {
+ Warn("Jexus Manager is not running as administrator, so TCP reserved port range is not verified. Please run \"netsh int ipv4 show excludedportrange protocol=tcp\" at command prompt to see if any conflict exists.");
+ }
+
+ if (binding.Host != "localhost")
+ {
+ if (site.Server.Mode == WorkingMode.IisExpress)
+ {
+ var reservation = binding.ToUrlPrefix();
+ if (!feature.Items.Any(item => item.UrlPrefix == reservation))
+ {
+ Warn($"URL reservation {reservation} is missing. So this binding only works if IIS Express runs as administrator.");
+ }
+ }
+
+ Info($"This site can take external traffic if,");
+ Info($" * TCP port {binding.EndPoint.Port} must be opened on Windows Firewall (or any other installed firewall products).");
+ }
+
+ if (binding.EndPoint.Address.Equals(IPAddress.Any))
+ {
+ if (binding.Host != "localhost")
+ {
+ Info($" * Requests from web browsers must be routed to following end points on this machine,");
+ foreach (IPAddress address in adapters)
+ {
+ Info(address.AddressFamily == AddressFamily.InterNetworkV6
+ ? $" * [{address}]:{binding.EndPoint.Port}."
+ : $" * {address}:{binding.EndPoint.Port}.");
+ }
+ }
+
+ if (Socket.OSSupportsIPv4)
+ {
+ Debug($"This site can take local traffic at {IPAddress.Loopback}:{binding.EndPoint.Port}.");
+ }
+
+ if (Socket.OSSupportsIPv6)
+ {
+ Debug($"This site can take local traffic at [{IPAddress.IPv6Loopback}]:{binding.EndPoint.Port}.");
+ }
+ }
+ else
+ {
+ Info($" * The networking must be properly set up to forward requests from web browsers to {binding.EndPoint} on this machine.");
+ }
+
+ if (binding.Host == "*" || binding.Host == string.Empty)
+ {
+ if (binding.EndPoint.Address.Equals(IPAddress.Any))
+ {
+ Info($" * Web browsers can use several URLs, such as");
+ foreach (IPAddress address in adapters)
+ {
+ Debug(address.AddressFamily == AddressFamily.InterNetworkV6
+ ? $" * {binding.Protocol}://[{address}]:{binding.EndPoint.Port}."
+ : $" * {binding.Protocol}://{address}:{binding.EndPoint.Port}.");
+ }
+
+ Info($" * {binding.Protocol}://localhost:{binding.EndPoint.Port}.");
+ Info($" * {binding.Protocol}://{IPAddress.Loopback}:{binding.EndPoint.Port}.");
+ Info($" * {binding.Protocol}://[{IPAddress.IPv6Loopback}]:{binding.EndPoint.Port}.");
+ }
+ else
+ {
+ Info($" * Web browsers should use URL {binding.Protocol}://{binding.EndPoint.Address}:{binding.EndPoint.Port}.");
+ }
+ }
+ else
+ {
+ Info($" * Web browsers should use URL {binding.Protocol}://{binding.Host}:{binding.EndPoint.Port}. Requests must have Host header of \"{binding.Host}\".");
+ if (!binding.Host.IsWildcard())
+ {
+ Info($" Start DNS query for {binding.Host}.");
+ // IMPORTANT: wildcard host is not supported.
+ try
+ {
+ var entry = Dns.GetHostEntry(binding.Host);
+ var list = entry.AddressList;
+ Info($" DNS Query returns {list.Length} result(s).");
+ var found = false;
+ foreach (var address in list)
+ {
+ Info(address.AddressFamily == AddressFamily.InterNetworkV6
+ ? $" * [{address}]"
+ : $" * {address}");
+ if (adapters.Any(item => address.Equals(item)))
+ {
+ found = true;
+ break;
+ }
+
+ if (address.Equals(IPAddress.Loopback))
+ {
+ found = true;
+ }
+ }
+
+ if (!found)
+ {
+ Warn($" DNS query of \"{binding.Host}\" does not return a known IP address for any network adapter of this machine.");
+ Warn(" The server usually uses private IP addresses, and DNS query returns public IP addresses.");
+ Warn(" If packets are forwarded from public IP to private IP properly, this warning can be ignored.");
+ Warn(" Otherwise, please review DNS settings (or modify the hosts file to emulate DNS).");
+ }
+ }
+ catch (SocketException ex)
+ {
+ Error($"DNS query failed: {ex.Message}.");
+ Error($"Please review the host name {binding.Host}.");
+ }
+ }
+ }
+
+ if (binding.Protocol == "https")
+ {
+ Warn("Binding Diagnostics does not verify certificates and other SSL/TLS related settings.");
+ Warn($"Please run SSL Diagnostics at server level to analyze SSL/TLS configuration. More information can be found at https://docs.jexusmanager.com/tutorials/ssl-diagnostics.html.");
+ }
+
+ Debug(string.Empty);
+ }
+ }
+ catch (CryptographicException ex)
+ {
+ _logger.LogError(ex, "Cryptographic error in binding diagnostics. HResult: {HResult}", ex.HResult);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error analyzing binding");
+ }
+ }
}
}
diff --git a/JexusManager/Features/Main/HomePage.Designer.cs b/JexusManager/Features/Main/HomePage.Designer.cs
index d84fd497..9a68e95f 100644
--- a/JexusManager/Features/Main/HomePage.Designer.cs
+++ b/JexusManager/Features/Main/HomePage.Designer.cs
@@ -35,6 +35,10 @@ private void InitializeComponent()
this.label2 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.panel2 = new System.Windows.Forms.Panel();
+ this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.lblCurrentVersion = new System.Windows.Forms.Label();
+ this.lblUpdateStatus = new System.Windows.Forms.Label();
+ this.btnDownloadUpdate = new System.Windows.Forms.Button();
this.label3 = new System.Windows.Forms.Label();
this.panel3 = new System.Windows.Forms.Panel();
this.groupBox1 = new System.Windows.Forms.GroupBox();
@@ -43,6 +47,7 @@ private void InitializeComponent()
this.btnSponsor = new System.Windows.Forms.Button();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
+ this.groupBox2.SuspendLayout();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
@@ -82,6 +87,7 @@ private void InitializeComponent()
// panel2
//
this.panel2.BackColor = System.Drawing.Color.White;
+ this.panel2.Controls.Add(this.groupBox2);
this.panel2.Controls.Add(this.label3);
this.panel2.Controls.Add(this.panel3);
this.panel2.Controls.Add(this.groupBox1);
@@ -92,6 +98,51 @@ private void InitializeComponent()
this.panel2.Size = new System.Drawing.Size(862, 489);
this.panel2.TabIndex = 1;
//
+ // groupBox2
+ //
+ this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.groupBox2.Controls.Add(this.lblCurrentVersion);
+ this.groupBox2.Controls.Add(this.lblUpdateStatus);
+ this.groupBox2.Controls.Add(this.btnDownloadUpdate);
+ this.groupBox2.Location = new System.Drawing.Point(360, 32);
+ this.groupBox2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ this.groupBox2.Name = "groupBox2";
+ this.groupBox2.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ this.groupBox2.Size = new System.Drawing.Size(478, 160);
+ this.groupBox2.TabIndex = 3;
+ this.groupBox2.TabStop = false;
+ this.groupBox2.Text = "Updates";
+ //
+ // lblCurrentVersion
+ //
+ this.lblCurrentVersion.AutoSize = true;
+ this.lblCurrentVersion.Location = new System.Drawing.Point(24, 33);
+ this.lblCurrentVersion.Name = "lblCurrentVersion";
+ this.lblCurrentVersion.Size = new System.Drawing.Size(92, 15);
+ this.lblCurrentVersion.TabIndex = 0;
+ this.lblCurrentVersion.Text = "Current Version:";
+ //
+ // lblUpdateStatus
+ //
+ this.lblUpdateStatus.AutoSize = true;
+ this.lblUpdateStatus.Location = new System.Drawing.Point(24, 76);
+ this.lblUpdateStatus.Name = "lblUpdateStatus";
+ this.lblUpdateStatus.Size = new System.Drawing.Size(153, 15);
+ this.lblUpdateStatus.TabIndex = 1;
+ this.lblUpdateStatus.Text = "Checking for updates...";
+ //
+ // btnDownloadUpdate
+ //
+ this.btnDownloadUpdate.Location = new System.Drawing.Point(24, 110);
+ this.btnDownloadUpdate.Name = "btnDownloadUpdate";
+ this.btnDownloadUpdate.Size = new System.Drawing.Size(200, 30);
+ this.btnDownloadUpdate.TabIndex = 2;
+ this.btnDownloadUpdate.Text = "Download Update";
+ this.btnDownloadUpdate.UseVisualStyleBackColor = true;
+ this.btnDownloadUpdate.Visible = false;
+ this.btnDownloadUpdate.Click += new System.EventHandler(this.btnDownloadUpdate_Click);
+ //
// label3
//
this.label3.AutoSize = true;
@@ -175,6 +226,8 @@ private void InitializeComponent()
this.panel1.PerformLayout();
this.panel2.ResumeLayout(false);
this.panel2.PerformLayout();
+ this.groupBox2.ResumeLayout(false);
+ this.groupBox2.PerformLayout();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
@@ -193,5 +246,9 @@ private void InitializeComponent()
private Label label3;
private LinkLabel txtStudio;
private Button btnSponsor;
+ private GroupBox groupBox2;
+ private Label lblCurrentVersion;
+ private Label lblUpdateStatus;
+ private Button btnDownloadUpdate;
}
}
diff --git a/JexusManager/Features/Main/HomePage.cs b/JexusManager/Features/Main/HomePage.cs
index cccce634..b73426d1 100644
--- a/JexusManager/Features/Main/HomePage.cs
+++ b/JexusManager/Features/Main/HomePage.cs
@@ -4,16 +4,66 @@
namespace JexusManager.Features.Main
{
+ using System;
using System.Diagnostics;
+ using System.Drawing;
+ using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Web.Management.Client.Win32;
public partial class HomePage : ModulePage
{
+ private UpdateHelper.UpdateInfo _updateInfo;
+
public HomePage()
{
InitializeComponent();
+ LoadUpdateInfo();
+ }
+
+ private async void LoadUpdateInfo()
+ {
+ try
+ {
+ _updateInfo = await UpdateHelper.CheckForUpdate();
+ UpdateVersionDisplay();
+ }
+ catch (Exception ex)
+ {
+ lblUpdateStatus.Text = $"Error checking for updates: {ex.Message}";
+ lblUpdateStatus.ForeColor = Color.Red;
+ }
+ }
+
+ private void UpdateVersionDisplay()
+ {
+ if (_updateInfo == null)
+ return;
+
+ // Always show current version
+ lblCurrentVersion.Text = $"Current Version: {_updateInfo.CurrentVersion}";
+
+ if (!string.IsNullOrEmpty(_updateInfo.ErrorMessage))
+ {
+ lblUpdateStatus.Text = _updateInfo.ErrorMessage;
+ lblUpdateStatus.ForeColor = Color.Red;
+ btnDownloadUpdate.Visible = false;
+ return;
+ }
+
+ if (_updateInfo.UpdateAvailable)
+ {
+ lblUpdateStatus.Text = $"An update is available: {_updateInfo.LatestVersion}";
+ lblUpdateStatus.ForeColor = Color.Green;
+ btnDownloadUpdate.Visible = true;
+ }
+ else
+ {
+ lblUpdateStatus.Text = "You are using the latest version.";
+ lblUpdateStatus.ForeColor = Color.Green;
+ btnDownloadUpdate.Visible = false;
+ }
}
private void txtHome_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
@@ -25,12 +75,20 @@ private void txtHome_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
private void txtStudio_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
- DialogHelper.ProcessStart("https://blog.lextudio.com");
+ DialogHelper.ProcessStart("https://halfblood.pro");
}
private void btnSponsor_Click(object sender, System.EventArgs e)
{
DialogHelper.ProcessStart("https://github.com/sponsors/lextm");
}
+
+ private void btnDownloadUpdate_Click(object sender, EventArgs e)
+ {
+ if (_updateInfo != null && !string.IsNullOrEmpty(_updateInfo.ReleaseUrl))
+ {
+ DialogHelper.ProcessStart(_updateInfo.ReleaseUrl);
+ }
+ }
}
}
diff --git a/JexusManager/Features/Main/KestrelDiagDialog.cs b/JexusManager/Features/Main/KestrelDiagDialog.cs
index 2029ea0d..44ac64f3 100644
--- a/JexusManager/Features/Main/KestrelDiagDialog.cs
+++ b/JexusManager/Features/Main/KestrelDiagDialog.cs
@@ -26,9 +26,11 @@ namespace JexusManager.Features.Main
using Newtonsoft.Json;
using System.Net;
using NuGet.Versioning;
+ using Microsoft.Extensions.Logging;
public partial class KestrelDiagDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("KestrelDiagDialog");
private static IDictionary> mappings = new Dictionary>();
private static IDictionary fileCaches = new Dictionary();
@@ -488,8 +490,7 @@ public KestrelDiagDialog(IServiceProvider provider, Application application)
catch (Exception ex)
{
Error("Cannot analyze ASP.NET Core web app successfully.");
- System.Diagnostics.Debug.WriteLine(ex);
- System.Diagnostics.Debug.WriteLine("source web app");
+ _logger.LogError(ex, "Error loading web.config of source web app");
}
}
}
@@ -498,12 +499,12 @@ public KestrelDiagDialog(IServiceProvider provider, Application application)
Error("A generic exception occurred.");
Error($"To run ASP.NET Core on IIS, please refer to https://docs.microsoft.com/aspnet/core/host-and-deploy/iis/index for more details.");
Debug(ex.ToString());
- System.Diagnostics.Debug.WriteLine(ex);
+ _logger.LogDebug(ex.ToString());
}
catch (Exception ex)
{
Debug(ex.ToString());
- System.Diagnostics.Debug.WriteLine(ex);
+ _logger.LogDebug(ex.ToString());
}
}));
diff --git a/JexusManager/Features/Main/PhpDiagDialog.cs b/JexusManager/Features/Main/PhpDiagDialog.cs
index a3746058..e1673549 100644
--- a/JexusManager/Features/Main/PhpDiagDialog.cs
+++ b/JexusManager/Features/Main/PhpDiagDialog.cs
@@ -22,9 +22,14 @@ namespace JexusManager.Features.Main
using IniParser.Parser;
using EnumsNET;
using JexusManager.Features.Modules;
+ using System.Windows.Forms;
+ using Microsoft.Extensions.Logging;
+ using JexusManager;
public partial class PhpDiagDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("PhpDiagDialog");
+
private struct PhpVersion
{
public PhpVersion(string version, DateTime expiringDate, Version cppVersion)
@@ -65,234 +70,7 @@ public PhpDiagDialog(IServiceProvider provider, ServerManager server)
.Subscribe(evt =>
{
txtResult.Clear();
- try
- {
- Warn("IMPORTANT: This report might contain confidential information. Mask such before sharing with others.");
- Warn("-----");
- Debug($"System Time: {DateTime.Now}");
- Debug($"Processor Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}");
- Debug($"OS: {Environment.OSVersion}");
- Debug($"Server Type: {server.Mode.AsString(EnumFormat.Description)}");
- Debug(string.Empty);
-
- var modules = new ModulesFeature((Module)provider);
- modules.Load();
- Debug($"Scan {modules.Items.Count} installed module(s).");
- if (modules.Items.All(item => item.Name != "FastCgiModule"))
- {
- Error($"FastCGI module is not installed as part of IIS. Please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
- return;
- }
- else
- {
- Debug("FastCGI module is installed.");
- }
-
- Debug(string.Empty);
- var handlers = new HandlersFeature((Module)provider);
- handlers.Load();
- var foundPhpHandler = new List();
- Debug($"Scan {handlers.Items.Count} registered handler(s).");
- foreach (var item in handlers.Items)
- {
- if (item.Modules == "FastCgiModule")
- {
- if (File.Exists(item.ScriptProcessor))
- {
- Debug($"* Found a valid FastCGI handler as {{ Name: {item.Name}, Path: {item.Path}, State: {item.GetState(handlers.AccessPolicy)}, Module: {item.TypeString}, Entry Type: {item.Flag} }}. File path: {item.ScriptProcessor}.");
- foundPhpHandler.Add(item);
- }
- else
- {
- Error($"* Found an invalid FastCGI handler as {{Name: {item.Name}, Path: {item.Path}, State: {item.GetState(handlers.AccessPolicy)}, Module: {item.TypeString}, Entry Type: {item.Flag} }} because file path does not exist: {item.ScriptProcessor}.");
- }
- }
- }
-
- if (foundPhpHandler.Count == 0)
- {
- Error($"No valid FastCGI handler is registered for this web site.");
- Error($"To run PHP on IIS, please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
- return;
- }
-
- Debug(string.Empty);
- var fastCgiFeature = new FastCgiFeature((Module)provider);
- fastCgiFeature.Load();
- Debug($"Scan {fastCgiFeature.Items.Count} registered FastCGI application(s).");
- var foundPhp = new List();
- foreach (var item in fastCgiFeature.Items)
- {
- var combination = string.IsNullOrWhiteSpace(item.Arguments) ? item.Path : item.Path + '|' + item.Arguments;
- foreach (var handler in foundPhpHandler)
- {
- if (string.Equals(combination, handler.ScriptProcessor, StringComparison.OrdinalIgnoreCase))
- {
- Debug($"* Found FastCGI application registered as {{ Full path: {item.Path}, Arguments: {item.Arguments} }}.");
- foundPhp.Add(item);
- break;
- }
- }
- }
-
- if (foundPhp.Count == 0)
- {
- Error($"No suitable FastCGI appilcation is registered on this server.");
- Error($"To run PHP on IIS, please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
- return;
- }
-
- Debug(Environment.NewLine);
- Debug($"Verify web stack installation versions.");
- foreach (var item in foundPhp)
- {
- var path = item.Path;
- if (path.TrimEnd('"').EndsWith("php-cgi.exe", StringComparison.OrdinalIgnoreCase))
- {
- // PHP
- var info = FileVersionInfo.GetVersionInfo(path);
- var version = $"{info.FileMajorPart}.{info.FileMinorPart}";
- if (knownPhpVersions.ContainsKey(version))
- {
- var matched = knownPhpVersions[version];
- if (matched.ExpiringDate <= DateTime.Now)
- {
- Error($"* PHP {info.FileVersion} ({path}) is unknown or obsolete. Please refer to http://php.net/supported-versions.php for more details.");
- }
- else if (matched.ExpiringDate > DateTime.Now && (matched.ExpiringDate - DateTime.Now).TotalDays < 180)
- {
- Warn($"* PHP {version} ({path}) will soon be obsolete. Please refer to http://php.net/supported-versions.php for more details.");
- }
- else
- {
- Debug($"* PHP {version} ({path}) is supported.");
- }
-
- var x86 = DialogHelper.GetImageArchitecture(path);
- var cppFile = Path.Combine(
- Environment.GetFolderPath(x86 ? Environment.SpecialFolder.SystemX86 : Environment.SpecialFolder.System),
- $"msvcp{matched.CppVersion.Major}0.dll");
- if (File.Exists(cppFile))
- {
- var cpp = FileVersionInfo.GetVersionInfo(cppFile);
- if (cpp.FileMinorPart >= matched.CppVersion.Minor)
- {
- Debug($" Visual C++ runtime is detected (expected: {matched.CppVersion}, detected: {cpp.FileVersion}): {cppFile}.");
- }
- else
- {
- Error($" Visual C++ runtime {matched.CppVersion} is not detected. Please install it following the tips on https://windows.php.net/download/.");
- }
- }
- else
- {
- Error($" Visual C++ {matched.CppVersion} runtime is not detected. Please install it following the tips on https://windows.php.net/download/.");
- }
- }
- else
- {
- Error($"* PHP {info.FileVersion} ({path}) is unknown or obsolete. Please refer to http://php.net/supported-versions.php for more details.");
- }
- }
- }
-
- Debug(string.Empty);
- var systemPath = Environment.GetEnvironmentVariable("Path");
- Debug($"Windows Path environment variable: {systemPath}.");
- Debug(string.Empty);
- string[] paths = systemPath.Split(new char[1] { Path.PathSeparator });
- foreach (var item in foundPhp)
- {
- var path = item.Path;
- if (path.TrimEnd('"').EndsWith("php-cgi.exe", StringComparison.OrdinalIgnoreCase))
- {
- var rootFolder = Path.GetDirectoryName(path);
- Debug($"[{rootFolder}]");
- if (!Directory.Exists(rootFolder))
- {
- Error("Invalid root folder is found. Skip.");
- continue;
- }
-
- var config = Path.Combine(rootFolder, "php.ini");
- if (File.Exists(config))
- {
- Info($"Found PHP config file {config}.");
- var parser = new ConcatenateDuplicatedKeysIniDataParser();
- parser.Configuration.ConcatenateSeparator = " ";
- var data = parser.Parse(File.ReadAllText(config));
- var extensionFolder = data["PHP"]["extension_dir"];
- if (extensionFolder == null)
- {
- extensionFolder = "\"ext\"";
- }
-
- extensionFolder = extensionFolder.Trim('"');
-
- var fullPath = Path.Combine(rootFolder, extensionFolder);
- Info($"PHP loadable extension folder: {fullPath}");
- var extesionNames = data["PHP"]["extension"];
- if (extesionNames == null)
- {
- Info("No extension to verify.");
- }
- else
- {
- var extensions = extesionNames.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
- Info($"Found {extensions.Length} extension(s) to verify.");
- var noError = true;
- foreach (var name in extensions)
- {
- var fileName = Path.Combine(fullPath, $"php_{name}.dll");
- if (!File.Exists(fileName))
- {
- Error($"* Extension {name} is listed, but on disk the file cannot be found {fileName}");
- noError = false;
- }
- }
-
- if (noError)
- {
- Info("All extension(s) listed can be found on disk.");
- }
- }
- }
- else
- {
- Warn($"Cannot find PHP config file {config}. Default settings are used.");
- }
-
- var matched = false;
- foreach (var system in paths)
- {
- if (string.Equals(rootFolder, system, StringComparison.OrdinalIgnoreCase))
- {
- matched = true;
- break;
- }
- }
-
- if (matched)
- {
- Debug($"PHP installation has been added to Windows Path environment variable.");
- }
- else
- {
- Error($"PHP installation is not yet added to Windows Path environment variable. Please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
- Warn($"Restart Jexus Manager and rerun PHP Diagnostics after changing Windows Path environment variable.");
- }
-
- Debug(string.Empty);
-
- // TODO: verify other configuration in php.info.
- }
- }
- }
- catch (Exception ex)
- {
- Debug(ex.ToString());
- System.Diagnostics.Debug.WriteLine(ex);
- }
+ AnalyzePhpConfig(server, (Module)provider, knownPhpVersions);
}));
container.Add(
@@ -360,5 +138,236 @@ private void PhpDiagDialogHelpButtonClicked(object sender, System.ComponentModel
{
BtnHelpClick(null, EventArgs.Empty);
}
+
+ private void AnalyzePhpConfig(ServerManager server, Module module, Dictionary knownPhpVersions)
+ {
+ try
+ {
+ Warn("IMPORTANT: This report might contain confidential information. Mask such before sharing with others.");
+ Warn("-----");
+ Debug($"System Time: {DateTime.Now}");
+ Debug($"Processor Architecture: {Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")}");
+ Debug($"OS: {Environment.OSVersion}");
+ Debug($"Server Type: {server.Mode.AsString(EnumFormat.Description)}");
+ Debug(string.Empty);
+
+ var modules = new ModulesFeature(module);
+ modules.Load();
+ Debug($"Scan {modules.Items.Count} installed module(s).");
+ if (modules.Items.All(item => item.Name != "FastCgiModule"))
+ {
+ Error($"FastCGI module is not installed as part of IIS. Please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
+ return;
+ }
+ else
+ {
+ Debug("FastCGI module is installed.");
+ }
+
+ Debug(string.Empty);
+ var handlers = new HandlersFeature(module);
+ handlers.Load();
+ var foundPhpHandler = new List();
+ Debug($"Scan {handlers.Items.Count} registered handler(s).");
+ foreach (var item in handlers.Items)
+ {
+ if (item.Modules == "FastCgiModule")
+ {
+ if (File.Exists(item.ScriptProcessor))
+ {
+ Debug($"* Found a valid FastCGI handler as {{ Name: {item.Name}, Path: {item.Path}, State: {item.GetState(handlers.AccessPolicy)}, Module: {item.TypeString}, Entry Type: {item.Flag} }}. File path: {item.ScriptProcessor}.");
+ foundPhpHandler.Add(item);
+ }
+ else
+ {
+ Error($"* Found an invalid FastCGI handler as {{Name: {item.Name}, Path: {item.Path}, State: {item.GetState(handlers.AccessPolicy)}, Module: {item.TypeString}, Entry Type: {item.Flag} }} because file path does not exist: {item.ScriptProcessor}.");
+ }
+ }
+ }
+
+ if (foundPhpHandler.Count == 0)
+ {
+ Error($"No valid FastCGI handler is registered for this web site.");
+ Error($"To run PHP on IIS, please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
+ return;
+ }
+
+ Debug(string.Empty);
+ var fastCgiFeature = new FastCgiFeature(module);
+ fastCgiFeature.Load();
+ Debug($"Scan {fastCgiFeature.Items.Count} registered FastCGI application(s).");
+ var foundPhp = new List();
+ foreach (var item in fastCgiFeature.Items)
+ {
+ var combination = string.IsNullOrWhiteSpace(item.Arguments) ? item.Path : item.Path + '|' + item.Arguments;
+ foreach (var handler in foundPhpHandler)
+ {
+ if (string.Equals(combination, handler.ScriptProcessor, StringComparison.OrdinalIgnoreCase))
+ {
+ Debug($"* Found FastCGI application registered as {{ Full path: {item.Path}, Arguments: {item.Arguments} }}.");
+ foundPhp.Add(item);
+ break;
+ }
+ }
+ }
+
+ if (foundPhp.Count == 0)
+ {
+ Error($"No suitable FastCGI appilcation is registered on this server.");
+ Error($"To run PHP on IIS, please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
+ return;
+ }
+
+ Debug(Environment.NewLine);
+ Debug($"Verify web stack installation versions.");
+ foreach (var item in foundPhp)
+ {
+ var path = item.Path;
+ if (path.TrimEnd('"').EndsWith("php-cgi.exe", StringComparison.OrdinalIgnoreCase))
+ {
+ // PHP
+ var info = FileVersionInfo.GetVersionInfo(path);
+ var version = $"{info.FileMajorPart}.{info.FileMinorPart}";
+ if (knownPhpVersions.ContainsKey(version))
+ {
+ var matched = knownPhpVersions[version];
+ if (matched.ExpiringDate <= DateTime.Now)
+ {
+ Error($"* PHP {info.FileVersion} ({path}) is unknown or obsolete. Please refer to http://php.net/supported-versions.php for more details.");
+ }
+ else if (matched.ExpiringDate > DateTime.Now && (matched.ExpiringDate - DateTime.Now).TotalDays < 180)
+ {
+ Warn($"* PHP {version} ({path}) will soon be obsolete. Please refer to http://php.net/supported-versions.php for more details.");
+ }
+ else
+ {
+ Debug($"* PHP {version} ({path}) is supported.");
+ }
+
+ var x86 = DialogHelper.GetImageArchitecture(path);
+ var cppFile = Path.Combine(
+ Environment.GetFolderPath(x86 ? Environment.SpecialFolder.SystemX86 : Environment.SpecialFolder.System),
+ $"msvcp{matched.CppVersion.Major}0.dll");
+ if (File.Exists(cppFile))
+ {
+ var cpp = FileVersionInfo.GetVersionInfo(cppFile);
+ if (cpp.FileMinorPart >= matched.CppVersion.Minor)
+ {
+ Debug($" Visual C++ runtime is detected (expected: {matched.CppVersion}, detected: {cpp.FileVersion}): {cppFile}.");
+ }
+ else
+ {
+ Error($" Visual C++ runtime {matched.CppVersion} is not detected. Please install it following the tips on https://windows.php.net/download/.");
+ }
+ }
+ else
+ {
+ Error($" Visual C++ {matched.CppVersion} runtime is not detected. Please install it following the tips on https://windows.php.net/download/.");
+ }
+ }
+ else
+ {
+ Error($"* PHP {info.FileVersion} ({path}) is unknown or obsolete. Please refer to http://php.net/supported-versions.php for more details.");
+ }
+ }
+ }
+
+ Debug(string.Empty);
+ var systemPath = Environment.GetEnvironmentVariable("Path");
+ Debug($"Windows Path environment variable: {systemPath}.");
+ Debug(string.Empty);
+ string[] paths = systemPath.Split(new char[1] { Path.PathSeparator });
+ foreach (var item in foundPhp)
+ {
+ var path = item.Path;
+ if (path.TrimEnd('"').EndsWith("php-cgi.exe", StringComparison.OrdinalIgnoreCase))
+ {
+ var rootFolder = Path.GetDirectoryName(path);
+ Debug($"[{rootFolder}]");
+ if (!Directory.Exists(rootFolder))
+ {
+ Error("Invalid root folder is found. Skip.");
+ continue;
+ }
+
+ var config = Path.Combine(rootFolder, "php.ini");
+ if (File.Exists(config))
+ {
+ Info($"Found PHP config file {config}.");
+ var parser = new ConcatenateDuplicatedKeysIniDataParser();
+ parser.Configuration.ConcatenateSeparator = " ";
+ var data = parser.Parse(File.ReadAllText(config));
+ var extensionFolder = data["PHP"]["extension_dir"];
+ if (extensionFolder == null)
+ {
+ extensionFolder = "\"ext\"";
+ }
+
+ extensionFolder = extensionFolder.Trim('"');
+
+ var fullPath = Path.Combine(rootFolder, extensionFolder);
+ Info($"PHP loadable extension folder: {fullPath}");
+ var extesionNames = data["PHP"]["extension"];
+ if (extesionNames == null)
+ {
+ Info("No extension to verify.");
+ }
+ else
+ {
+ var extensions = extesionNames.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ Info($"Found {extensions.Length} extension(s) to verify.");
+ var noError = true;
+ foreach (var name in extensions)
+ {
+ var fileName = Path.Combine(fullPath, $"php_{name}.dll");
+ if (!File.Exists(fileName))
+ {
+ Error($"* Extension {name} is listed, but on disk the file cannot be found {fileName}");
+ noError = false;
+ }
+ }
+
+ if (noError)
+ {
+ Info("All extension(s) listed can be found on disk.");
+ }
+ }
+ }
+ else
+ {
+ Warn($"Cannot find PHP config file {config}. Default settings are used.");
+ }
+
+ var matched = false;
+ foreach (var system in paths)
+ {
+ if (string.Equals(rootFolder, system, StringComparison.OrdinalIgnoreCase))
+ {
+ matched = true;
+ break;
+ }
+ }
+
+ if (matched)
+ {
+ Debug($"PHP installation has been added to Windows Path environment variable.");
+ }
+ else
+ {
+ Error($"PHP installation is not yet added to Windows Path environment variable. Please refer to https://learn.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh994590(v=ws.11) for more details.");
+ Warn($"Restart Jexus Manager and rerun PHP Diagnostics after changing Windows Path environment variable.");
+ }
+
+ Debug(string.Empty);
+
+ // TODO: verify other configuration in php.info.
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error analyzing PHP configuration");
+ }
+ }
}
}
diff --git a/JexusManager/Features/Main/SslDiagDialog.cs b/JexusManager/Features/Main/SslDiagDialog.cs
index 731b22f1..9d8444c4 100644
--- a/JexusManager/Features/Main/SslDiagDialog.cs
+++ b/JexusManager/Features/Main/SslDiagDialog.cs
@@ -21,9 +21,12 @@ namespace JexusManager.Features.Main
using System.Reactive.Linq;
using System.Collections.Generic;
using EnumsNET;
+ using Microsoft.Extensions.Logging;
public partial class SslDiagDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("SslDiagDialog");
+
public SslDiagDialog(IServiceProvider provider, ServerManager server)
: base(provider)
{
@@ -204,7 +207,7 @@ public SslDiagDialog(IServiceProvider provider, ServerManager server)
else
{
Error("#You have a private key that corresponds to this certificate but CryptAcquireCertificatePrivateKey failed.");
- System.Diagnostics.Debug.WriteLine("CryptAcquireCertificatePrivateKey failed");
+ _logger.LogWarning("CryptAcquireCertificatePrivateKey failed");
}
if (shouldRelease)
@@ -349,7 +352,7 @@ public SslDiagDialog(IServiceProvider provider, ServerManager server)
Error($"Problems detected on certificate store {binding.CertificateStoreName}.");
if (ex.HResult != Microsoft.Web.Administration.NativeMethods.NonExistingStore)
{
- System.Diagnostics.Debug.WriteLine($"CryptographicException {ex.HResult} from SslDiag.");
+ _logger.LogError(ex, "CryptographicException {HResult} from SslDiag", ex.HResult);
throw;
}
@@ -364,12 +367,12 @@ public SslDiagDialog(IServiceProvider provider, ServerManager server)
catch (CryptographicException ex)
{
Debug(ex.ToString());
- System.Diagnostics.Debug.WriteLine(ex);
- System.Diagnostics.Debug.WriteLine($"hResult {ex.HResult}");
+ _logger.LogError(ex, "CryptographicException {HResult} from SslDiag", ex.HResult);
}
catch (Exception ex)
{
Debug(ex.ToString());
+ _logger.LogError(ex, "Error analyzing certificate. HResult: {HResult}", ex.HResult);
}
}));
diff --git a/JexusManager/Features/Main/VsDiagDialog.cs b/JexusManager/Features/Main/VsDiagDialog.cs
index 0a05d956..9d488276 100644
--- a/JexusManager/Features/Main/VsDiagDialog.cs
+++ b/JexusManager/Features/Main/VsDiagDialog.cs
@@ -19,9 +19,14 @@ namespace JexusManager.Features.Main
using System.Xml.XPath;
using Newtonsoft.Json.Linq;
using System.Linq;
+ using System.Windows.Forms;
+ using Microsoft.Extensions.Logging;
+ using JexusManager;
public partial class VsDiagDialog : DialogForm
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("VsDiagDialog");
+
public VsDiagDialog(IServiceProvider provider, Site site)
: base(provider)
{
@@ -102,7 +107,7 @@ public VsDiagDialog(IServiceProvider provider, Site site)
catch (Exception ex)
{
Debug(ex.ToString());
- System.Diagnostics.Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error analyzing VS configuration");
}
}));
@@ -191,7 +196,7 @@ private void AnalyzeAspNetCoreProject(string project, Site site)
Info($"applicationUrl is {rawUrl}.");
var iisUrl = sslPort == 0 ? rawUrl : $"https://localhost:{sslPort}/";
var matched = false;
- foreach (Binding binding in site.Bindings)
+ foreach (Microsoft.Web.Administration.Binding binding in site.Bindings)
{
Info($"Binding {binding.ToShortString()}.");
if (string.Equals(binding.ToIisUrl(), iisUrl, StringComparison.OrdinalIgnoreCase))
@@ -229,92 +234,98 @@ private static bool IsAspNetCoreProject(XDocument xml)
private void AnalyzeWebProject(XDocument xml, XmlNamespaceManager namespaceManager, XNamespace name, Site site)
{
- Debug($"Analyze ASP.NET project.");
- Debug($"Extract web project settings.");
- var webSettings = xml.Root.XPathSelectElement("/x:Project/x:ProjectExtensions/x:VisualStudio/x:FlavorProperties/x:WebProjectProperties", namespaceManager);
- var useIIS = webSettings.Element(name + "UseIIS")?.Value;
- Info($"UseIIS: {useIIS}");
- var autoAssignPort = webSettings.Element(name + "AutoAssignPort")?.Value;
- Info($"AutoAssignPort: {autoAssignPort}");
- var developmentServerPort = webSettings.Element(name + "DevelopmentServerPort")?.Value;
- Info($"DevelopmentServerPort: {developmentServerPort}");
- var developmentServerVPath = webSettings.Element(name + "DevelopmentServerVPath")?.Value;
- Info($"DevelopmentServerVPath: {developmentServerVPath}");
- var iisUrl = webSettings.Element(name + "IISUrl")?.Value;
- Info($"IISUrl: {iisUrl}");
- var ntlmAuthentication = webSettings.Element(name + "NTLMAuthentication")?.Value;
- Info($"NTLMAuthentication: {ntlmAuthentication}");
- var useCustomServer = webSettings.Element(name + "UseCustomServer")?.Value;
- Info($"UseCustomServer: {useCustomServer}");
- var customServerUrl = webSettings.Element(name + "CustomServerUrl")?.Value;
- Info($"CustomServerUrl: {customServerUrl}");
- var saveServerSettingsInUserFile = webSettings.Element(name + "SaveServerSettingsInUserFile")?.Value;
- Info($"SaveServerSettingsInUserFile: {saveServerSettingsInUserFile}");
-
- var useIISExpress = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:UseIISExpress", namespaceManager)?.Value;
- Info($"UseIISExpress: {useIISExpress}");
- var iisExpressSSLPort = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressSSLPort", namespaceManager)?.Value;
- Info($"IISExpressSSLPort: {iisExpressSSLPort}");
- var iisExpressAnonymousAuthentication = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressAnonymousAuthentication", namespaceManager)?.Value;
- Info($"IISExpressAnonymousAuthentication: {iisExpressAnonymousAuthentication}");
- var iisExpressWindowsAuthentication = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressWindowsAuthentication", namespaceManager)?.Value;
- Info($"IISExpressWindowsAuthentication: {iisExpressWindowsAuthentication}");
- var iisExpressUseClassicPipelineMode = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressUseClassicPipelineMode", namespaceManager)?.Value;
- Info($"IISExpressUseClassicPipelineMode: {iisExpressUseClassicPipelineMode}");
- var useGlobalApplicationHostFile = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:UseGlobalApplicationHostFile", namespaceManager)?.Value;
- Info($"UseGlobalApplicationHostFile: {useGlobalApplicationHostFile}");
-
- Info($"Scan all bindings.");
- bool matched = false;
- if (string.Equals(useIIS, "true", StringComparison.OrdinalIgnoreCase))
+ try
{
- if (string.Equals(useIISExpress, "true", StringComparison.OrdinalIgnoreCase))
+ Debug($"Analyze ASP.NET project.");
+ Debug($"Extract web project settings.");
+ var webSettings = xml.Root.XPathSelectElement("/x:Project/x:ProjectExtensions/x:VisualStudio/x:FlavorProperties/x:WebProjectProperties", namespaceManager);
+ var useIIS = webSettings.Element(name + "UseIIS")?.Value;
+ Info($"UseIIS: {useIIS}");
+ var autoAssignPort = webSettings.Element(name + "AutoAssignPort")?.Value;
+ Info($"AutoAssignPort: {autoAssignPort}");
+ var developmentServerPort = webSettings.Element(name + "DevelopmentServerPort")?.Value;
+ Info($"DevelopmentServerPort: {developmentServerPort}");
+ var developmentServerVPath = webSettings.Element(name + "DevelopmentServerVPath")?.Value;
+ Info($"DevelopmentServerVPath: {developmentServerVPath}");
+ var iisUrl = webSettings.Element(name + "IISUrl")?.Value;
+ Info($"IISUrl: {iisUrl}");
+ var ntlmAuthentication = webSettings.Element(name + "NTLMAuthentication")?.Value;
+ Info($"NTLMAuthentication: {ntlmAuthentication}");
+ var useCustomServer = webSettings.Element(name + "UseCustomServer")?.Value;
+ Info($"UseCustomServer: {useCustomServer}");
+ var customServerUrl = webSettings.Element(name + "CustomServerUrl")?.Value;
+ Info($"CustomServerUrl: {customServerUrl}");
+ var saveServerSettingsInUserFile = webSettings.Element(name + "SaveServerSettingsInUserFile")?.Value;
+ Info($"SaveServerSettingsInUserFile: {saveServerSettingsInUserFile}");
+
+ var useIISExpress = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:UseIISExpress", namespaceManager)?.Value;
+ Info($"UseIISExpress: {useIISExpress}");
+ var iisExpressSSLPort = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressSSLPort", namespaceManager)?.Value;
+ Info($"IISExpressSSLPort: {iisExpressSSLPort}");
+ var iisExpressAnonymousAuthentication = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressAnonymousAuthentication", namespaceManager)?.Value;
+ Info($"IISExpressAnonymousAuthentication: {iisExpressAnonymousAuthentication}");
+ var iisExpressWindowsAuthentication = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressWindowsAuthentication", namespaceManager)?.Value;
+ Info($"IISExpressWindowsAuthentication: {iisExpressWindowsAuthentication}");
+ var iisExpressUseClassicPipelineMode = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:IISExpressUseClassicPipelineMode", namespaceManager)?.Value;
+ Info($"IISExpressUseClassicPipelineMode: {iisExpressUseClassicPipelineMode}");
+ var useGlobalApplicationHostFile = xml.Root.XPathSelectElement("/x:Project/x:PropertyGroup/x:UseGlobalApplicationHostFile", namespaceManager)?.Value;
+ Info($"UseGlobalApplicationHostFile: {useGlobalApplicationHostFile}");
+
+ Info($"Scan all bindings.");
+ bool matched = false;
+ if (string.Equals(useIIS, "true", StringComparison.OrdinalIgnoreCase))
{
- // IIS Express
- Info($"IIS Express is used for this project.");
- foreach (Binding binding in site.Bindings)
+ if (string.Equals(useIISExpress, "true", StringComparison.OrdinalIgnoreCase))
{
- Info($"Binding {binding.ToShortString()}.");
- if (string.Equals(binding.ToIisUrl(), iisUrl, StringComparison.OrdinalIgnoreCase))
+ // IIS Express
+ Info($"IIS Express is used for this project.");
+ foreach (Microsoft.Web.Administration.Binding binding in site.Bindings)
{
- Info($"A matching binding is found for {iisUrl}.");
- matched = true;
+ Info($"Binding {binding.ToShortString()}.");
+ if (string.Equals(binding.ToIisUrl(), iisUrl, StringComparison.OrdinalIgnoreCase))
+ {
+ Info($"A matching binding is found for {iisUrl}.");
+ matched = true;
+ }
}
}
- }
- else if (string.Equals(useIISExpress, "false", StringComparison.OrdinalIgnoreCase))
- {
- // IIS
- Info($"Full IIS is used for this project.");
- foreach (Binding binding in site.Bindings)
+ else if (string.Equals(useIISExpress, "false", StringComparison.OrdinalIgnoreCase))
{
- Info($"Binding {binding.ToShortString()}.");
- if (string.Equals(binding.ToIisUrl(), iisUrl, StringComparison.OrdinalIgnoreCase))
+ // IIS
+ Info($"Full IIS is used for this project.");
+ foreach (Microsoft.Web.Administration.Binding binding in site.Bindings)
{
- Info($"A matching binding is found for {iisUrl}.");
- matched = true;
+ Info($"Binding {binding.ToShortString()}.");
+ if (string.Equals(binding.ToIisUrl(), iisUrl, StringComparison.OrdinalIgnoreCase))
+ {
+ Info($"A matching binding is found for {iisUrl}.");
+ matched = true;
+ }
}
}
+ else
+ {
+ Warn($"An unexpected condition hit.");
+ _logger.LogWarning("Unexpected condition hit");
+ return;
+ }
}
else
{
- //TODO: What?
- Warn($"An unexpected condition hit.");
- System.Diagnostics.Debug.WriteLine("unexpected condition hit.");
+ // External server
+ Warn($"External server is used.");
return;
}
- }
- else
- {
- // External server
- Warn($"External server is used.");
- return;
- }
- if (!matched)
+ if (!matched)
+ {
+ Error($"No matching binding is found for {iisUrl}.");
+ Error($"Please edit project file and IIS Express configuration file to match each other.");
+ }
+ }
+ catch (Exception ex)
{
- Error($"No matching binding is found for {iisUrl}.");
- Error($"Please edit project file and IIS Express configuration file to match each other.");
+ _logger.LogError(ex, "Error analyzing web project");
}
}
diff --git a/JexusManager/JexusManager.csproj b/JexusManager/JexusManager.csproj
index 9c54a135..7926dd98 100644
--- a/JexusManager/JexusManager.csproj
+++ b/JexusManager/JexusManager.csproj
@@ -50,6 +50,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/JexusManager/LoggingService.cs b/JexusManager/LoggingService.cs
new file mode 100644
index 00000000..c9e9458b
--- /dev/null
+++ b/JexusManager/LoggingService.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Concurrent;
+using System.Drawing;
+using System.Windows.Forms;
+using Microsoft.Extensions.Logging;
+
+namespace JexusManager
+{
+ public class TextBoxLoggerProvider : ILoggerProvider
+ {
+ private readonly RichTextBox _textBox;
+ private readonly ConcurrentQueue<(string text, Color color)> _pendingMessages = new ConcurrentQueue<(string, Color)>();
+ private readonly object _lock = new object();
+ private bool _isInitialized;
+ private Timer _initCheckTimer;
+
+ public TextBoxLoggerProvider(RichTextBox textBox)
+ {
+ _textBox = textBox;
+
+ // Start a timer to check for control initialization
+ _initCheckTimer = new Timer();
+ _initCheckTimer.Interval = 100; // Check every 100ms
+ _initCheckTimer.Tick += (s, e) => {
+ if (_textBox.IsHandleCreated && !_isInitialized)
+ {
+ lock (_lock)
+ {
+ if (!_isInitialized)
+ {
+ _isInitialized = true;
+ // Process any pending messages
+ while (_pendingMessages.TryDequeue(out var message))
+ {
+ AppendTextSafe(message.text, message.color);
+ }
+ _initCheckTimer.Stop();
+ _initCheckTimer.Dispose();
+ _initCheckTimer = null;
+ }
+ }
+ }
+ };
+ _initCheckTimer.Start();
+ }
+
+ public ILogger CreateLogger(string categoryName)
+ {
+ return new TextBoxLogger(this, categoryName);
+ }
+
+ public void Dispose()
+ {
+ _initCheckTimer?.Dispose();
+ }
+
+ internal void LogMessage(string message, Color color)
+ {
+ if (!_isInitialized)
+ {
+ _pendingMessages.Enqueue((message + Environment.NewLine, color));
+ return;
+ }
+
+ AppendTextSafe(message + Environment.NewLine, color);
+ }
+
+ private void AppendTextSafe(string text, Color color)
+ {
+ if (_textBox.IsDisposed)
+ return;
+
+ if (_textBox.InvokeRequired)
+ {
+ try
+ {
+ _textBox.Invoke(new Action(() => AppendTextSafe(text, color)));
+ }
+ catch (ObjectDisposedException)
+ {
+ // Form might be closing
+ }
+ return;
+ }
+
+ int start = _textBox.TextLength;
+ _textBox.AppendText(text);
+ int end = _textBox.TextLength;
+
+ // Color the new text
+ _textBox.Select(start, end - start);
+ _textBox.SelectionColor = color;
+ _textBox.SelectionLength = 0; // Clear selection
+
+ // Keep last 1000 lines
+ while (_textBox.Lines.Length > 1000)
+ {
+ var index = _textBox.GetFirstCharIndexFromLine(0);
+ var length = _textBox.GetFirstCharIndexFromLine(1) - index;
+ if (length > 0)
+ _textBox.Text = _textBox.Text.Remove(index, length);
+ }
+
+ _textBox.SelectionStart = _textBox.TextLength;
+ _textBox.ScrollToCaret();
+ }
+ }
+
+ public class TextBoxLogger : ILogger
+ {
+ private readonly TextBoxLoggerProvider _provider;
+
+ public TextBoxLogger(TextBoxLoggerProvider provider, string categoryName)
+ {
+ _provider = provider;
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return null;
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return true;
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel))
+ return;
+
+ // Convert log levels to short names and get appropriate colors
+ var (levelText, color) = logLevel switch
+ {
+ LogLevel.Trace => ("trc", Color.Gray),
+ LogLevel.Debug => ("dbg", Color.LightGray),
+ LogLevel.Information => ("inf", Color.White),
+ LogLevel.Warning => ("wrn", Color.Yellow),
+ LogLevel.Error => ("err", Color.Red),
+ LogLevel.Critical => ("crt", Color.Magenta),
+ _ => ("unk", Color.DarkGray)
+ };
+
+ var message = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{levelText}] {formatter(state, exception)}";
+ if (exception != null)
+ message += Environment.NewLine + exception;
+
+ _provider.LogMessage(message, color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/JexusManager/MainForm.cs b/JexusManager/MainForm.cs
index d2a1f56f..5340f05b 100644
--- a/JexusManager/MainForm.cs
+++ b/JexusManager/MainForm.cs
@@ -58,9 +58,13 @@ namespace JexusManager
using JexusManager.Features.Asp;
using JexusManager.Features.TraceFailedRequests;
using System.Diagnostics;
+ using Microsoft.Extensions.Logging;
+ using System.Drawing;
public sealed partial class MainForm : Form
{
+ private readonly Panel _logPanel;
+ private readonly RichTextBox _logTextBox;
private const string expressGlobalInstanceName = "Global";
private readonly List _providers;
private readonly ServiceContainer _serviceContainer;
@@ -68,13 +72,39 @@ public sealed partial class MainForm : Form
private TreeNode IisExpressRoot { get; }
private TreeNode IisRoot { get; }
private TreeNode JexusRoot { get; }
+ private TreeNode StartPage { get; set; }
public ManagementUIService UIService { get; }
- public MainForm(List files)
+ public MainForm(List files, RichTextBox texBox)
{
InitializeComponent();
+ // Create logging panel
+ _logTextBox = texBox;
+
+ _logPanel = new Panel
+ {
+ Dock = DockStyle.Bottom,
+ Height = 200,
+ Visible = false
+ };
+ _logPanel.Controls.Add(_logTextBox);
+ Controls.Add(_logPanel);
+
+ LogHelper.GetLogger().LogInformation("Jexus Manager starting up. Version: {Version}",
+ Assembly.GetExecutingAssembly().GetName().Version);
+
+ // Add toggle logging menu item
+ helpToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
+ var toggleLogItem = new ToolStripMenuItem("Show &Logs", null, (s, e) =>
+ {
+ _logPanel.Visible = !_logPanel.Visible;
+ ((ToolStripMenuItem)s).Text = _logPanel.Visible ? "Hide &Logs" : "Show &Logs";
+ });
+ helpToolStripMenuItem.DropDownItems.Add(toggleLogItem);
+
+
removeToolStripMenuItem.Image = DefaultTaskList.RemoveImage;
removeToolStripMenuItem1.Image = DefaultTaskList.RemoveImage;
btnRemoveFarmServer.Image = DefaultTaskList.RemoveImage;
@@ -97,7 +127,8 @@ public MainForm(List files)
actRunAsAdmin.Image = NativeMethods.GetShieldIcon();
btnAbout.Text = string.Format("About Jexus Manager {0}", Assembly.GetExecutingAssembly().GetName().Version);
- treeView1.Nodes.Add(new PlaceholderTreeNode("Start Page", 0) { ContextMenuStrip = cmsIis });
+ StartPage = new PlaceholderTreeNode("Start Page", 0) { ContextMenuStrip = cmsIis };
+ treeView1.Nodes.Add(StartPage);
if (!Helper.IsRunningOnMono())
{
IisExpressRoot = new PlaceholderTreeNode("IIS Express", 10) { ContextMenuStrip = cmsIis };
@@ -217,8 +248,10 @@ internal Action SaveMenuItem
private void LoadIisExpress()
{
+ LogHelper.GetLogger().LogInformation("Loading IIS Express servers");
if (!IisExpressServerManager.ServerInstalled)
{
+ LogHelper.GetLogger().LogWarning("IIS Express is not installed on this machine");
return;
}
@@ -327,6 +360,7 @@ private void LoadIis()
private void RegisterServer(ServerTreeNode data)
{
+ LogHelper.GetLogger().LogInformation("Registering server {Name} ({Mode})", data.DisplayName, data.Mode);
data.ContextMenuStrip = cmsServer;
if (data.Mode == WorkingMode.IisExpress)
{
@@ -383,24 +417,28 @@ private void actCreateSite_Execute(object sender, EventArgs e)
var selected = treeView1.SelectedNode;
if (selected == null)
{
+ LogHelper.GetLogger().LogWarning("Cannot create site: no node selected");
return;
}
var data = GetCurrentData(selected);
if (data.IsBusy)
{
+ LogHelper.GetLogger().LogWarning("Cannot create site: server {Name} is busy", data.DisplayName);
return;
}
if (data.ServerManager == null)
{
- Debug.WriteLine($"null server: {data.DisplayName} : {data.Mode} : {selected.Text} : {selected.GetType().FullName}");
+ LogHelper.GetLogger().LogWarning("Null server: {DisplayName} : {Mode} : {Text} : {Type}",
+ data.DisplayName, data.Mode, selected.Text, selected.GetType().FullName);
return;
}
if (data.ServerManager.Sites == null)
{
- Debug.WriteLine($"null sites collection: {data.DisplayName} : {data.Mode} : {selected.Text} : {selected.GetType().FullName} : {data.ServerManager.FileName}");
+ LogHelper.GetLogger().LogWarning("Null sites collection: {DisplayName} : {Mode} : {Text} : {Type} : {FileName}",
+ data.DisplayName, data.Mode, selected.Text, selected.GetType().FullName, data.ServerManager.FileName);
return;
}
@@ -412,9 +450,11 @@ private void actCreateSite_Execute(object sender, EventArgs e)
return;
}
+ LogHelper.GetLogger().LogInformation("Creating new site on server {Server}", data.DisplayName);
data.ServerManager.Sites.Add(dialog.NewSite);
dialog.NewSite.Applications[0].Save();
data.ServerManager.CommitChanges();
+ LogHelper.GetLogger().LogInformation("Site {SiteName} created successfully", dialog.NewSite.Name);
AddSiteNode(dialog.NewSite);
}
@@ -492,6 +532,7 @@ private void btnRemoveSite_Click(object sender, EventArgs e)
private void Form1FormClosing(object sender, FormClosingEventArgs e)
{
+ LogHelper.GetLogger().LogInformation("Application shutting down");
if (actSave.Enabled)
{
var result = UIService.ShowMessage("The connection list has changed. Do you want to save changes?", Text, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
@@ -515,7 +556,7 @@ private void Form1FormClosing(object sender, FormClosingEventArgs e)
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ LogHelper.GetLogger().LogError(ex, "Error during server shutdown");
var last = ex;
Exception previous = null;
while (last.InnerException != null)
@@ -664,7 +705,7 @@ private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Text != ManagerTreeNode.TempNodeName)
{
- Debug.WriteLine($"wrong node {e.Node.GetType().FullName} {e.Node.Text}");
+ LogHelper.GetLogger().LogWarning("Wrong node type: {Type} Name: {Text}", e.Node.GetType().FullName, e.Node.Text);
}
return;
@@ -939,7 +980,7 @@ internal void ConnectToServer()
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ LogHelper.GetLogger().LogError(ex, "Error during server shutdown");
File.WriteAllText(DialogHelper.DebugLog, ex.ToString());
var last = ex;
while (last is AggregateException)
@@ -1100,7 +1141,8 @@ private void treeView1_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEve
private async void btnUpdate_Click(object sender, EventArgs e)
{
- await UpdateHelper.FindUpdate();
+ // Navigate to the Start Page which will display update information
+ treeView1.SelectedNode = StartPage;
}
private void btnRemoveFarmServer_Click(object sender, EventArgs e)
diff --git a/JexusManager/Program.cs b/JexusManager/Program.cs
index a3c873a8..3bef1b13 100644
--- a/JexusManager/Program.cs
+++ b/JexusManager/Program.cs
@@ -9,15 +9,20 @@
namespace JexusManager
{
using JexusManager.Dialogs;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
using Mono.Options;
using System;
using System.Collections.Generic;
+ using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
internal static class Program
{
+ public static IServiceProvider ServiceProvider { get; private set; }
+
///
/// The main entry point for the application.
///
@@ -50,12 +55,32 @@ private static void Main(string[] args)
Microsoft.Web.Administration.JexusServerManager.Enabled = jexus;
+ // UI initialization
ApplicationConfiguration.Initialize();
-
- // TODO: set encryption support
- // ProtectedConfigurationProvider.Provider = new WorkingEncryptionServiceProvider();
+ var textBox = new RichTextBox
+ {
+ Multiline = true,
+ ReadOnly = true,
+ Dock = DockStyle.Fill,
+ ScrollBars = RichTextBoxScrollBars.Vertical,
+ Font = new Font("Consolas", 9),
+ BackColor = Color.Black,
+ ForeColor = Color.LightGray
+ };
+
+ var provider = new TextBoxLoggerProvider(textBox);
+ var services = new ServiceCollection();
+ services.AddLogging(builder =>
+ {
+ builder.AddProvider(provider);
+ });
+
+ ServiceProvider = services.BuildServiceProvider();
+
+ var loggerFactory = ServiceProvider.GetRequiredService();
+ LogHelper.Initialize(loggerFactory);
- Application.Run(new MainForm(extra));
+ Application.Run(new MainForm(extra, textBox));
}
private static void ShowHelp(OptionSet optionSet)
diff --git a/JexusManager/Tree/ManagerTreeNode.cs b/JexusManager/Tree/ManagerTreeNode.cs
index e23437b0..98c31d75 100644
--- a/JexusManager/Tree/ManagerTreeNode.cs
+++ b/JexusManager/Tree/ManagerTreeNode.cs
@@ -5,18 +5,22 @@
namespace JexusManager.Tree
{
using System;
- using System.Collections.Generic;
- using System.ComponentModel.Design;
using System.IO;
- using System.Windows.Forms;
+ using Microsoft.Extensions.Logging;
+ using JexusManager;
using Microsoft.Web.Administration;
using Microsoft.Web.Management.Server;
using Application = Microsoft.Web.Administration.Application;
+ using System.Collections.Generic;
+ using System.Windows.Forms;
+ using System.ComponentModel.Design;
internal abstract class ManagerTreeNode : TreeNode
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("ManagerTreeNode");
+
public const string TempNodeName = "ManagerTreeNodeTemp";
public IServiceProvider ServiceProvider { get; set; }
@@ -41,29 +45,29 @@ protected ManagerTreeNode(string text, IServiceProvider serviceProvider) : base(
protected void LoadChildren(Application rootApp, int rootLevel, string rootFolder, string pathToSite, ContextMenuStrip phyMenu, ContextMenuStrip vDirMenu, ContextMenuStrip appMenu)
{
- var treeNodes = new List();
- foreach (VirtualDirectory virtualDirectory in rootApp.VirtualDirectories)
+ try
{
- var path = virtualDirectory.PathToSite();
- if (!path.StartsWith(pathToSite))
+ var treeNodes = new List();
+ foreach (VirtualDirectory virtualDirectory in rootApp.VirtualDirectories)
{
- continue;
- }
+ var path = virtualDirectory.PathToSite();
+ if (!path.StartsWith(pathToSite))
+ {
+ continue;
+ }
- if (GetLevel(path) != rootLevel + 1)
- {
- continue;
- }
+ if (GetLevel(path) != rootLevel + 1)
+ {
+ continue;
+ }
- // IMPORTANT: only create level+1 vDir nodes.
- var virtualDirectoryNode = new VirtualDirectoryTreeNode(ServiceProvider, virtualDirectory, ServerNode) { ContextMenuStrip = vDirMenu };
- treeNodes.Add(virtualDirectoryNode);
- }
+ // IMPORTANT: only create level+1 vDir nodes.
+ var virtualDirectoryNode = new VirtualDirectoryTreeNode(ServiceProvider, virtualDirectory, ServerNode) { ContextMenuStrip = vDirMenu };
+ treeNodes.Add(virtualDirectoryNode);
+ }
- var loaded = new HashSet();
- if (Directory.Exists(rootFolder))
- {
- try
+ var loaded = new HashSet();
+ if (Directory.Exists(rootFolder))
{
// IMPORTANT: only create level+1 physical nodes.
foreach (var folder in new DirectoryInfo(rootFolder).GetDirectories())
@@ -105,46 +109,46 @@ protected void LoadChildren(Application rootApp, int rootLevel, string rootFolde
treeNodes.Add(directory);
}
}
- catch (IOException ex)
- {
- System.Diagnostics.Debug.WriteLine(ex);
- }
- }
- foreach (Application application in rootApp.Site.Applications)
- {
- if (application.IsRoot())
+ foreach (Application application in rootApp.Site.Applications)
{
- continue;
- }
+ if (application.IsRoot())
+ {
+ continue;
+ }
- if (!application.Path.StartsWith(pathToSite))
- {
- continue;
- }
+ if (!application.Path.StartsWith(pathToSite))
+ {
+ continue;
+ }
- if (loaded.Contains(application.Path))
- {
- continue;
- }
+ if (loaded.Contains(application.Path))
+ {
+ continue;
+ }
- if (GetLevel(application.Path) != rootLevel + 1)
- {
- continue;
- }
+ if (GetLevel(application.Path) != rootLevel + 1)
+ {
+ continue;
+ }
- if (application.VirtualDirectories.Count == 0)
- {
- continue;
+ if (application.VirtualDirectories.Count == 0)
+ {
+ continue;
+ }
+
+ // IMPORTANT: only create level+1 physical nodes.
+ var appNode = new ApplicationTreeNode(ServiceProvider, application, ServerNode) { ContextMenuStrip = appMenu };
+ treeNodes.Add(appNode);
}
- // IMPORTANT: only create level+1 physical nodes.
- var appNode = new ApplicationTreeNode(ServiceProvider, application, ServerNode) { ContextMenuStrip = appMenu };
- treeNodes.Add(appNode);
+ treeNodes.Sort(s_comparer);
+ Nodes.AddRange(treeNodes.ToArray());
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error loading children");
}
-
- treeNodes.Sort(s_comparer);
- Nodes.AddRange(treeNodes.ToArray());
}
private static readonly TreeNodeComparer s_comparer = new TreeNodeComparer();
@@ -168,7 +172,7 @@ public void Explore()
public void EditPermissions()
{
- NativeMethods.ShowFileProperties(Folder);
+ Microsoft.Web.Administration.NativeMethods.ShowFileProperties(Folder);
}
public void Browse()
diff --git a/JexusManager/Tree/PlaceholderTreeNode.cs b/JexusManager/Tree/PlaceholderTreeNode.cs
index 6b3439b6..3b68d9ca 100644
--- a/JexusManager/Tree/PlaceholderTreeNode.cs
+++ b/JexusManager/Tree/PlaceholderTreeNode.cs
@@ -17,6 +17,8 @@ namespace JexusManager.Tree
internal sealed class PlaceholderTreeNode : ManagerTreeNode
{
+ private static HomePage _homePage;
+
public PlaceholderTreeNode(string name, int image)
: base(name, null)
{
@@ -36,7 +38,12 @@ public PlaceholderTreeNode(string name, int image)
public override void LoadPanels(MainForm mainForm, ServiceContainer serviceContainer, List moduleProviders)
{
- mainForm.LoadInner(new HomePage());
+ if (_homePage == null)
+ {
+ _homePage = new HomePage();
+ }
+
+ mainForm.LoadInner(_homePage);
}
public override void HandleDoubleClick()
diff --git a/JexusManager/Tree/ServerTreeNode.cs b/JexusManager/Tree/ServerTreeNode.cs
index 85d9fab7..3bd33abd 100644
--- a/JexusManager/Tree/ServerTreeNode.cs
+++ b/JexusManager/Tree/ServerTreeNode.cs
@@ -3,24 +3,26 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
-using System.Collections.Generic;
-using System.ComponentModel.Design;
-using System.IO;
-using System.Text;
using System.Windows.Forms;
-
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using Microsoft.Web.Administration;
+using System.IO;
using JexusManager.Dialogs;
-using JexusManager.Features.Main;
+using System.ComponentModel.Design;
+using System.Text;
using JexusManager.Services;
-
-using Microsoft.Web.Administration;
-using Microsoft.Web.Management.Client;
+using System.Collections.Generic;
using Microsoft.Web.Management.Server;
+using JexusManager.Features.Main;
+using Microsoft.Web.Management.Client;
namespace JexusManager.Tree
{
- internal sealed class ServerTreeNode : ManagerTreeNode
+ internal class ServerTreeNode : ManagerTreeNode
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("ServerTreeNode");
+
private enum NodeStatus
{
Default = 0,
@@ -233,7 +235,7 @@ public override void HandleDoubleClick()
}
catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error connecting to server");
File.WriteAllText(DialogHelper.DebugLog, ex.ToString());
var last = ex;
while (last is AggregateException)
diff --git a/JexusManager/UpdateHelper.cs b/JexusManager/UpdateHelper.cs
index 47870520..8a4a5103 100644
--- a/JexusManager/UpdateHelper.cs
+++ b/JexusManager/UpdateHelper.cs
@@ -13,8 +13,23 @@ namespace JexusManager
internal static class UpdateHelper
{
- public static async Task FindUpdate()
+ public class UpdateInfo
{
+ public bool UpdateAvailable { get; set; }
+ public Version CurrentVersion { get; set; }
+ public Version LatestVersion { get; set; }
+ public string ReleaseUrl { get; set; }
+ public string ErrorMessage { get; set; }
+ }
+
+ public static async Task CheckForUpdate()
+ {
+ var updateInfo = new UpdateInfo
+ {
+ ReleaseUrl = "https://github.com/jexuswebserver/JexusManager/releases",
+ CurrentVersion = Assembly.GetExecutingAssembly().GetName().Version
+ };
+
string version = null;
var previous = ServicePointManager.SecurityProtocol;
try
@@ -24,8 +39,8 @@ public static async Task FindUpdate()
var releases = await client.Repository.Release.GetAll("jexuswebserver", "JexusManager");
if (releases.Count == 0)
{
- DialogHelper.MessageBoxShow("No update is found.");
- return;
+ updateInfo.ErrorMessage = "No update is found.";
+ return updateInfo;
}
var recent = releases[0];
@@ -33,31 +48,24 @@ public static async Task FindUpdate()
}
catch (Exception)
{
- DialogHelper.MessageBoxShow("Cannot connect to GitHub. Will open https://github.com/jexuswebserver/JexusManager/releases.");
- DialogHelper.ProcessStart("https://github.com/jexuswebserver/JexusManager/releases");
- return;
+ updateInfo.ErrorMessage = "Cannot connect to GitHub.";
+ return updateInfo;
}
finally
{
ServicePointManager.SecurityProtocol = previous;
}
- Version latest;
- if (!Version.TryParse(version, out latest))
- {
- DialogHelper.MessageBoxShow("No update is found.");
- return;
- }
-
- var current = Assembly.GetExecutingAssembly().GetName().Version;
- if (current >= latest)
+ if (!Version.TryParse(version, out Version latest))
{
- DialogHelper.MessageBoxShow($"{current} is in use. No update is found, and {latest} is latest release.");
- return;
+ updateInfo.ErrorMessage = "No update is found.";
+ return updateInfo;
}
- DialogHelper.MessageBoxShow($"{current} is in use. An update ({latest}) is available. Will open https://github.com/jexuswebserver/JexusManager/releases.");
- DialogHelper.ProcessStart("https://github.com/jexuswebserver/JexusManager/releases");
+ updateInfo.LatestVersion = latest;
+ updateInfo.UpdateAvailable = updateInfo.CurrentVersion < latest;
+
+ return updateInfo;
}
}
}
diff --git a/JexusManager/Wizards/ConnectionWizard/CredentialsPage.cs b/JexusManager/Wizards/ConnectionWizard/CredentialsPage.cs
index 637f6e5b..fffe1651 100644
--- a/JexusManager/Wizards/ConnectionWizard/CredentialsPage.cs
+++ b/JexusManager/Wizards/ConnectionWizard/CredentialsPage.cs
@@ -5,20 +5,20 @@
namespace JexusManager.Wizards.ConnectionWizard
{
using System;
- using System.Diagnostics;
- using System.IO;
- using System.Net.Security;
- using System.Text;
- using System.Threading;
using System.Windows.Forms;
-
- using JexusManager.Dialogs;
-
- using Microsoft.Web.Administration;
+ using Microsoft.Extensions.Logging;
+ using JexusManager;
using Microsoft.Web.Management.Client.Win32;
+ using Microsoft.Web.Administration;
+ using System.Threading;
+ using System.Net.Security;
+ using JexusManager.Dialogs;
+ using System.Text;
public partial class CredentialsPage : WizardPage
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("CredentialsPage");
+
public CredentialsPage()
{
InitializeComponent();
@@ -138,21 +138,26 @@ private bool OpenConnection(SynchronizationContext context)
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
- File.WriteAllText(DialogHelper.DebugLog, ex.ToString());
- var last = ex;
- while (last is AggregateException)
- {
- last = last.InnerException;
- }
-
- var message = new StringBuilder();
- message.AppendLine("Could not connect to the specified computer.")
- .AppendLine()
- .AppendFormat("Details: {0}", last?.Message);
- service?.ShowMessage(message.ToString(), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ RaiseError(ex);
return false;
}
}
+
+ private void RaiseError(Exception ex)
+ {
+ _logger.LogError(ex, "Error in credentials page");
+ var service = (IManagementUIService)GetService(typeof(IManagementUIService));
+ var last = ex;
+ while (last is AggregateException)
+ {
+ last = last.InnerException;
+ }
+
+ var message = new StringBuilder();
+ message.AppendLine("Could not connect to the specified computer.")
+ .AppendLine()
+ .AppendFormat("Details: {0}", last?.Message);
+ service?.ShowMessage(message.ToString(), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
}
}
diff --git a/Microsoft.Web.Administration/AspNetCoreHelper.cs b/Microsoft.Web.Administration/AspNetCoreHelper.cs
index 7800b681..ccb2f160 100644
--- a/Microsoft.Web.Administration/AspNetCoreHelper.cs
+++ b/Microsoft.Web.Administration/AspNetCoreHelper.cs
@@ -6,14 +6,21 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using System.Xml.Linq;
using System.Xml.XPath;
+using JexusManager;
+using Microsoft.Extensions.Logging;
namespace Microsoft.Web.Administration
{
internal static class AspNetCoreHelper
{
- public static void FixConfigFile(string fileName)
+ private static readonly ILogger _logger = LogHelper.GetLogger("AspNetCoreHelper");
+
+ internal static void FixConfigFile(string fileName)
{
var doc = XDocument.Load(fileName);
var virturalDirectories = doc.Descendants("virtualDirectory");
@@ -198,7 +205,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
.FirstOrDefault();
if (devServerVersion == null)
{
- Debug.WriteLine($"Blazor dev server missing");
+ _logger.LogWarning("Blazor dev server missing in project {Project}", project);
return;
}
@@ -208,7 +215,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
int index = input.IndexOf("net");
if (index == -1)
{
- Debug.WriteLine($"Unknown framework {input}");
+ _logger.LogWarning("Unknown framework {Framework} in project {Project}", input, project);
return;
}
@@ -219,7 +226,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
var devServerLibrary = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages", "microsoft.aspnetcore.components.webassembly.devserver", devServerVersion, "tools", "blazor-devserver.dll");
if (!File.Exists(devServerLibrary))
{
- Debug.WriteLine($"Cannot find Blazor dev server library {devServerVersion}");
+ _logger.LogWarning("Cannot find Blazor dev server library version {Version}", devServerVersion);
return;
}
@@ -236,7 +243,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
int index = input.IndexOf("net");
if (index == -1)
{
- Debug.WriteLine($"Unknown framework {input}");
+ _logger.LogWarning("Unknown framework {Framework} in project {Project}", input, project);
return;
}
@@ -255,7 +262,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
}
else
{
- Debug.WriteLine($"impossible ASP.NET Core version {version}");
+ _logger.LogInformation("ASP.NET Core {Version} is older than baseline {Baseline}", version, baseVersion);
}
}
else
@@ -274,7 +281,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
var files = Directory.GetFiles(Path.Combine(root, "bin", "Debug", input), "*.dll");
if (files.Length == 1)
{
- Debug.WriteLine($"Didn't find the expected assembly. Choose another instead.");
+ _logger.LogDebug("Expected assembly not found, using alternative: {Assembly}", files[0]);
primary = files[0];
}
else
@@ -285,7 +292,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
if (primary == null)
{
- Debug.WriteLine($"Didn't find compiled assembly for {input}");
+ _logger.LogWarning("Could not find compiled assembly for framework {Framework} in {Path}", input, root);
return;
}
@@ -294,7 +301,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
if (!File.Exists(file))
{
// Not VS 15.2 and above
- Debug.WriteLine($"Didn't detect VS 15.2 or above");
+ _logger.LogWarning("Visual Studio 15.2 or above not detected. VSWhere not found at {Path}", file);
return;
}
@@ -311,7 +318,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
var launcher = $@"{folder}\Common7\IDE\Extensions\Microsoft\Web Tools\ProjectSystem\VSIISExeLauncher.exe";
if (!File.Exists(launcher))
{
- Debug.WriteLine($"Didn't detect VSIISExeLauncher");
+ _logger.LogWarning("VSIISExeLauncher not found at {Path}", launcher);
return;
}
@@ -325,6 +332,7 @@ public static void InjectEnvironmentVariables(Site site, ProcessStartInfo startI
private static string RestoreAndBuild(string root)
{
var dotnet = Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\dotnet\dotnet.exe");
+ _logger.LogInformation("Running dotnet restore in {Path}", root);
var restore = Process.Start(new ProcessStartInfo
{
FileName = dotnet,
@@ -333,6 +341,8 @@ private static string RestoreAndBuild(string root)
});
Debug.Assert(restore != null, nameof(restore) + " != null");
restore.WaitForExit();
+
+ _logger.LogInformation("Running dotnet build in {Path}", root);
var build = Process.Start(new ProcessStartInfo
{
FileName = dotnet,
diff --git a/Microsoft.Web.Administration/ConfigurationAttribute.cs b/Microsoft.Web.Administration/ConfigurationAttribute.cs
index 855e3775..8c69542d 100644
--- a/Microsoft.Web.Administration/ConfigurationAttribute.cs
+++ b/Microsoft.Web.Administration/ConfigurationAttribute.cs
@@ -3,15 +3,19 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
+using Microsoft.Extensions.Logging;
+using JexusManager;
using System.Diagnostics;
-using System.Security.Cryptography;
using System.Runtime.InteropServices;
+using System.Security.Cryptography;
namespace Microsoft.Web.Administration
{
[DebuggerDisplay("{Name}")]
- public class ConfigurationAttribute
+ public sealed class ConfigurationAttribute
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("ConfigurationAttribute");
+
private object _value;
private readonly ConfigurationElement _element;
@@ -240,26 +244,10 @@ private string Encrypt(string value)
}
catch (Exception ex)
{
- // If encryption fails with the selected provider, try a fallback approach
- System.Diagnostics.Debug.WriteLine($"Error encrypting with {selectedProvider}: {ex.Message}");
-
- // Create a temporary RSA provider and encrypt with it
- try
- {
- using var rsa = new RSACryptoServiceProvider(2048);
- byte[] dataBytes = System.Text.Encoding.Unicode.GetBytes(value);
- byte[] encryptedBytes = rsa.Encrypt(dataBytes, false);
- string encryptedValue = Convert.ToBase64String(encryptedBytes);
-
- // Just return the encrypted value without the [enc:] wrapper
- // This will treat it as cleartext later but at least it's obscured
- return value;
- }
- catch
- {
- // If all encryption attempts fail, return the original value
- return value;
- }
+ _logger.LogError(ex, "Error encrypting with provider {Provider}", selectedProvider);
+
+ // If all encryption attempts fail, return the original value
+ return value;
}
}
}
diff --git a/Microsoft.Web.Administration/ConfigurationAttributeSchema.cs b/Microsoft.Web.Administration/ConfigurationAttributeSchema.cs
index 3c47e4d7..528d08f9 100644
--- a/Microsoft.Web.Administration/ConfigurationAttributeSchema.cs
+++ b/Microsoft.Web.Administration/ConfigurationAttributeSchema.cs
@@ -4,17 +4,21 @@
using System;
using System.Xml.Linq;
+using Microsoft.Extensions.Logging;
+using JexusManager;
using System.Diagnostics;
-using System.Globalization;
-using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
+using System.Reflection;
+using System.Globalization;
namespace Microsoft.Web.Administration
{
[DebuggerDisplay("{Name}[{Type}]")]
public sealed class ConfigurationAttributeSchema
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("ConfigurationAttributeSchema");
+
private ConfigurationEnumValueCollection _collection;
private ConfigurationValidatorBase _validator;
@@ -220,7 +224,7 @@ public void CreateValidator()
}
catch (MissingMethodException)
{
- Debug.WriteLine($"type: {ValidationType}; parameter: {ValidationParameter}; is null ? {ValidationParameter == null}");
+ _logger.LogDebug($"type: {ValidationType}; parameter: {ValidationParameter}; is null ? {ValidationParameter == null}");
throw;
}
}
diff --git a/Microsoft.Web.Administration/FileContext.cs b/Microsoft.Web.Administration/FileContext.cs
index be1cea08..8ccaba8e 100644
--- a/Microsoft.Web.Administration/FileContext.cs
+++ b/Microsoft.Web.Administration/FileContext.cs
@@ -3,20 +3,23 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
using System.Xml;
using System.Xml.Linq;
+using Microsoft.Extensions.Logging;
+using JexusManager;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Linq;
using System.Xml.XPath;
+using System.Diagnostics;
namespace Microsoft.Web.Administration
{
- [DebuggerDisplay("{FileName}[{Location}]")]
internal sealed class FileContext : IEquatable
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("FileContext");
+
private readonly ServerManager _server;
private readonly object _locker = new object();
private readonly bool _doNotThrow;
@@ -135,51 +138,51 @@ public void Save()
lock (_locker)
{
- if (AppHost)
+ try
{
- // TODO: load settings from applicationHost.config.
- var historyFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Jexus Manager",
- "history");
- int last = 0;
- if (!Directory.Exists(historyFolder))
- {
- Directory.CreateDirectory(historyFolder);
- }
- else
+ if (AppHost)
{
- var existing = new DirectoryInfo(historyFolder).GetDirectories();
- last =
- existing.Select(found => found.Name.Substring("CFGHISTORY_".Length))
- .Select(int.Parse)
- .Concat(new[] { last })
- .Max();
- }
+ // TODO: load settings from applicationHost.config.
+ var historyFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Jexus Manager",
+ "history");
+ int last = 0;
+ if (!Directory.Exists(historyFolder))
+ {
+ Directory.CreateDirectory(historyFolder);
+ }
+ else
+ {
+ var existing = new DirectoryInfo(historyFolder).GetDirectories();
+ last =
+ existing.Select(found => found.Name.Substring("CFGHISTORY_".Length))
+ .Select(int.Parse)
+ .Concat(new[] { last })
+ .Max();
+ }
- last++;
- var revisionFolder = Path.Combine(historyFolder, "CFGHISTORY_" + last.ToString("D10"));
- if (!Directory.Exists(revisionFolder))
- {
- Directory.CreateDirectory(revisionFolder);
- }
+ last++;
+ var revisionFolder = Path.Combine(historyFolder, "CFGHISTORY_" + last.ToString("D10"));
+ if (!Directory.Exists(revisionFolder))
+ {
+ Directory.CreateDirectory(revisionFolder);
+ }
- var fileName = Path.GetFileName(FileName);
- if (fileName != null)
- {
- File.Copy(FileName, Path.Combine(revisionFolder, fileName));
+ var fileName = Path.GetFileName(FileName);
+ if (fileName != null)
+ {
+ File.Copy(FileName, Path.Combine(revisionFolder, fileName));
+ }
}
- }
- if (_document != null)
- {
- try
+ if (_document != null)
{
using var stream = File.Open(FileName, FileMode.Create, FileAccess.Write, FileShare.None);
_document.Save(stream);
}
- catch (SystemException ex)
- {
- Debug.WriteLine(ex);
- }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error saving file {FileName}", FileName);
}
}
}
@@ -371,7 +374,7 @@ private void LoadSchema(XDocument document, string fileName)
if (found == null)
{
found = new SectionSchema(name, element, fileName);
- Debug.Assert(name != null, nameof(name) + " != null");
+ System.Diagnostics.Debug.Assert(name != null, nameof(name) + " != null");
_sectionSchemas.Add(name, found);
}
diff --git a/Microsoft.Web.Administration/IisExpressServerManager.cs b/Microsoft.Web.Administration/IisExpressServerManager.cs
index 41809503..fd25c2ee 100644
--- a/Microsoft.Web.Administration/IisExpressServerManager.cs
+++ b/Microsoft.Web.Administration/IisExpressServerManager.cs
@@ -9,12 +9,16 @@
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
+using JexusManager;
+using Microsoft.Extensions.Logging;
using Exception = System.Exception;
namespace Microsoft.Web.Administration
{
public sealed class IisExpressServerManager : ServerManager
{
+ private static readonly ILogger _logger = LogHelper.GetLogger("IisExpressServerManager");
+
public Version Version { get; }
public string PrimaryExecutable { get; }
@@ -87,12 +91,12 @@ internal override bool GetSiteState(Site site)
StartInfo = new ProcessStartInfo
{
Verb = site.Bindings.ElevationRequired && !PublicNativeMethods.IsProcessElevated
- ? "runas"
- : null,
+ ? "runas"
+ : null,
UseShellExecute = true,
FileName = "cmd",
Arguments =
- $"/c \"\"{CertificateInstallerLocator.FileName}\" /config:\"{site.FileContext.FileName}\" /siteId:{site.Id}\"",
+ $"/c \"\"{CertificateInstallerLocator.FileName}\" /config:\"{site.FileContext.FileName}\" /siteId:{site.Id}\"",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
@@ -107,22 +111,19 @@ internal override bool GetSiteState(Site site)
// elevation is cancelled.
if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
+ _logger.LogWarning(ex, "Win32 error getting site state. Native error code: {Code}", ex.NativeErrorCode);
}
}
catch (InvalidOperationException ex)
{
if (ex.HResult != NativeMethods.NoProcessAssociated)
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"hresult {ex.HResult}");
+ _logger.LogError(ex, "Error getting site state. HResult: {HResult}", ex.HResult);
}
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Unexpected error getting site state");
}
return true;
@@ -236,14 +237,12 @@ internal override void Stop(Site site)
// elevation is cancelled.
if (ex.NativeErrorCode != (int)Windows.Win32.Foundation.WIN32_ERROR.ERROR_CANCELLED)
{
- Debug.WriteLine(ex);
- Debug.WriteLine($"native {ex.NativeErrorCode}");
- // throw;
+ _logger.LogError(ex, "Win32 error stopping site. Native error code: {Code}", ex.NativeErrorCode);
}
}
catch (Exception ex)
{
- Debug.WriteLine(ex);
+ _logger.LogError(ex, "Error stopping site", ex);
}
}
diff --git a/Microsoft.Web.Configuration.AppHostFileProvider/AesEncryptionProvider.cs b/Microsoft.Web.Configuration.AppHostFileProvider/AesEncryptionProvider.cs
index d2be43e8..c3eb547e 100644
--- a/Microsoft.Web.Configuration.AppHostFileProvider/AesEncryptionProvider.cs
+++ b/Microsoft.Web.Configuration.AppHostFileProvider/AesEncryptionProvider.cs
@@ -60,12 +60,10 @@ public unsafe static string AesDecrypt(byte[] encrypted, string keyContainer, st
dwFlags = CRYPT_KEY_FLAGS.CRYPT_OAEP;
}
- fixed (byte* sessionPtr = sessionKey)
+ ReadOnlySpan sessionPtr = sessionKey;
+ if (!Windows.Win32.PInvoke.CryptImportKey(handle, sessionPtr, hKey, dwFlags, out hEncryptKey))
{
- if (!Windows.Win32.PInvoke.CryptImportKey(handle, sessionPtr, (uint)sessionKey.Length, hKey, dwFlags, out hEncryptKey))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
+ throw new Win32Exception(Marshal.GetLastWin32Error());
}
byte[] array = new byte[encrypted.Length];
@@ -122,36 +120,30 @@ public unsafe static byte[] AesEncrypt(string data, string keyContainer, string
dwFlags = CRYPT_KEY_FLAGS.CRYPT_OAEP;
}
- fixed (byte* sessionPtr = sessionKey)
+ ReadOnlySpan sessionPtr = sessionKey;
+ if (!Windows.Win32.PInvoke.CryptImportKey(handle, sessionPtr, hKey, dwFlags, out hEncryptKey))
{
- if (!Windows.Win32.PInvoke.CryptImportKey(handle, sessionPtr, (uint)sessionKey.Length, hKey, dwFlags, out hEncryptKey))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
+ throw new Win32Exception(Marshal.GetLastWin32Error());
}
uint pdwDataLen = (uint)array.Length;
- fixed (byte* arrayPtr = array)
+ ReadOnlySpan arrayPtr = array;
+ if (!Windows.Win32.PInvoke.CryptEncrypt(hEncryptKey, 0, Final: true, 0u, arrayPtr, ref pdwDataLen))
{
- if (!Windows.Win32.PInvoke.CryptEncrypt(hEncryptKey, 0, Final: true, 0u, arrayPtr, ref pdwDataLen, 0))
+ int lastWin32Error = Marshal.GetLastWin32Error();
+ if (lastWin32Error != 234)
{
- int lastWin32Error = Marshal.GetLastWin32Error();
- if (lastWin32Error != 234)
- {
- throw new Win32Exception(lastWin32Error);
- }
+ throw new Win32Exception(lastWin32Error);
}
}
byte[] array2 = new byte[pdwDataLen];
array.CopyTo(array2, 0);
pdwDataLen = (uint)array.Length;
- fixed (byte* array2Ptr = array2)
+ ReadOnlySpan array2Ptr = array2;
+ if (!Windows.Win32.PInvoke.CryptEncrypt(hEncryptKey, 0, Final: true, 0u, array2Ptr, ref pdwDataLen))
{
- if (!Windows.Win32.PInvoke.CryptEncrypt(hEncryptKey, 0, Final: true, 0u, array2Ptr, ref pdwDataLen, (uint)array2.Length))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
+ throw new Win32Exception(Marshal.GetLastWin32Error());
}
return array2;
diff --git a/Microsoft.Web.Configuration.AppHostFileProvider/LoggingService.cs b/Microsoft.Web.Configuration.AppHostFileProvider/LoggingService.cs
new file mode 100644
index 00000000..8878d334
--- /dev/null
+++ b/Microsoft.Web.Configuration.AppHostFileProvider/LoggingService.cs
@@ -0,0 +1,35 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace JexusManager
+{
+ public static class LogHelper
+ {
+ private static ILoggerFactory _loggerFactory;
+
+ public static void Initialize(ILoggerFactory loggerFactory)
+ {
+ _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
+ }
+
+ public static ILogger GetLogger()
+ {
+ if (_loggerFactory == null)
+ {
+ throw new InvalidOperationException("LoggerFactory has not been initialized.");
+ }
+
+ return _loggerFactory.CreateLogger();
+ }
+
+ public static ILogger GetLogger(string categoryName)
+ {
+ if (_loggerFactory == null)
+ {
+ throw new InvalidOperationException("LoggerFactory has not been initialized.");
+ }
+
+ return _loggerFactory.CreateLogger(categoryName);
+ }
+ }
+}
diff --git a/Microsoft.Web.Configuration.AppHostFileProvider/Microsoft.Web.Configuration.AppHostFileProvider.csproj b/Microsoft.Web.Configuration.AppHostFileProvider/Microsoft.Web.Configuration.AppHostFileProvider.csproj
index 99824381..951fc404 100644
--- a/Microsoft.Web.Configuration.AppHostFileProvider/Microsoft.Web.Configuration.AppHostFileProvider.csproj
+++ b/Microsoft.Web.Configuration.AppHostFileProvider/Microsoft.Web.Configuration.AppHostFileProvider.csproj
@@ -12,6 +12,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.cs b/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.cs
index e21ead44..d6bd8ae5 100644
--- a/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.cs
+++ b/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.cs
@@ -88,6 +88,7 @@ public static string CngDecrypt(byte[] encrypted, string keyContainer)
uint pcbData = (uint)encrypted.Length;
byte[] array = new byte[pcbData];
encrypted.CopyTo(array, 0);
+ if (IisCngDecrypt(keyContainer, array, ref pcbData) != 0L)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
diff --git a/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.json b/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.json
index ba907b86..66fff887 100644
--- a/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.json
+++ b/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.json
@@ -1,5 +1,8 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": false,
- "emitSingleFile": true
+ "emitSingleFile": true,
+ "useSafeHandles": true,
+ "allowMarshaling": true,
+ "multiTargetingFriendlyAPIs": true
}
diff --git a/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.txt b/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.txt
index e2151334..8465e079 100644
--- a/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.txt
+++ b/Microsoft.Web.Configuration.AppHostFileProvider/NativeMethods.txt
@@ -7,8 +7,8 @@ CryptImportKey
CryptGetUserKey
CryptGenRandom
PROV_RSA_AES
-AT_KEYEXCHANGE
-ERROR_MORE_DATA
-NTE_EXISTS
+CERT_KEY_SPEC
+WIN32_ERROR
+CRYPT_KEY_FLAGS
CRYPT_NEWKEYSET
-CRYPT_MACHINE_KEYSET
+NTE_EXISTS