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
20 changes: 14 additions & 6 deletions .github/workflows/dotnet-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ jobs:
- name: Build the application
run: msbuild $env:Solution_Name /p:Configuration=${{ matrix.configuration }} /p:Platform=${{ matrix.platform }}

# Download PsExec and Create Batch Script
# Download PsExec, create launcher batch, and stage only release files (no PDB/obj noise)
- name: Prepare Artifacts
run: |
$out = "bin/${{ matrix.platform }}/${{ matrix.configuration }}"
Invoke-WebRequest -Uri "https://download.sysinternals.com/files/PSTools.zip" -OutFile "PSTools.zip"
Expand-Archive -Path "PSTools.zip" -DestinationPath "PSTools"
Copy-Item -Path "PSTools/PsExec.exe" -Destination "bin/${{ matrix.platform }}/${{ matrix.configuration }}/"
Copy-Item -Path "PSTools/PsExec.exe" -Destination $out

$batContent = @"
net session >nul 2>&1
Expand All @@ -71,13 +72,20 @@ jobs:
exit /b
)

"%~dp0PsExec" -i -s -d "%~dp0AsusFanControlGUI.exe"
"%~dp0PsExec" -i -s -d "%~dp0AsusFanControl.exe"
"@
$batContent | Out-File -FilePath "bin/${{ matrix.platform }}/${{ matrix.configuration }}/RunAsAdmin.bat" -Encoding ASCII
$batContent | Out-File -FilePath "$out/RunAsAdmin.bat" -Encoding ASCII

$stage = "artifacts/AsusFanControl"
New-Item -ItemType Directory -Force -Path $stage | Out-Null
Copy-Item "$out/AsusFanControl.exe" $stage
Copy-Item "$out/AsusFanControl.exe.config" $stage
Copy-Item "$out/PsExec.exe" $stage
Copy-Item "$out/RunAsAdmin.bat" $stage

