From 0c0502d8166be659a8bf9ad261216cd5ecd36210 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:04:57 +0800 Subject: [PATCH 01/11] 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 fd4f3d0667..7b93906d0c 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -74,6 +74,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 2d6bead0e1e3a323b60997fa7d52f77a0b45f8e6 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:08:30 +0800 Subject: [PATCH 02/11] 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 6b447dfb8f..10cd05cddb 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(); @@ -91,6 +92,8 @@ public Settings( public bool AllowMessageEditing { get; set; } + public bool EnableEmbeddedServicePulse { get; set; } + //HINT: acceptance tests only public Func MessageFilter { get; set; } From cd28866119040c20a9eab95debe4642a8d2ef3f8 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:17:56 +0800 Subject: [PATCH 03/11] 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 10cd05cddb..058b13ba4d 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(); @@ -93,6 +98,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 5ac6e2894198052d6f292408aed242a36d0bcc8d Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:30:07 +0800 Subject: [PATCH 04/11] 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 7b93906d0c..52a9cbb9f2 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -74,7 +74,7 @@ - + diff --git a/src/ServiceControl/Infrastructure/Settings/Settings.cs b/src/ServiceControl/Infrastructure/Settings/Settings.cs index 058b13ba4d..2ff3e449d7 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 e90c198f12aaadcc596eebe66cafd624a5fe2386 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 20 Jan 2026 12:45:02 +0800 Subject: [PATCH 05/11] 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 52a9cbb9f2..3037f8365a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -74,7 +74,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 2ff3e449d7..3b5c10ce15 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 ab36794244af1f1dd1ff385f7d6c3a5e9dbdf8ad Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 20 Jan 2026 13:05:39 +0800 Subject: [PATCH 06/11] 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 1469921ca8b154fbaacc2063d3a9bd8adaf3e87c Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 20 Jan 2026 13:49:54 +0800 Subject: [PATCH 07/11] 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 36f65472c4e6a6e5aaf133a27785f1f35cb67367 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 21 Jan 2026 17:20:22 +0800 Subject: [PATCH 08/11] 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 c58957c9b3e59323fa290331b18ea747e3499342 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 21 Jan 2026 17:20:38 +0800 Subject: [PATCH 09/11] 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 3b5c10ce15..7135be8a56 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 2a26157f3be293716d2cbb90639dbb1f41ea7c79 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 22 Jan 2026 11:08:33 +0800 Subject: [PATCH 10/11] 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 9c6d6e50c6dfdaf689103fc6242a3b1ccda0201a Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 23 Jan 2026 09:55:54 +0800 Subject: [PATCH 11/11] 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