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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/pr-build-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ jobs:
with:
dotnet-version: '8.0.x'

- name: Run tests
shell: pwsh
run: dotnet test BASpark.sln -c Release --verbosity normal

- name: Build Single EXE
shell: pwsh
run: |
Expand Down Expand Up @@ -51,4 +55,4 @@ jobs:
with:
name: BASpark-Executable-Preview
path: src/publish_single/
retention-days: 7
retention-days: 7
9 changes: 9 additions & 0 deletions BASpark.sln
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BASpark", "src\BASpark.csproj", "{F43895B0-1503-A7FF-2A92-DDB1081972A3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BASpark.Tests", "tests\BASpark.Tests\BASpark.Tests.csproj", "{2B0161F9-72DB-431B-8E9A-5D192D62995B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -16,12 +20,17 @@ Global
{F43895B0-1503-A7FF-2A92-DDB1081972A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F43895B0-1503-A7FF-2A92-DDB1081972A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F43895B0-1503-A7FF-2A92-DDB1081972A3}.Release|Any CPU.Build.0 = Release|Any CPU
{2B0161F9-72DB-431B-8E9A-5D192D62995B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B0161F9-72DB-431B-8E9A-5D192D62995B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B0161F9-72DB-431B-8E9A-5D192D62995B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B0161F9-72DB-431B-8E9A-5D192D62995B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F43895B0-1503-A7FF-2A92-DDB1081972A3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{2B0161F9-72DB-431B-8E9A-5D192D62995B} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B27D4FF-6E40-4998-BBE8-EE8897314A93}
Expand Down
55 changes: 55 additions & 0 deletions src/AutoStartManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.IO;

namespace BASpark
{
public readonly record struct AutoStartPlan(bool RegistryRunEnabled, bool ScheduledTaskEnabled);

public static class AutoStartManager
{
public const string RunValueName = "BASpark";
public const string TaskName = "BASparkAutoStart";

public static AutoStartPlan CreatePlan(bool autoStart, bool runAsAdmin)
{
if (!autoStart)
{
return new AutoStartPlan(false, false);
}

return runAsAdmin
? new AutoStartPlan(false, true)
: new AutoStartPlan(true, false);
}

public static string BuildRunCommand(string exePath)
{
return $"\"{exePath}\" --autostart";
}

public static string? ResolveExecutablePath(
string? processPath,
string? mainModulePath,
string? assemblyLocation,
string baseDirectory)
{
foreach (string? candidate in new[] { processPath, mainModulePath, assemblyLocation })
{
if (IsExecutablePath(candidate))
{
return candidate;
}
}

return string.IsNullOrWhiteSpace(baseDirectory)
? null
: Path.Combine(baseDirectory, "BASpark.exe");
}

private static bool IsExecutablePath(string? path)
{
return !string.IsNullOrWhiteSpace(path) &&
path.EndsWith(".exe", StringComparison.OrdinalIgnoreCase);
}
}
}
33 changes: 12 additions & 21 deletions src/ControlPanelWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -856,40 +856,31 @@
bool autoStart = CheckAutoStart.IsChecked == true;
bool runAsAdmin = CheckRunAsAdmin.IsChecked == true;

string? exePath = Assembly.GetExecutingAssembly().Location;
if (string.IsNullOrEmpty(exePath))
{
exePath = Process.GetCurrentProcess().MainModule?.FileName;
}
string? exePath = AutoStartManager.ResolveExecutablePath(
Environment.ProcessPath,
Process.GetCurrentProcess().MainModule?.FileName,
Assembly.GetExecutingAssembly().Location,

Check warning on line 862 in src/ControlPanelWindow.xaml.cs

View workflow job for this annotation

GitHub Actions / build

'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'.
AppDomain.CurrentDomain.BaseDirectory);

if (string.IsNullOrEmpty(exePath)) return;

string taskName = "BASparkAutoStart";
string regKeyPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
AutoStartPlan plan = AutoStartManager.CreatePlan(autoStart, runAsAdmin);

try
{
using (RegistryKey? key = Registry.CurrentUser.OpenSubKey(regKeyPath, true))
using (RegistryKey? key = Registry.CurrentUser.CreateSubKey(regKeyPath, true))
{
if (autoStart)
if (plan.RegistryRunEnabled)
{
if (runAsAdmin)
{
if (key?.GetValue("BASpark") != null)
{
key.DeleteValue("BASpark", false);
}
ManageTaskScheduler(taskName, exePath, true);
}
else
{
ManageTaskScheduler(taskName, exePath, false);
}
key?.SetValue(AutoStartManager.RunValueName, AutoStartManager.BuildRunCommand(exePath));
}
else
{
ManageTaskScheduler(taskName, exePath, false);
key?.DeleteValue(AutoStartManager.RunValueName, false);
}

ManageTaskScheduler(AutoStartManager.TaskName, exePath, plan.ScheduledTaskEnabled);
}
}
catch (Exception ex)
Expand Down
51 changes: 51 additions & 0 deletions tests/BASpark.Tests/AutoStartManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace BASpark.Tests;

public class AutoStartManagerTests
{
[Fact]
public void CreatePlan_UsesRegistryForNormalAutoStart()
{
AutoStartPlan plan = AutoStartManager.CreatePlan(autoStart: true, runAsAdmin: false);

Assert.True(plan.RegistryRunEnabled);
Assert.False(plan.ScheduledTaskEnabled);
}

[Fact]
public void CreatePlan_UsesScheduledTaskForElevatedAutoStart()
{
AutoStartPlan plan = AutoStartManager.CreatePlan(autoStart: true, runAsAdmin: true);

Assert.False(plan.RegistryRunEnabled);
Assert.True(plan.ScheduledTaskEnabled);
}

[Fact]
public void CreatePlan_DisablesBothStartupMechanismsWhenAutoStartIsOff()
{
AutoStartPlan plan = AutoStartManager.CreatePlan(autoStart: false, runAsAdmin: true);

Assert.False(plan.RegistryRunEnabled);
Assert.False(plan.ScheduledTaskEnabled);
}

[Fact]
public void BuildRunCommand_AddsAutostartArgumentToQuotedExecutablePath()
{
string command = AutoStartManager.BuildRunCommand(@"C:\Program Files\BASpark\BASpark.exe");

Assert.Equal(@"""C:\Program Files\BASpark\BASpark.exe"" --autostart", command);
}

[Fact]
public void ResolveExecutablePath_PrefersProcessExeOverAssemblyDll()
{
string? resolved = AutoStartManager.ResolveExecutablePath(
processPath: @"C:\Apps\BASpark\BASpark.exe",
mainModulePath: @"C:\Apps\BASpark\BASpark.exe",
assemblyLocation: @"C:\Apps\BASpark\BASpark.dll",
baseDirectory: @"C:\Apps\BASpark");

Assert.Equal(@"C:\Apps\BASpark\BASpark.exe", resolved);
}
}
25 changes: 25 additions & 0 deletions tests/BASpark.Tests/BASpark.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\BASpark.csproj" />
</ItemGroup>

</Project>
Loading