Skip to content

Commit 74d7a8c

Browse files
committed
chore: Upgrade to NET10
1 parent 34333e9 commit 74d7a8c

6 files changed

Lines changed: 66 additions & 74 deletions

File tree

WinServicesTool/Forms/FormMain.cs

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public sealed partial class FormMain : Form
3333
private readonly bool _shouldSaveOnClose;
3434
private readonly bool _isRunningAsAdmin;
3535
private readonly bool _restartingAsAdmin;
36+
private Font? _fontRegular;
37+
private readonly Dictionary<string, Font> _fontCache = [];
3638

3739
// Store original FillWeight values to detect user modifications
3840
private readonly Dictionary<string, float> _originalFillWeights = [];
@@ -186,6 +188,8 @@ private void FormPrincipal_Load(object? sender, EventArgs e)
186188

187189
private void FormPrincipal_Shown(object? sender, EventArgs e)
188190
{
191+
_isInitializingColumns = true;
192+
189193
try
190194
{
191195
// Restore window position/size/state from config
@@ -215,6 +219,10 @@ private void FormPrincipal_Shown(object? sender, EventArgs e)
215219
{
216220
// ignore restore failures
217221
}
222+
finally
223+
{
224+
_isInitializingColumns = false;
225+
}
218226
}
219227

220228
private void UpdateFilterLists()
@@ -614,7 +622,7 @@ private void GridServs_ColumnHeaderMouseClick(object? sender, DataGridViewCellMo
614622
}
615623

616624
private void GridServs_ColumnWidthChanged(object? sender, DataGridViewColumnEventArgs e)
617-
=> BeginInvoke(UpdateColumnHeaderHeight);
625+
=> BeginInvoke(() => UpdateColumnHeaderHeight());
618626

619627
private void GridServs_ColumnDisplayIndexChanged(object? sender, DataGridViewColumnEventArgs e)
620628
=> SaveColumnOrder();
@@ -628,9 +636,9 @@ private async Task ShowColumnChooserDialogAsync()
628636

629637
using var dlg = new FormColumnChooser([.. GridServs.Columns.Cast<DataGridViewColumn>()], visibleColumns);
630638

631-
#pragma warning disable WFO5002
639+
#pragma warning disable WFO5002
632640
if (await dlg.ShowDialogAsync(this) != DialogResult.OK)
633-
#pragma warning restore WFO5002
641+
#pragma warning restore WFO5002
634642
return;
635643

636644
// Update config with selected columns
@@ -673,10 +681,10 @@ private void ApplyColumnVisibility()
673681
/// Calculates and updates the column header height based on the maximum number of lines
674682
/// in visible column headers.
675683
/// </summary>
676-
private void UpdateColumnHeaderHeight()
684+
private void UpdateColumnHeaderHeight(bool force = false)
677685
{
678686
// Skip during initialization to avoid conflicts with auto-resize
679-
if (_isInitializingColumns)
687+
if (_isInitializingColumns && !force)
680688
return;
681689

682690
try
@@ -685,7 +693,7 @@ private void UpdateColumnHeaderHeight()
685693

686694
using var g = GridServs.CreateGraphics();
687695
var headerStyle = GridServs.ColumnHeadersDefaultCellStyle;
688-
var font = headerStyle.Font;
696+
var font = headerStyle.Font!;
689697

690698
foreach (DataGridViewColumn col in GridServs.Columns)
691699
{
@@ -1043,6 +1051,7 @@ private void BtnLoad_Click(object? sender, EventArgs e)
10431051

10441052
private async Task RefreshServiceListAsync()
10451053
{
1054+
_isInitializingColumns = true;
10461055
BtnLoad.Enabled = false;
10471056
var previousCursor = Cursor.Current;
10481057
Cursor.Current = Cursors.WaitCursor;
@@ -1075,7 +1084,7 @@ private async Task RefreshServiceListAsync()
10751084
// Update column header height after data is loaded and columns have their final widths
10761085
// Use a small delay to ensure all auto-resize operations are complete
10771086
await Task.Delay(50);
1078-
UpdateColumnHeaderHeight();
1087+
UpdateColumnHeaderHeight(force: true);
10791088

10801089
AppendLog($"Loaded {_allServices.Count} services.");
10811090
}
@@ -1090,6 +1099,7 @@ private async Task RefreshServiceListAsync()
10901099
ProgressBar.Visible = false;
10911100
UpdateActionButtonsEnabled();
10921101
Cursor.Current = previousCursor;
1102+
_isInitializingColumns = false;
10931103
}
10941104
}
10951105

@@ -1199,29 +1209,6 @@ private void ApplyFilterAndSort()
11991209
_servicesList.ListChanged += ServicesList_ListChanged;
12001210
serviceBindingSource.DataSource = _servicesList;
12011211
serviceBindingSource.ResetBindings(false);
1202-
1203-
// Ensure the DataGridView repaints so the sort glyph is shown/cleared immediately
1204-
GridServs.Refresh();
1205-
1206-
// If we have a sorted column, invalidate its header to ensure glyph is painted
1207-
if (string.IsNullOrEmpty(_sortPropertyName) || _sortOrder == SortOrder.None)
1208-
return;
1209-
1210-
var idx = GridServs.Columns.Cast<DataGridViewColumn>()
1211-
.ToList()
1212-
.FindIndex(c => c.DataPropertyName == _sortPropertyName || c.Name == _sortPropertyName);
1213-
1214-
if (idx < 0)
1215-
return;
1216-
1217-
GridServs.InvalidateColumn(idx);
1218-
1219-
// additional aggressive repaints
1220-
GridServs.Invalidate();
1221-
GridServs.Update();
1222-
1223-
// clear any selection that might leave header visually selected
1224-
GridServs.ClearSelection();
12251212
}
12261213