# Upload the build artifact
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: AsusFanControlGUI
path: bin/${{ matrix.platform }}/${{ matrix.configuration }}
name: AsusFanControl
path: artifacts/AsusFanControl
144 changes: 144 additions & 0 deletions AsusFanControl.Core/ProfileManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace AsusFanControl.Core
{
public class FanProfile
{
public string Name { get; set; }
public FanCurve Curve { get; set; }
public List<string> TriggerProcesses { get; set; } = new List<string>();

public FanProfile() { }

public FanProfile(string name, FanCurve curve, IEnumerable<string> triggerProcesses)
{
Name = name;
Curve = curve;
TriggerProcesses = triggerProcesses.ToList();
}

public override string ToString()
{
var curveStr = Curve?.ToString() ?? "";
var procStr = string.Join(";", TriggerProcesses.Select(p => Uri.EscapeDataString(p)));
return $"{Uri.EscapeDataString(Name)}|{curveStr}|{procStr}";
}

public static FanProfile FromString(string data)
{
if (string.IsNullOrWhiteSpace(data)) return null;
var parts = data.Split('|');
if (parts.Length < 3) return null;

return new FanProfile
{
Name = Uri.UnescapeDataString(parts[0]),
Curve = FanCurve.FromString(parts[1]),
TriggerProcesses = parts[2].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(Uri.UnescapeDataString).ToList()
};
}
}

public class ProfileManager
{
private readonly List<FanProfile> _profiles = new List<FanProfile>();
private readonly object _lock = new object();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Unused lock object (_lock) declared but never used. This suggests the class was intended to be thread-safe but the locking was omitted. If accessed from multiple threads concurrently, this could lead to race conditions and data corruption.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _lock object is defined but never used. Since _profiles is a List<FanProfile>, and CheckActiveProfile might be called from a background thread while the UI thread modifies the list (via AddProfile or RemoveProfile), this class is currently not thread-safe and could throw an InvalidOperationException during iteration.

private string _activeProfileName;

public IReadOnlyList<FanProfile> Profiles => _profiles;
public string ActiveProfileName => _activeProfileName;

public void LoadProfiles(string serialized)
{
lock (_lock)
{
_profiles.Clear();
_activeProfileName = null;
if (string.IsNullOrWhiteSpace(serialized)) return;

var entries = serialized.Split(new[] { "||" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var entry in entries)
{
var profile = FanProfile.FromString(entry);
if (profile != null)
_profiles.Add(profile);
}
}
}

public string SaveProfiles()
{
lock (_lock)
{
return string.Join("||", _profiles.Select(p => p.ToString()));
}
}

public void AddProfile(FanProfile profile)
{
if (profile == null) throw new ArgumentNullException(nameof(profile));
lock (_lock)
{
_profiles.RemoveAll(p => string.Equals(p.Name, profile.Name, StringComparison.OrdinalIgnoreCase));
_profiles.Add(profile);
}
}

public void RemoveProfile(string name)
{
lock (_lock)
{
_profiles.RemoveAll(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase));
}
}

private static string NormalizeProcessName(string processName)
{
if (string.IsNullOrEmpty(processName)) return null;
var name = processName;
if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
name = name[..^4];

Check failure on line 104 in AsusFanControl.Core/ProfileManager.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

Missing compiler required member 'System.Index..ctor'

Check failure on line 104 in AsusFanControl.Core/ProfileManager.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

Predefined type 'System.Index' is not defined or imported

Check failure on line 104 in AsusFanControl.Core/ProfileManager.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

Feature 'index operator' is not available in C# 7.3. Please use language version 8.0 or greater.

Check failure on line 104 in AsusFanControl.Core/ProfileManager.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

Predefined type 'System.Range' is not defined or imported

Check failure on line 104 in AsusFanControl.Core/ProfileManager.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

Feature 'range operator' is not available in C# 7.3. Please use language version 8.0 or greater.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The project targets .NET Framework 4.7.2 and explicitly sets <LangVersion>7.3</LangVersion> in the project file. The range operator [..^4] is a C# 8.0 feature and requires the System.Index type, which is not available in .NET Framework 4.7.2. This will cause a compilation error.

            if (name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
                name = name.Substring(0, name.Length - 4);

return name.ToLowerInvariant();
}

public FanProfile CheckActiveProfile(FanCurve defaultCurve)
{
try
{
var runningProcesses = new HashSet<string>(
Process.GetProcesses().Select(p =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Process.GetProcesses() is an expensive operation as it enumerates all system processes. If CheckActiveProfile is called frequently (e.g., from a timer), this will lead to high CPU usage. Consider using a more efficient way to check for specific running processes, such as WMI queries or P/Invoke with EnumProcesses.

{
try { return NormalizeProcessName(p.ProcessName); }
catch { return null; }
}).Where(n => n != null),
StringComparer.OrdinalIgnoreCase
);

foreach (var profile in _profiles)
{
if (profile.TriggerProcesses == null) continue;
if (profile.TriggerProcesses.Any(tp =>
{
var normalized = NormalizeProcessName(tp);
return normalized != null && runningProcesses.Contains(normalized);
}))
{
_activeProfileName = profile.Name;
return profile;
}
}
}
catch
{
// Ignore process enumeration errors
}

_activeProfileName = null;
return null;
}
}
}
110 changes: 110 additions & 0 deletions AsusFanControl.Core/TaskSchedulerHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Diagnostics;
using System.IO;

namespace AsusFanControl.Core
{
public static class TaskSchedulerHelper
{
private const string TaskName = "AsusFanControl_AutoStart";

private static bool WaitForExitSafely(Process proc, int timeoutMs, out int exitCode)
{
if (!proc.WaitForExit(timeoutMs))
{
try { proc.Kill(true); } catch { }

Check failure on line 15 in AsusFanControl.Core/TaskSchedulerHelper.cs

View workflow job for this annotation

GitHub Actions / build (Release, x64)

No overload for method 'Kill' takes 1 arguments

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The proc.Kill(true) overload (which kills the process tree) was introduced in .NET Core 3.0 and .NET Standard 2.1. It is not available in .NET Framework 4.7.2, which this project targets. This will result in a compilation error.

                try { proc.Kill(); } catch { }

try { proc.WaitForExit(1000); } catch { }
exitCode = -1;
return false;
}
exitCode = proc.ExitCode;
return true;
}

public static bool IsTaskRegistered()
{
try
{
var psi = new ProcessStartInfo
{
FileName = "schtasks.exe",
Arguments = $"/Query /TN \"{TaskName}\" /FO CSV /NH",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var proc = Process.Start(psi))
{
if (!WaitForExitSafely(proc, 5000, out int exitCode))
return false;
return exitCode == 0;
}
}
catch
{
return false;
}
}

public static bool RegisterTask(string exePath)
{
try
{
var safePath = Path.GetFullPath(exePath);
if (!File.Exists(safePath))
return false;

var args = exePath.IndexOfAny(new[] { '"', '\n', '\r', ';', '&', '|', '>', '<' }) >= 0;
if (args)
return false;
Comment on lines +58 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This validation is overly restrictive. Including the double quote character " in the IndexOfAny check will cause RegisterTask to return false if the user provides a quoted path, which is common for paths containing spaces. Additionally, schtasks.exe correctly handles quoted paths in the /TR argument.

                var args = exePath.IndexOfAny(new[] { '\n', '\r', ';', '&', '|', '>', '<' }) >= 0;
                if (args)
                    return false;


var psi = new ProcessStartInfo
{
FileName = "schtasks.exe",
Arguments = $"/Create /TN \"{TaskName}\" /TR \"\\\"{safePath}\\\"\" /SC ONLOGON /RL HIGHEST /F",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var proc = Process.Start(psi))
{
if (!WaitForExitSafely(proc, 10000, out int exitCode))
return false;
return exitCode == 0;
}
}
catch
{
return false;
}
}

public static bool UnregisterTask()
{
try
{
var psi = new ProcessStartInfo
{
FileName = "schtasks.exe",
Arguments = $"/Delete /TN \"{TaskName}\" /F",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var proc = Process.Start(psi))
{
if (!WaitForExitSafely(proc, 10000, out int exitCode))
return false;
return exitCode == 0;
}
}
catch
{
return false;
}
}
}
}
12 changes: 6 additions & 6 deletions AsusFanControl.sln
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33801.468
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsusFanControl", "AsusFanControl\AsusFanControl.csproj", "{DF94635E-4107-4EE9-8675-7137E750BC86}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsusFanControl.Core", "AsusFanControl.Core\AsusFanControl.Core.csproj", "{5DE454F6-67E4-42EB-A427-04022A4E0CA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsusFanControlGUI", "AsusFanControlGUI\AsusFanControlGUI.csproj", "{42CC78B6-E3BB-4092-A423-A4EC20FB3C11}"
EndProject
Expand All @@ -13,10 +13,10 @@ Global
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DF94635E-4107-4EE9-8675-7137E750BC86}.Debug|x64.ActiveCfg = Debug|x64
{DF94635E-4107-4EE9-8675-7137E750BC86}.Debug|x64.Build.0 = Debug|x64
{DF94635E-4107-4EE9-8675-7137E750BC86}.Release|x64.ActiveCfg = Release|x64
{DF94635E-4107-4EE9-8675-7137E750BC86}.Release|x64.Build.0 = Release|x64
{5DE454F6-67E4-42EB-A427-04022A4E0CA2}.Debug|x64.ActiveCfg = Debug|Any CPU
{5DE454F6-67E4-42EB-A427-04022A4E0CA2}.Debug|x64.Build.0 = Debug|Any CPU
{5DE454F6-67E4-42EB-A427-04022A4E0CA2}.Release|x64.ActiveCfg = Release|Any CPU
{5DE454F6-67E4-42EB-A427-04022A4E0CA2}.Release|x64.Build.0 = Release|Any CPU
{42CC78B6-E3BB-4092-A423-A4EC20FB3C11}.Debug|x64.ActiveCfg = Debug|x64
{42CC78B6-E3BB-4092-A423-A4EC20FB3C11}.Debug|x64.Build.0 = Debug|x64
{42CC78B6-E3BB-4092-A423-A4EC20FB3C11}.Release|x64.ActiveCfg = Release|x64
Expand Down
6 changes: 0 additions & 6 deletions AsusFanControl/App.config

This file was deleted.

Loading
Loading