From 7a4b272c564b60baa29d6a87967a4f2bf3094891 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:04:57 +0800 Subject: [PATCH 01/13] Embed ServicePulse --- src/Directory.Packages.props | 1 + src/ServiceControl/Hosting/Commands/RunCommand.cs | 5 +++++ src/ServiceControl/ServiceControl.csproj | 1 + 3 files changed, 7 insertions(+) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index e81765563d..0bde5ccbd1 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -75,6 +75,7 @@ + diff --git a/src/ServiceControl/Hosting/Commands/RunCommand.cs b/src/ServiceControl/Hosting/Commands/RunCommand.cs index ac5cd439b4..877522ccaa 100644 --- a/src/ServiceControl/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl/Hosting/Commands/RunCommand.cs @@ -10,6 +10,7 @@ using ServiceControl; using ServiceControl.Hosting.Auth; using ServiceControl.Hosting.Https; + using ServicePulse; class RunCommand : AbstractCommand { @@ -30,6 +31,10 @@ public override async Task Execute(HostArguments args, Settings settings) var app = hostBuilder.Build(); app.UseServiceControl(settings.ForwardedHeadersSettings, settings.HttpsSettings); + app.UseServicePulse(ServicePulseSettings.GetFromEnvironmentVariables() with + { + ServiceControlUrl = settings.ApiUrl + }); app.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled); await app.RunAsync(settings.RootUrl); diff --git a/src/ServiceControl/ServiceControl.csproj b/src/ServiceControl/ServiceControl.csproj index 41f3c49bda..047ceabf67 100644 --- a/src/ServiceControl/ServiceControl.csproj +++ b/src/ServiceControl/ServiceControl.csproj @@ -37,6 +37,7 @@ + From 5d4fefbaa0f6ea566eb9e4e37a77e0a2dfa7dbc6 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:08:30 +0800 Subject: [PATCH 02/13] Add setting to enable/disable embedded ServicePulse --- src/ServiceControl/Hosting/Commands/RunCommand.cs | 9 ++++++--- src/ServiceControl/Infrastructure/Settings/Settings.cs | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ServiceControl/Hosting/Commands/RunCommand.cs b/src/ServiceControl/Hosting/Commands/RunCommand.cs index 877522ccaa..a670cbc08a 100644 --- a/src/ServiceControl/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl/Hosting/Commands/RunCommand.cs @@ -31,10 +31,13 @@ public override async Task Execute(HostArguments args, Settings settings) var app = hostBuilder.Build(); app.UseServiceControl(settings.ForwardedHeadersSettings, settings.HttpsSettings); - app.UseServicePulse(ServicePulseSettings.GetFromEnvironmentVariables() with + if (settings.EnableEmbeddedServicePulse) { - ServiceControlUrl = settings.ApiUrl - }); + app.UseServicePulse(ServicePulseSettings.GetFromEnvironmentVariables() with + { + ServiceControlUrl = settings.ApiUrl + }); + } app.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled); await app.RunAsync(settings.RootUrl); diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index f477ff4b22..0d8fc3acbf 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -65,6 +65,7 @@ public Settings( MaximumConcurrencyLevel = SettingsReader.Read(SettingsRootNamespace, "MaximumConcurrencyLevel"); RetryHistoryDepth = SettingsReader.Read(SettingsRootNamespace, "RetryHistoryDepth", 10); AllowMessageEditing = SettingsReader.Read(SettingsRootNamespace, "AllowMessageEditing"); + EnableEmbeddedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableEmbeddedServicePulse", true); NotificationsFilter = SettingsReader.Read(SettingsRootNamespace, "NotificationsFilter"); RemoteInstances = GetRemoteInstances().ToArray(); TimeToRestartErrorIngestionAfterFailure = GetTimeToRestartErrorIngestionAfterFailure(); @@ -103,6 +104,8 @@ public Settings( public bool AllowMessageEditing { get; set; } + public bool EnableEmbeddedServicePulse { get; set; } + //HINT: acceptance tests only public Func MessageFilter { get; set; } From befc3c63b7c582ea921902a9c74f5277d06ad4ca Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:17:56 +0800 Subject: [PATCH 03/13] Move settings --- src/ServiceControl/Hosting/Commands/RunCommand.cs | 6 +----- src/ServiceControl/Infrastructure/Settings/Settings.cs | 6 ++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ServiceControl/Hosting/Commands/RunCommand.cs b/src/ServiceControl/Hosting/Commands/RunCommand.cs index a670cbc08a..c58e456779 100644 --- a/src/ServiceControl/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl/Hosting/Commands/RunCommand.cs @@ -10,7 +10,6 @@ using ServiceControl; using ServiceControl.Hosting.Auth; using ServiceControl.Hosting.Https; - using ServicePulse; class RunCommand : AbstractCommand { @@ -33,10 +32,7 @@ public override async Task Execute(HostArguments args, Settings settings) app.UseServiceControl(settings.ForwardedHeadersSettings, settings.HttpsSettings); if (settings.EnableEmbeddedServicePulse) { - app.UseServicePulse(ServicePulseSettings.GetFromEnvironmentVariables() with - { - ServiceControlUrl = settings.ApiUrl - }); + app.UseServicePulse(settings.ServicePulseSettings); } app.UseServiceControlAuthentication(settings.OpenIdConnectSettings.Enabled); diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 0d8fc3acbf..d340ac8b8c 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -15,6 +15,7 @@ namespace ServiceBus.Management.Infrastructure.Settings using ServiceControl.Infrastructure.WebApi; using ServiceControl.Persistence; using ServiceControl.Transports; + using ServicePulse; using JsonSerializer = System.Text.Json.JsonSerializer; public class Settings @@ -66,6 +67,10 @@ public Settings( RetryHistoryDepth = SettingsReader.Read(SettingsRootNamespace, "RetryHistoryDepth", 10); AllowMessageEditing = SettingsReader.Read(SettingsRootNamespace, "AllowMessageEditing"); EnableEmbeddedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableEmbeddedServicePulse", true); + ServicePulseSettings = ServicePulseSettings.GetFromEnvironmentVariables() with + { + ServiceControlUrl = ApiUrl + }; NotificationsFilter = SettingsReader.Read(SettingsRootNamespace, "NotificationsFilter"); RemoteInstances = GetRemoteInstances().ToArray(); TimeToRestartErrorIngestionAfterFailure = GetTimeToRestartErrorIngestionAfterFailure(); @@ -105,6 +110,7 @@ public Settings( public bool AllowMessageEditing { get; set; } public bool EnableEmbeddedServicePulse { get; set; } + public ServicePulseSettings ServicePulseSettings { get; set; } //HINT: acceptance tests only public Func MessageFilter { get; set; } From 4fd0c680896d1190cbff81bf406a74aef944f478 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:30:07 +0800 Subject: [PATCH 04/13] Tell ServicePulse it is running in embedded mode --- src/Directory.Packages.props | 2 +- src/ServiceControl/Infrastructure/Settings/Settings.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 0bde5ccbd1..f2d7f038a0 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -75,7 +75,7 @@ - + diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index d340ac8b8c..2cdc96136e 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -69,7 +69,8 @@ public Settings( EnableEmbeddedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableEmbeddedServicePulse", true); ServicePulseSettings = ServicePulseSettings.GetFromEnvironmentVariables() with { - ServiceControlUrl = ApiUrl + ServiceControlUrl = ApiUrl, + IsEmbedded = true }; NotificationsFilter = SettingsReader.Read(SettingsRootNamespace, "NotificationsFilter"); RemoteInstances = GetRemoteInstances().ToArray(); From 25761f226e36b20f9853ddeb8314b74a81edc688 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 20 Jan 2026 12:45:02 +0800 Subject: [PATCH 05/13] Show SP url in SCMU and default to enabled --- src/Directory.Packages.props | 2 +- .../UI/InstanceAdd/ServiceControlAddAttachment.cs | 2 ++ .../Infrastructure/Settings/Settings.cs | 2 +- .../ServiceControl/ServiceControlAppConfig.cs | 1 + .../Configuration/ServiceControl/SettingsList.cs | 6 ++++++ .../Instances/ServiceControlBaseService.cs | 11 +++++++---- .../Instances/ServiceControlInstallableBase.cs | 2 ++ .../Instances/ServiceControlInstance.cs | 3 +++ src/ServiceControlInstaller.Engine/Interfaces.cs | 1 + 9 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index f2d7f038a0..b42557e23e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -75,7 +75,7 @@ - + diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs index 5e8eb5d97a..ef701cccd2 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs @@ -76,6 +76,8 @@ async Task Add() serviceControlNewInstance.ServiceAccount = viewModel.ServiceControl.ServiceAccount; serviceControlNewInstance.ServiceAccountPwd = viewModel.ServiceControl.Password; serviceControlNewInstance.EnableFullTextSearchOnBodies = viewModel.ServiceControl.EnableFullTextSearchOnBodies.Value; + // TODO: Make this configurable + serviceControlNewInstance.EnableEmbeddedServicePulse = true; } var auditNewInstance = viewModel.InstallAuditInstance ? ServiceControlAuditNewInstance.CreateWithDefaultPersistence() : null; diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 2cdc96136e..3bee8aedec 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -66,7 +66,7 @@ public Settings( MaximumConcurrencyLevel = SettingsReader.Read(SettingsRootNamespace, "MaximumConcurrencyLevel"); RetryHistoryDepth = SettingsReader.Read(SettingsRootNamespace, "RetryHistoryDepth", 10); AllowMessageEditing = SettingsReader.Read(SettingsRootNamespace, "AllowMessageEditing"); - EnableEmbeddedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableEmbeddedServicePulse", true); + EnableEmbeddedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableEmbeddedServicePulse", false); ServicePulseSettings = ServicePulseSettings.GetFromEnvironmentVariables() with { ServiceControlUrl = ApiUrl, diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs index c3c79743bf..d3f537d149 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/ServiceControlAppConfig.cs @@ -38,6 +38,7 @@ protected override void UpdateSettings() settings.Set(ServiceControlSettings.ErrorRetentionPeriod, details.ErrorRetentionPeriod.ToString(), version); settings.Set(ServiceControlSettings.EnableFullTextSearchOnBodies, details.EnableFullTextSearchOnBodies.ToString(), version); settings.Set(ServiceControlSettings.RemoteInstances, RemoteInstanceConverter.ToJson(details.RemoteInstances), version); + settings.Set(ServiceControlSettings.EnableEmbeddedServicePulse, details.EnableEmbeddedServicePulse.ToString(), version); // Windows services allow a maximum of 125 seconds when stopping a service. // When shutting down or restarting the OS we have no control over the diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs index 9dc2166000..1ff4425a51 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs @@ -113,5 +113,11 @@ public static class ServiceControlSettings Name = "LicensingComponent/RabbitMQ/Password", RemovedFrom = new SemanticVersion(6, 5, 0) }; + + public static readonly SettingInfo EnableEmbeddedServicePulse = new() + { + Name = "ServiceControl/EnableEmbeddedServicePulse", + SupportedFrom = new SemanticVersion(6, 9, 0) + }; } } diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs index 5e2a3a762f..808c06ec9a 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs @@ -85,6 +85,7 @@ public string AclMaintenanceUrl public TimeSpan ErrorRetentionPeriod { get; set; } public bool SkipQueueCreation { get; set; } public bool EnableFullTextSearchOnBodies { get; set; } + public bool EnableEmbeddedServicePulse { get; set; } protected abstract string BaseServiceName { get; } @@ -92,12 +93,13 @@ public string Url { get { + var suffix = EnableEmbeddedServicePulse ? "" : "api/"; if (string.IsNullOrWhiteSpace(VirtualDirectory)) { - return $"http://{HostName}:{Port}/api/"; + return $"http://{HostName}:{Port}/{suffix}"; } - return $"http://{HostName}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}api/"; + return $"http://{HostName}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}"; } } @@ -105,6 +107,7 @@ public string BrowsableUrl { get { + var suffix = EnableEmbeddedServicePulse ? "" : "api/"; string host = HostName switch { "*" => "localhost", @@ -113,10 +116,10 @@ public string BrowsableUrl }; if (string.IsNullOrWhiteSpace(VirtualDirectory)) { - return $"http://{host}:{Port}/api/"; + return $"http://{host}:{Port}/{suffix}"; } - return $"http://{host}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}api/"; + return $"http://{host}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}/"; } } diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs index 6f237b1803..14643a84f7 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstallableBase.cs @@ -91,6 +91,8 @@ string[] FlagFiles public bool ForwardErrorMessages { get; set; } + public bool EnableEmbeddedServicePulse { get; set; } + public TransportInfo TransportPackage { get; set; } public string ConnectionString { get; set; } diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs index f500a82545..047a4356b7 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs @@ -135,6 +135,8 @@ public override void Reload() AuditLogQueue = AppConfig.Read(ServiceControlSettings.AuditLogQueue, string.IsNullOrEmpty(AuditQueue) ? null : $"{AuditQueue}.log"); } + EnableEmbeddedServicePulse = AppConfig.Read(ServiceControlSettings.EnableEmbeddedServicePulse, false); + if (TimeSpan.TryParse(AppConfig.Read(ServiceControlSettings.ErrorRetentionPeriod, (string)null), out var errorRetentionPeriod)) { ErrorRetentionPeriod = errorRetentionPeriod; @@ -181,6 +183,7 @@ protected override void ApplySettingsChanges(KeyValueConfigurationCollection set settings.Set(ServiceControlSettings.ErrorLogQueue, ErrorLogQueue, Version); settings.Set(ServiceControlSettings.EnableFullTextSearchOnBodies, EnableFullTextSearchOnBodies.ToString(), Version); settings.Set(ServiceControlSettings.PersistenceType, PersistenceManifest.Name); + settings.Set(ServiceControlSettings.EnableEmbeddedServicePulse, EnableEmbeddedServicePulse.ToString(), Version); if (RemoteInstances != null) { diff --git a/src/ServiceControlInstaller.Engine/Interfaces.cs b/src/ServiceControlInstaller.Engine/Interfaces.cs index 914be2cc43..0f8200fefc 100644 --- a/src/ServiceControlInstaller.Engine/Interfaces.cs +++ b/src/ServiceControlInstaller.Engine/Interfaces.cs @@ -109,6 +109,7 @@ public interface IServiceControlInstance : IServiceControlBaseInstance, IURLInfo string ErrorLogQueue { get; } string VirtualDirectory { get; } bool ForwardErrorMessages { get; } + bool EnableEmbeddedServicePulse { get; } TimeSpan ErrorRetentionPeriod { get; } TimeSpan? AuditRetentionPeriod { get; set; } List RemoteInstances { get; } From 13b6001f171173cb8bde0952c5b87fe21447bdcb Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 20 Jan 2026 13:05:39 +0800 Subject: [PATCH 06/13] Allow disabling embedded ServicePulse at creation --- .../EnableEmbeddedServicePulseOption.cs | 8 ++++++++ .../InstanceAdd/ServiceControlAddAttachment.cs | 3 +-- .../UI/InstanceAdd/ServiceControlAddView.xaml | 6 ++++++ .../InstanceAdd/ServiceControlAddViewModel.cs | 9 +++++++++ .../InstanceAdd/ServiceControlInformation.cs | 18 ++++++++++++++++++ .../Validation/QueueValidationTests.cs | 2 ++ 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/ServiceControl.Config/UI/InstanceAdd/EnableEmbeddedServicePulseOption.cs diff --git a/src/ServiceControl.Config/UI/InstanceAdd/EnableEmbeddedServicePulseOption.cs b/src/ServiceControl.Config/UI/InstanceAdd/EnableEmbeddedServicePulseOption.cs new file mode 100644 index 0000000000..77bbd17fca --- /dev/null +++ b/src/ServiceControl.Config/UI/InstanceAdd/EnableEmbeddedServicePulseOption.cs @@ -0,0 +1,8 @@ +namespace ServiceControl.Config.UI.InstanceAdd +{ + public class EnableEmbeddedServicePulseOption + { + public string Name { get; set; } + public bool Value { get; set; } + } +} \ No newline at end of file diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs index ef701cccd2..0b104c8d20 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddAttachment.cs @@ -76,8 +76,7 @@ async Task Add() serviceControlNewInstance.ServiceAccount = viewModel.ServiceControl.ServiceAccount; serviceControlNewInstance.ServiceAccountPwd = viewModel.ServiceControl.Password; serviceControlNewInstance.EnableFullTextSearchOnBodies = viewModel.ServiceControl.EnableFullTextSearchOnBodies.Value; - // TODO: Make this configurable - serviceControlNewInstance.EnableEmbeddedServicePulse = true; + serviceControlNewInstance.EnableEmbeddedServicePulse = viewModel.ServiceControl.EnableEmbeddedServicePulse.Value; } var auditNewInstance = viewModel.InstallAuditInstance ? ServiceControlAuditNewInstance.CreateWithDefaultPersistence() : null; diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml index 4b09b4623c..e5258f6272 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddView.xaml @@ -254,6 +254,12 @@ Header="FULL TEXT SEARCH ON MESSAGE BODIES" ItemsSource="{Binding ErrorEnableFullTextSearchOnBodiesOptions}" SelectedValue="{Binding ErrorEnableFullTextSearchOnBodies}" /> + diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs index f514290399..6c263dc3aa 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs @@ -220,6 +220,15 @@ public EnableFullTextSearchOnBodiesOption ErrorEnableFullTextSearchOnBodies set => ServiceControl.EnableFullTextSearchOnBodies = value; } + public IEnumerable ErrorEnableEmbeddedServicePulseOptions => + ServiceControl.EnableEmbeddedServicePulseOptions; + + public EnableEmbeddedServicePulseOption ErrorEnableEmbeddedServicePulse + { + get => ServiceControl.EnableEmbeddedServicePulse; + set => ServiceControl.EnableEmbeddedServicePulse = value; + } + /* Add Audit Instance */ public string AuditInstanceName diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs index 843dc4bbd6..b674e96c2a 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs @@ -38,6 +38,19 @@ public ServiceControlInformation(ServiceControlEditorViewModel viewModelParent) Value = false } }; + EnableEmbeddedServicePulseOptions = new[] + { + new EnableEmbeddedServicePulseOption + { + Name = "On", + Value = true + }, + new EnableEmbeddedServicePulseOption + { + Name = "Off", + Value = false + } + }; ErrorRetention = SettingConstants.ErrorRetentionPeriodDefaultInDaysForUI; Description = "ServiceControl Service"; HostName = "localhost"; @@ -48,6 +61,7 @@ public ServiceControlInformation(ServiceControlEditorViewModel viewModelParent) PortNumber = "33333"; DatabaseMaintenancePortNumber = "33334"; EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodiesOptions.First(p => p.Value); //Default to On. + EnableEmbeddedServicePulse = EnableEmbeddedServicePulseOptions.First(p => p.Value); //Default to On. ViewModelParent = viewModelParent; } @@ -92,6 +106,10 @@ public ForwardingOption ErrorForwarding public EnableFullTextSearchOnBodiesOption EnableFullTextSearchOnBodies { get; set; } + public IEnumerable EnableEmbeddedServicePulseOptions { get; } + + public EnableEmbeddedServicePulseOption EnableEmbeddedServicePulse { get; set; } + protected void UpdateErrorRetention(TimeSpan value) { ErrorRetention = ErrorRetentionUnits == TimeSpanUnits.Days ? value.TotalDays : value.TotalHours; diff --git a/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs b/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs index 578ef22468..5307a02856 100644 --- a/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs +++ b/src/ServiceControlInstaller.Engine.UnitTests/Validation/QueueValidationTests.cs @@ -21,6 +21,8 @@ class FakeServiceControlInstance : IServiceControlInstance public bool ForwardErrorMessages { get; set; } + public bool EnableEmbeddedServicePulse { get; set; } + public TimeSpan ErrorRetentionPeriod { get; set; } public TimeSpan? AuditRetentionPeriod { get; set; } From f9ab202cb3fc0c13bf7df13f715b8be6954a3d4d Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 20 Jan 2026 13:49:54 +0800 Subject: [PATCH 07/13] Enable updating a running instance --- .../UI/InstanceAdd/ServiceControlAddViewModel.cs | 2 +- .../UI/InstanceAdd/ServiceControlInformation.cs | 1 + .../UI/InstanceEdit/ServiceControlEditAttachment.cs | 1 + .../UI/InstanceEdit/ServiceControlEditView.xaml | 6 ++++++ .../UI/InstanceEdit/ServiceControlEditViewModel.cs | 10 ++++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs index 6c263dc3aa..24fda9a51a 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlAddViewModel.cs @@ -222,7 +222,7 @@ public EnableFullTextSearchOnBodiesOption ErrorEnableFullTextSearchOnBodies public IEnumerable ErrorEnableEmbeddedServicePulseOptions => ServiceControl.EnableEmbeddedServicePulseOptions; - + public EnableEmbeddedServicePulseOption ErrorEnableEmbeddedServicePulse { get => ServiceControl.EnableEmbeddedServicePulse; diff --git a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs index b674e96c2a..b21adaa93f 100644 --- a/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs +++ b/src/ServiceControl.Config/UI/InstanceAdd/ServiceControlInformation.cs @@ -140,6 +140,7 @@ public void UpdateFromInstance(ServiceControlInstance instance) ErrorForwardingQueueName = instance.ErrorLogQueue; UpdateErrorRetention(instance.ErrorRetentionPeriod); EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodiesOptions.FirstOrDefault(p => p.Value == instance.EnableFullTextSearchOnBodies); + EnableEmbeddedServicePulse = EnableEmbeddedServicePulseOptions.FirstOrDefault(p => p.Value == instance.EnableEmbeddedServicePulse); } ForwardingOption errorForwarding; diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs index 86ccfe100a..04944dc748 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditAttachment.cs @@ -70,6 +70,7 @@ async Task Save() instance.DatabaseMaintenancePort = !string.IsNullOrWhiteSpace(viewModel.ServiceControl.DatabaseMaintenancePortNumber) ? Convert.ToInt32(viewModel.ServiceControl.DatabaseMaintenancePortNumber) : null; instance.VirtualDirectory = null; instance.ForwardErrorMessages = viewModel.ServiceControl.ErrorForwarding.Value; + instance.EnableEmbeddedServicePulse = viewModel.ServiceControl.EnableEmbeddedServicePulse.Value; instance.ErrorQueue = viewModel.ServiceControl.ErrorQueueName; instance.ErrorLogQueue = viewModel.ServiceControl.ErrorForwardingQueueName; instance.ErrorRetentionPeriod = viewModel.ServiceControl.ErrorRetentionPeriod; diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml index a81b7420e9..35e310c88a 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditView.xaml @@ -231,6 +231,12 @@ Header="FULL TEXT SEARCH ON MESSAGE BODIES" ItemsSource="{Binding EnableFullTextSearchOnBodiesOptions}" SelectedValue="{Binding EnableFullTextSearchOnBodies}" /> + diff --git a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs index 8ca48e5864..7510173620 100644 --- a/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs +++ b/src/ServiceControl.Config/UI/InstanceEdit/ServiceControlEditViewModel.cs @@ -42,6 +42,7 @@ public void UpdateInstanceFromViewModel(ServiceControlInstance instance) instance.ConnectionString = ConnectionString; instance.DatabaseMaintenancePort = Convert.ToInt32(ServiceControl.DatabaseMaintenancePortNumber); instance.EnableFullTextSearchOnBodies = ServiceControl.EnableFullTextSearchOnBodies.Value; + instance.EnableEmbeddedServicePulse = ServiceControl.EnableEmbeddedServicePulse.Value; } public string InstanceName => ServiceControl.InstanceName; @@ -189,6 +190,15 @@ public EnableFullTextSearchOnBodiesOption EnableFullTextSearchOnBodies set => ServiceControl.EnableFullTextSearchOnBodies = value; } + public IEnumerable EnableEmbeddedServicePulseOptions => + ServiceControl.EnableEmbeddedServicePulseOptions; + + public EnableEmbeddedServicePulseOption EnableEmbeddedServicePulse + { + get => ServiceControl.EnableEmbeddedServicePulse; + set => ServiceControl.EnableEmbeddedServicePulse = value; + } + public bool SubmitAttempted { get; set; } } } \ No newline at end of file From 16f9feb4f8ea165cdd1b8c5366a2fc5b2d868cbc Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 21 Jan 2026 17:20:22 +0800 Subject: [PATCH 08/13] Show ServicePulse heading if using embedded --- .../UI/InstanceDetails/InstanceDetailsView.xaml | 2 +- .../InstanceDetails/InstanceDetailsViewModel.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml index 46485b62c3..dcddb94c1a 100644 --- a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml +++ b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsView.xaml @@ -137,7 +137,7 @@ diff --git a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs index af944eec87..a98ad0f62b 100644 --- a/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs +++ b/src/ServiceControl.Config/UI/InstanceDetails/InstanceDetailsViewModel.cs @@ -114,6 +114,22 @@ public string BrowsableUrl public bool HasBrowsableUrl => ServiceInstance is IURLInfo; + public string UrlHeading + { + get + { + if (IsServiceControlInstance) + { + if (ServiceControlInstance.EnableEmbeddedServicePulse) + { + return "SERVICEPULSE"; + } + } + + return "URL"; + } + } + public string InstallPath => ((IServicePaths)ServiceInstance).InstallPath; public string DBPath => GetDBPathIfAvailable(); @@ -291,6 +307,7 @@ public Task HandleAsync(PostRefreshInstances message, CancellationToken cancella NotifyOfPropertyChange("HasNewVersion"); NotifyOfPropertyChange("Transport"); NotifyOfPropertyChange("BrowsableUrl"); + NotifyOfPropertyChange("UrlHeading"); return Task.CompletedTask; } From 06bf8cdd3e139443f16d02a8629051238e2e3018 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 21 Jan 2026 17:20:38 +0800 Subject: [PATCH 09/13] Fix ApiUrl --- src/ServiceControl/Infrastructure/Settings/Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 3bee8aedec..c17b2d0175 100644 --- a/src/ServiceControl/Infrastructure/Settings/Settings.cs +++ b/src/ServiceControl/Infrastructure/Settings/Settings.cs @@ -69,7 +69,7 @@ public Settings( EnableEmbeddedServicePulse = SettingsReader.Read(SettingsRootNamespace, "EnableEmbeddedServicePulse", false); ServicePulseSettings = ServicePulseSettings.GetFromEnvironmentVariables() with { - ServiceControlUrl = ApiUrl, + ServiceControlUrl = $"{ApiUrl}/", IsEmbedded = true }; NotificationsFilter = SettingsReader.Read(SettingsRootNamespace, "NotificationsFilter"); From e756a285b3f55fffe9001aa5d51cfc1274d0332a Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 22 Jan 2026 11:08:33 +0800 Subject: [PATCH 10/13] Ask about embedded ServicePulse on upgrade --- .../UpgradeServiceControlInstanceCommand.cs | 18 ++++++++++++++++++ .../Instances/ServiceControlUpgradeOptions.cs | 2 ++ 2 files changed, 20 insertions(+) diff --git a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs index 452e955f6d..fcb07e0817 100644 --- a/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs +++ b/src/ServiceControl.Config/Commands/UpgradeServiceControlInstanceCommand.cs @@ -119,6 +119,24 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model) } } + if (!instance.AppConfig.AppSettingExists(ServiceControlSettings.EnableEmbeddedServicePulse.Name)) + { + var result = await windowManager.ShowYesNoCancelDialog("INPUT REQUIRED - EMBEDDED SERVICEPULSE", + "ServiceControl can host an embedded version of ServicePulse which allows you to monitor your ServiceControl instance without needing to install ServicePulse separately.", + "Would you like to enable the embedded ServicePulse for this instance?", + "Enable Embedded ServicePulse", + "Do NOT enable Embedded ServicePulse"); + + if (!result.HasValue) + { + //Dialog was cancelled + await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances()); + return; + } + + upgradeOptions.EnableEmbeddedServicePulse = result.Value; + } + if (await commandChecks.StopBecauseInstanceIsRunning(instance)) { return; diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs index d670ec50c7..8c27a1a533 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlUpgradeOptions.cs @@ -10,6 +10,7 @@ public class ServiceControlUpgradeOptions public int? MaintenancePort { get; set; } public bool SkipQueueCreation { get; set; } public string RemoteUrl { get; set; } + public bool EnableEmbeddedServicePulse { get; set; } public bool Force { get; set; } public void ApplyChangesToInstance(ServiceControlBaseService instance) @@ -53,6 +54,7 @@ void ApplyChangesTo(ServiceControlInstance instance) } instance.SkipQueueCreation = SkipQueueCreation; + instance.EnableEmbeddedServicePulse = EnableEmbeddedServicePulse; } void ApplyChangesTo(ServiceControlAuditInstance instance) From 476f3755266af6bb62a4840281257ebb836c5395 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 23 Jan 2026 09:55:54 +0800 Subject: [PATCH 11/13] Enable configuration of embedded SP in PWSH --- .../NewServiceControlInstance.cs | 4 ++++ ...Control.Management.PowerShell.dll-help.xml | 22 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs index c7e2d6cfee..632fc48774 100644 --- a/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs +++ b/src/ServiceControl.Management.PowerShell/Cmdlets/ServiceControlInstances/NewServiceControlInstance.cs @@ -94,6 +94,9 @@ public class NewServiceControlInstance : PSCmdlet [Parameter(Mandatory = false, HelpMessage = "Specify whether to enable full text search on error messages.")] public SwitchParameter EnableFullTextSearchOnBodies { get; set; } = true; + [Parameter(Mandatory = false, HelpMessage = "Specify whether to enable embedded ServicePulse instance.")] + public SwitchParameter EnableEmbeddedServicePulse { get; set; } + [Parameter(Mandatory = false, HelpMessage = "Reuse the specified log, db, and install paths even if they are not empty")] public SwitchParameter Force { get; set; } @@ -172,6 +175,7 @@ protected override void ProcessRecord() details.TransportPackage = ServiceControlCoreTransports.Find(Transport); details.SkipQueueCreation = SkipQueueCreation; details.EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodies; + details.EnableEmbeddedServicePulse = EnableEmbeddedServicePulse; var modulePath = Path.GetDirectoryName(MyInvocation.MyCommand.Module.Path); diff --git a/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml b/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml index 586dc08540..e32df137fb 100644 --- a/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml +++ b/src/ServiceControl.Management.PowerShell/ServiceControl.Management.PowerShell.dll-help.xml @@ -2655,6 +2655,13 @@ SwitchParameter + + EnableEmbeddedServicePulse + + Enable the embedded version of ServicePulse that ships with ServiceControl. + + SwitchParameter + Force @@ -2942,6 +2949,18 @@ + + EnableEmbeddedServicePulse + + Enable the embedded version of ServicePulse that ships with ServiceControl + + SwitchParameter + + SwitchParameter + + + + @@ -2998,7 +3017,8 @@ -DisplayName 'ServiceControl Test' ` -AuditRetentionPeriod $AuditRetention ` -ErrorRetentionPeriod $ErrorRetention ` - -ForwardErrorMessages:$false + -ForwardErrorMessages:$false ` + -EnableEmbeddedServicePulse Add a servicecontrol instance From 2d37aadcadb595a618484b67f82c5b40ffd4b31b Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 28 Jan 2026 14:43:13 +0800 Subject: [PATCH 12/13] Update SCMU to use https if enabled --- .../Configuration/ServiceControl/SettingsList.cs | 6 ++++++ .../Instances/ServiceControlBaseService.cs | 11 +++++++---- .../Instances/ServiceControlInstance.cs | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs index 1ff4425a51..ed8a142431 100644 --- a/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs +++ b/src/ServiceControlInstaller.Engine/Configuration/ServiceControl/SettingsList.cs @@ -119,5 +119,11 @@ public static class ServiceControlSettings Name = "ServiceControl/EnableEmbeddedServicePulse", SupportedFrom = new SemanticVersion(6, 9, 0) }; + + public static readonly SettingInfo HttpsEnabled = new() + { + Name = "ServiceControl/Https.Enabled", + SupportedFrom = new SemanticVersion(6, 9, 0) + }; } } diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs index 808c06ec9a..23b8035f9b 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlBaseService.cs @@ -86,9 +86,12 @@ public string AclMaintenanceUrl public bool SkipQueueCreation { get; set; } public bool EnableFullTextSearchOnBodies { get; set; } public bool EnableEmbeddedServicePulse { get; set; } + public bool HttpsEnabled { get; set; } protected abstract string BaseServiceName { get; } + public string UrlScheme => HttpsEnabled ? "https" : "http"; + public string Url { get @@ -96,10 +99,10 @@ public string Url var suffix = EnableEmbeddedServicePulse ? "" : "api/"; if (string.IsNullOrWhiteSpace(VirtualDirectory)) { - return $"http://{HostName}:{Port}/{suffix}"; + return $"{UrlScheme}://{HostName}:{Port}/{suffix}"; } - return $"http://{HostName}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}"; + return $"{UrlScheme}://{HostName}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}"; } } @@ -116,10 +119,10 @@ public string BrowsableUrl }; if (string.IsNullOrWhiteSpace(VirtualDirectory)) { - return $"http://{host}:{Port}/{suffix}"; + return $"{UrlScheme}://{host}:{Port}/{suffix}"; } - return $"http://{host}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}/"; + return $"{UrlScheme}://{host}:{Port}/{VirtualDirectory}{(VirtualDirectory.EndsWith("/") ? string.Empty : "/")}{suffix}/"; } } diff --git a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs index 047a4356b7..01e420c77d 100644 --- a/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs +++ b/src/ServiceControlInstaller.Engine/Instances/ServiceControlInstance.cs @@ -136,6 +136,7 @@ public override void Reload() } EnableEmbeddedServicePulse = AppConfig.Read(ServiceControlSettings.EnableEmbeddedServicePulse, false); + HttpsEnabled = AppConfig.Read(ServiceControlSettings.HttpsEnabled, false); if (TimeSpan.TryParse(AppConfig.Read(ServiceControlSettings.ErrorRetentionPeriod, (string)null), out var errorRetentionPeriod)) { From 2709ff35858733adaf3e50ea51fca69de0b50a5f Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 28 Jan 2026 14:43:24 +0800 Subject: [PATCH 13/13] Adjust package reference after rebase --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index b42557e23e..206342038c 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -75,7 +75,7 @@ - +