12271214
private void ServicesList_ListChanged(object? sender, ListChangedEventArgs e)
@@ -1362,8 +1349,9 @@ private void GridServs_CellFormatting(object? sender, DataGridViewCellFormatting
13621349
var item = _servicesList[e.RowIndex];
13631350
var row = GridServs.Rows[e.RowIndex];
13641351

1365-
// Define a base font, which will be regular
1366-
var baseFont = (Font?)row.DefaultCellStyle.Font ?? GridServs.DefaultCellStyle.Font; // Yes, this can be null
1352+
// Cache the base font to avoid recreating it every time
1353+
_fontRegular ??= new Font((Font?)GridServs.Font ?? SystemFonts.DefaultFont, FontStyle.Regular);
1354+
13671355
var style = FontStyle.Regular;
13681356

13691357
// Check if the executable exists
@@ -1372,8 +1360,16 @@ private void GridServs_CellFormatting(object? sender, DataGridViewCellFormatting
13721360
if (string.IsNullOrEmpty(exePath) || !File.Exists(exePath))
13731361
style |= FontStyle.Strikeout;
13741362

1375-
// Apply font style
1376-
row.DefaultCellStyle.Font = new Font(baseFont, style);
1363+
// Use cached font or create and cache it
1364+
var cacheKey = style.ToString();
1365+
1366+
if (!_fontCache.TryGetValue(cacheKey, out var font))
1367+
{
1368+
font = new Font(GridServs.Font ?? SystemFonts.DefaultFont, style);
1369+
_fontCache[cacheKey] = font;
1370+
}
1371+
1372+
row.DefaultCellStyle.Font = font;
13771373

13781374
// Color the entire row by status
13791375
row.DefaultCellStyle.BackColor = item.GetStatus() switch
@@ -1586,7 +1582,6 @@ private void FormPrincipal_FormClosing(object? sender, FormClosingEventArgs e)
15861582
{
15871583
// Save current window bounds/state to config
15881584
var rect = (WindowState == FormWindowState.Normal) ? Bounds : RestoreBounds;
1589-
15901585
_appConfig.WindowLeft = rect.Left;
15911586
_appConfig.WindowTop = rect.Top;
15921587
_appConfig.WindowWidth = rect.Width;

WinServicesTool/Services/RegistryService.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,9 @@ public bool IsServiceManagedByNssm(string serviceName)
6060
var parametersPath = $@"SYSTEM\CurrentControlSet\Services\{serviceName}\Parameters";
6161
using var parametersKey = Registry.LocalMachine.OpenSubKey(parametersPath);
6262

63-
if (parametersKey == null)
64-
return false;
65-
6663
// If the "Application" registry entry exists, it's an NSSM-managed service
67-
var applicationValue = parametersKey.GetValue("Application");
64+
var applicationValue = parametersKey?.GetValue("Application");
65+
6866
return applicationValue != null;
6967
}
7068
catch

WinServicesTool/Services/ServiceNativeHelper.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -163,18 +163,20 @@ public static System.ServiceProcess.ServiceStartMode ToServiceStartMode(this Sta
163163
var _ => System.ServiceProcess.ServiceStartMode.Manual
164164
};
165165

166-
/// <summary>
167-
/// Gets the <see cref="System.ServiceProcess.ServiceControllerStatus"/> for this configuration.
168-
/// </summary>
169-
public static System.ServiceProcess.ServiceControllerStatus GetStatus(this ServiceConfiguration config)
170-
=> config.CurrentState.ToServiceControllerStatus();
171-
172-
/// <summary>
173-
/// Gets the <see cref="System.ServiceProcess.ServiceStartMode"/> for this configuration.
174-
/// </summary>
175-
public static System.ServiceProcess.ServiceStartMode GetStartMode(this ServiceConfiguration config)
176-
=> config.StartType.ToServiceStartMode();
177-
166+
extension(ServiceConfiguration config)
167+
{
168+
/// <summary>
169+
/// Gets the <see cref="System.ServiceProcess.ServiceControllerStatus"/> for this configuration.
170+
/// </summary>
171+
public System.ServiceProcess.ServiceControllerStatus GetStatus()
172+
=> config.CurrentState.ToServiceControllerStatus();
173+
174+
/// <summary>
175+
/// Gets the <see cref="System.ServiceProcess.ServiceStartMode"/> for this configuration.
176+
/// </summary>
177+
public System.ServiceProcess.ServiceStartMode GetStartMode()
178+
=> config.StartType.ToServiceStartMode();
179+
}
178180
}
179181

180182
/// <summary>
@@ -356,6 +358,7 @@ public string[] GetAllServiceNames()
356358
);
357359

358360
var lastError = Marshal.GetLastWin32Error();
361+
359362
if (lastError != ERROR_INSUFFICIENT_BUFFER && lastError != ERROR_MORE_DATA)
360363
break;
361364

@@ -513,9 +516,7 @@ public string[] GetAllServiceNames()
513516
/// Gets the complete configuration for the specified Windows service asynchronously.
514517
/// Offloads the Win32 calls to a thread pool thread to avoid blocking the UI.
515518
/// </summary>
516-
public async Task<ServiceConfiguration?> GetServiceConfigurationAsync(
517-
string serviceName,
518-
CancellationToken cancellationToken = default)
519+
public async Task<ServiceConfiguration?> GetServiceConfigurationAsync(string serviceName, CancellationToken cancellationToken = default)
519520
{
520521
await _semaphore.WaitAsync(cancellationToken);
521522

@@ -573,9 +574,7 @@ public string[] GetAllServiceNames()
573574
/// <param name="progress">Optional progress reporter (reports completed count)</param>
574575
/// <param name="cancellationToken">Cancellation token</param>
575576
/// <returns>Dictionary mapping service names to their configurations</returns>
576-
public async Task<Dictionary<string, ServiceConfiguration?>> GetAllServiceConfigurationsAsync(
577-
IProgress<int>? progress = null,
578-
CancellationToken cancellationToken = default)
577+
public async Task<Dictionary<string, ServiceConfiguration?>> GetAllServiceConfigurationsAsync(IProgress<int>? progress = null, CancellationToken cancellationToken = default)
579578
{
580579
var serviceNames = await Task.Run(GetAllServiceNames, cancellationToken);
581580

@@ -685,6 +684,7 @@ await Task.Run(() =>
685684
if (!StartService(serviceHandle, 0, nint.Zero))
686685
{
687686
var error = Marshal.GetLastWin32Error();
687+
688688
throw new InvalidOperationException($"Failed to start service '{serviceName}'. Error: {error}");
689689
}
690690

@@ -744,6 +744,7 @@ await Task.Run(() =>
744744
if (!ControlService(serviceHandle, SERVICE_CONTROL_STOP, ref serviceStatus))
745745
{
746746
var error = Marshal.GetLastWin32Error();
747+
747748
throw new InvalidOperationException($"Failed to stop service '{serviceName}'. Error: {error}");
748749
}
749750

WinServicesTool/Services/ServicePathHelperFactory.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace WinServicesTool.Services;
66
/// Factory-based wrapper around <see cref="ServicePathHelper"/> to provide a scoped service
77
/// for dependency injection without holding persistent SCManager connections.
88
/// </summary>
9-
public sealed class ServicePathHelperFactory(ILogger<ServicePathHelperFactory> logger, IRegistryService registryService) : IServicePathHelper
9+
public sealed class ServicePathHelperFactory(ILogger<ServicePathHelperFactory> logger, IRegistryService registryService, IPrivilegeService privilege) : IServicePathHelper
1010
{
1111
public string[] GetAllServiceNames()
1212
{
@@ -20,19 +20,17 @@ public string[] GetAllServiceNames()
2020
using var helper = new ServicePathHelper();
2121

2222
var config = helper.GetServiceConfiguration(serviceName);
23-
config?.IsNssmManaged = registryService.IsServiceManagedByNssm(serviceName);
23+
config?.IsNssmManaged = privilege.IsAdministrator() && registryService.IsServiceManagedByNssm(serviceName);
2424

2525
return config;
2626
}
2727

28-
public async Task<ServiceConfiguration?> GetServiceConfigurationAsync(
29-
string serviceName,
30-
CancellationToken cancellationToken = default)
28+
public async Task<ServiceConfiguration?> GetServiceConfigurationAsync(string serviceName, CancellationToken cancellationToken = default)
3129
{
3230
using var helper = new ServicePathHelper();
3331

3432
var config = await helper.GetServiceConfigurationAsync(serviceName, cancellationToken);
35-
config?.IsNssmManaged = registryService.IsServiceManagedByNssm(serviceName);
33+
config?.IsNssmManaged = privilege.IsAdministrator() && registryService.IsServiceManagedByNssm(serviceName);
3634

3735
return config;
3836
}
@@ -47,7 +45,7 @@ public string[] GetAllServiceNames()
4745
var result = await helper.GetServiceConfigurationsAsync(serviceNames, progress, cancellationToken);
4846

4947
foreach (var kvp in result)
50-
kvp.Value?.IsNssmManaged = registryService.IsServiceManagedByNssm(kvp.Key);
48+
kvp.Value?.IsNssmManaged = privilege.IsAdministrator() && registryService.IsServiceManagedByNssm(kvp.Key);
5149

5250
return result;
5351
}
@@ -61,7 +59,7 @@ public string[] GetAllServiceNames()
6159
var result = await helper.GetAllServiceConfigurationsAsync(progress, cancellationToken);
6260

6361
foreach (var kvp in result)
64-
kvp.Value?.IsNssmManaged = registryService.IsServiceManagedByNssm(kvp.Key);
62+
kvp.Value?.IsNssmManaged = privilege.IsAdministrator() && registryService.IsServiceManagedByNssm(kvp.Key);
6563

6664
return result;
6765
}
@@ -86,7 +84,7 @@ public async Task<List<ServiceConfiguration>> GetServicesAsync(IProgress<int>? p
8684
if (config == null)
8785
continue;
8886

89-
config.IsNssmManaged = registryService.IsServiceManagedByNssm(serviceName);
87+
config.IsNssmManaged = privilege.IsAdministrator() && registryService.IsServiceManagedByNssm(serviceName);
9088
services.Add(config);
9189
}
9290

WinServicesTool/WinServicesTool.csproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>WinExe</OutputType>
5-
<TargetFramework>net9.0-windows</TargetFramework>
5+
<TargetFramework>net10.0-windows</TargetFramework>
66
<Nullable>enable</Nullable>
77
<UseWindowsForms>true</UseWindowsForms>
88
<ImplicitUsings>enable</ImplicitUsings>
@@ -29,18 +29,18 @@
2929
</ItemGroup>
3030

3131
<ItemGroup>
32-
<PackageReference Include="AsyncAwaitBestPractices" Version="9.0.0" />
32+
<PackageReference Include="AsyncAwaitBestPractices" Version="10.0.0" />
3333
<PackageReference Include="Fody" Version="6.9.3">
3434
<PrivateAssets>all</PrivateAssets>
3535
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3636
</PackageReference>
37-
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
38-
<PackageReference Include="NLog" Version="6.0.5" />
39-
<PackageReference Include="NLog.Extensions.Logging" Version="6.0.5" />
37+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
38+
<PackageReference Include="NLog" Version="6.0.6" />
39+
<PackageReference Include="NLog.Extensions.Logging" Version="6.1.0" />
4040
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0">
4141
<PrivateAssets>all</PrivateAssets>
4242
</PackageReference>
43-
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.9" />
43+
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.0" />
4444
</ItemGroup>
4545

4646
<ItemGroup>

tests/WinServicesTool.Tests/WinServicesTool.Tests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0-windows7.0</TargetFramework>
4+
<TargetFramework>net10.0-windows</TargetFramework>
55
<UseWindowsForms>true</UseWindowsForms>
66
<IsPackable>false</IsPackable>
77
<Nullable>enable</Nullable>
88
<ImplicitUsings>enable</ImplicitUsings>
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
12+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
1313
<PackageReference Include="xunit" Version="2.9.3" />
1414
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

0 commit comments

Comments
 (0)