Skip to content

Commit 1e26775

Browse files
committed
fix(tasks): address code-review findings
- CRITICAL: AppConfigCloner.CopyInto now copies ScheduledTasks -- without it the feature silently no-opped on Apply (draft prefs dropped on commit). Regression test added. - ScheduledTaskController.QueryState: read state via schtasks /Query /XML and parse the locale-independent <Settings><Enabled> element instead of the localized /FO LIST 'Status:' field (was misclassifying state on non-English Windows -> auto-apply churn). stderr no longer redirected (un-drained pipe could deadlock). - ScheduledTaskMonitor: skip only fully-unmanaged tasks (Default + unmonitored) so a manual Apply disables an unmonitored task ('do it now' ignores IsMonitored), while avoiding background schtasks spawns for never-engaged tasks. - SettingDocs: add task: branches (mechanism/verify/apply/reverse) so ApplyResults and Learn More show real schtasks commands instead of '(unknown)'. - Injection guard: block ( ) too. - UI: query the five task states in parallel on Settings open.
1 parent b8c9c4c commit 1e26775

9 files changed

Lines changed: 187 additions & 49 deletions

File tree

docs/SETTINGS-REFERENCE.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3232,8 +3232,14 @@ sc.exe config "XboxNetApiSvc" start= auto; sc.exe start "XboxNetApiSvc" # rest
32323232
**Command line (PowerShell):**
32333233

32343234
```powershell
3235+
# Check the current value
3236+
schtasks /Query /TN "\microsoft\windows\application experience\microsoft compatibility appraiser" /XML # <Settings><Enabled>false</Enabled> = disabled
3237+
3238+
# Apply the gaming-optimized value
3239+
schtasks /Change /TN "\microsoft\windows\application experience\microsoft compatibility appraiser" /Disable
3240+
32353241
# Reverse it (restore the Windows default)
3236-
schtasks /Change /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /FO LIST)
3242+
schtasks /Change /TN "\microsoft\windows\application experience\microsoft compatibility appraiser" /Enable # re-enable the scheduled task
32373243
```
32383244

32393245
**Reversible via.** schtasks /Change /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /FO LIST)
@@ -3270,8 +3276,14 @@ schtasks /Change /TN "\Microsoft\Windows\Application Experience\Microsoft Compat
32703276
**Command line (PowerShell):**
32713277

32723278
```powershell
3279+
# Check the current value
3280+
schtasks /Query /TN "\microsoft\windows\application experience\microsoft compatibility appraiser exp" /XML # <Settings><Enabled>false</Enabled> = disabled
3281+
3282+
# Apply the gaming-optimized value
3283+
schtasks /Change /TN "\microsoft\windows\application experience\microsoft compatibility appraiser exp" /Disable
3284+
32733285
# Reverse it (restore the Windows default)
3274-
schtasks /Change /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser Exp" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser Exp" /FO LIST)
3286+
schtasks /Change /TN "\microsoft\windows\application experience\microsoft compatibility appraiser exp" /Enable # re-enable the scheduled task
32753287
```
32763288

32773289
**Reversible via.** schtasks /Change /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser Exp" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser Exp" /FO LIST)
@@ -3308,8 +3320,14 @@ schtasks /Change /TN "\Microsoft\Windows\Application Experience\Microsoft Compat
33083320
**Command line (PowerShell):**
33093321

33103322
```powershell
3323+
# Check the current value
3324+
schtasks /Query /TN "\microsoft\windows\application experience\pcapatchdbtask" /XML # <Settings><Enabled>false</Enabled> = disabled
3325+
3326+
# Apply the gaming-optimized value
3327+
schtasks /Change /TN "\microsoft\windows\application experience\pcapatchdbtask" /Disable
3328+
33113329
# Reverse it (restore the Windows default)
3312-
schtasks /Change /TN "\Microsoft\Windows\Application Experience\PcaPatchDbTask" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\PcaPatchDbTask" /FO LIST)
3330+
schtasks /Change /TN "\microsoft\windows\application experience\pcapatchdbtask" /Enable # re-enable the scheduled task
33133331
```
33143332

33153333
**Reversible via.** schtasks /Change /TN "\Microsoft\Windows\Application Experience\PcaPatchDbTask" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\PcaPatchDbTask" /FO LIST)
@@ -3346,8 +3364,14 @@ schtasks /Change /TN "\Microsoft\Windows\Application Experience\PcaPatchDbTask"
33463364
**Command line (PowerShell):**
33473365

33483366
```powershell
3367+
# Check the current value
3368+
schtasks /Query /TN "\microsoft\windows\application experience\programdataupdater" /XML # <Settings><Enabled>false</Enabled> = disabled
3369+
3370+
# Apply the gaming-optimized value
3371+
schtasks /Change /TN "\microsoft\windows\application experience\programdataupdater" /Disable
3372+
33493373
# Reverse it (restore the Windows default)
3350-
schtasks /Change /TN "\Microsoft\Windows\Application Experience\ProgramDataUpdater" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\ProgramDataUpdater" /FO LIST)
3374+
schtasks /Change /TN "\microsoft\windows\application experience\programdataupdater" /Enable # re-enable the scheduled task
33513375
```
33523376

33533377
**Reversible via.** schtasks /Change /TN "\Microsoft\Windows\Application Experience\ProgramDataUpdater" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\ProgramDataUpdater" /FO LIST)
@@ -3384,8 +3408,14 @@ schtasks /Change /TN "\Microsoft\Windows\Application Experience\ProgramDataUpdat
33843408
**Command line (PowerShell):**
33853409

33863410
```powershell
3411+
# Check the current value
3412+
schtasks /Query /TN "\microsoft\windows\application experience\startupapptask" /XML # <Settings><Enabled>false</Enabled> = disabled
3413+
3414+
# Apply the gaming-optimized value
3415+
schtasks /Change /TN "\microsoft\windows\application experience\startupapptask" /Disable
3416+
33873417
# Reverse it (restore the Windows default)
3388-
schtasks /Change /TN "\Microsoft\Windows\Application Experience\StartupAppTask" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\StartupAppTask" /FO LIST)
3418+
schtasks /Change /TN "\microsoft\windows\application experience\startupapptask" /Enable # re-enable the scheduled task
33893419
```
33903420

33913421
**Reversible via.** schtasks /Change /TN "\Microsoft\Windows\Application Experience\StartupAppTask" /Enable (verify: schtasks /Query /TN "\Microsoft\Windows\Application Experience\StartupAppTask" /FO LIST)

src/GamerGuardian/Monitors/ScheduledTaskMonitor.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,15 @@ public ScheduledTaskMonitor(ScheduledTaskDefinition def, Func<string, ScheduledT
3333
public IEnumerable<DriftItem> CheckDrift(AppConfig config)
3434
{
3535
if (!config.ScheduledTasks.TryGetValue(_def.TaskPath, out var pref) || pref is null) yield break;
36-
if (!pref.Monitor) yield break;
36+
37+
// Fully-unmanaged tasks (Windows default + not monitored) never drift, so skip
38+
// them before the background schtasks /Query spawn. A task the user explicitly
39+
// wants Disabled is still evaluated even when Monitor is off, so a manual Apply
40+
// ("do it now") disables it regardless of the Monitor checkbox -- matching the
41+
// app-wide rule that Apply ignores IsMonitored. (Reset stages Desired=Default +
42+
// Monitor=off, so it un-manages a task without re-enabling it -- the telemetry
43+
// tasks are harmless left disabled; re-enable explicitly via the row if wanted.)
44+
if (pref.Desired == ScheduledTaskTarget.Default && !pref.Monitor) yield break;
3745

3846
var current = _readState(_def.TaskPath);
3947
if (current == ScheduledTaskState.NotPresent) yield break; // absent on this build -> not drift

src/GamerGuardian/Services/AppConfigCloner.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,6 @@ public static void CopyInto(AppConfig source, AppConfig target)
5151
target.Global = source.Global;
5252
target.Services = source.Services;
5353
target.WindowsAiApps = source.WindowsAiApps;
54+
target.ScheduledTasks = source.ScheduledTasks;
5455
}
5556
}

src/GamerGuardian/Services/ScheduledTaskController.cs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Diagnostics;
33
using System.IO;
44
using System.Text;
5+
using System.Xml.Linq;
56

67
namespace GamerGuardian.Services;
78

@@ -29,7 +30,7 @@ public static class ScheduledTaskController
2930
// handed to the elevated cmd.exe is still guarded — matching ElevatedRegistry — so
3031
// a future caller can't open a command-injection hole. Backslash and space are
3132
// legal in task paths and intentionally allowed.
32-
private static readonly char[] ShellMeta = { '&', '|', '<', '>', '^', '"', '`', '%', ';', '\n', '\r' };
33+
private static readonly char[] ShellMeta = { '&', '|', '<', '>', '^', '"', '`', '%', ';', '(', ')', '\n', '\r' };
3334

3435
private static void GuardPath(string taskPath)
3536
{
@@ -40,9 +41,11 @@ private static void GuardPath(string taskPath)
4041
}
4142

4243
/// <summary>
43-
/// Reads the task's enabled state without elevation. A task that doesn't exist
44-
/// on this build (schtasks returns non-zero) maps to <see cref="ScheduledTaskState.NotPresent"/>,
45-
/// which the monitor treats as "not drift".
44+
/// Reads the task's enabled state without elevation via <c>schtasks /Query /XML</c>.
45+
/// The XML representation is locale-independent (unlike <c>/FO LIST</c>'s localized
46+
/// "Status:" field). A task that doesn't exist on this build (schtasks returns
47+
/// non-zero) maps to <see cref="ScheduledTaskState.NotPresent"/>, which the monitor
48+
/// treats as "not drift".
4649
/// </summary>
4750
public static ScheduledTaskState QueryState(string taskPath)
4851
{
@@ -52,11 +55,15 @@ public static ScheduledTaskState QueryState(string taskPath)
5255
var psi = new ProcessStartInfo
5356
{
5457
FileName = Path.Combine(system32, "schtasks.exe"),
55-
Arguments = $"/Query /TN \"{taskPath}\" /FO LIST",
58+
Arguments = $"/Query /TN \"{taskPath}\" /XML ONE",
5659
WorkingDirectory = system32,
5760
UseShellExecute = false,
5861
RedirectStandardOutput = true,
59-
RedirectStandardError = true,
62+
// stderr is intentionally NOT redirected: we don't use it, and an
63+
// un-drained redirected stderr pipe can deadlock the child if it
64+
// writes more than the pipe buffer. Inheriting (no console -> discarded)
65+
// is safe and avoids the deadlock.
66+
RedirectStandardError = false,
6067
CreateNoWindow = true,
6168
};
6269
using var p = Process.Start(psi);
@@ -65,7 +72,7 @@ public static ScheduledTaskState QueryState(string taskPath)
6572
p.WaitForExit(10_000);
6673
if (!p.HasExited) { try { p.Kill(entireProcessTree: true); } catch { } return ScheduledTaskState.NotPresent; }
6774
if (p.ExitCode != 0) return ScheduledTaskState.NotPresent;
68-
return ParseState(stdout);
75+
return ParseEnabledState(stdout);
6976
}
7077
catch
7178
{
@@ -74,25 +81,30 @@ public static ScheduledTaskState QueryState(string taskPath)
7481
}
7582

7683
/// <summary>
77-
/// Parses <c>schtasks /Query ... /FO LIST</c> output. The "Status:" line reads
78-
/// "Disabled" when the task is disabled and "Ready"/"Running" when enabled.
79-
/// Exposed for unit testing without spawning schtasks.
84+
/// Parses <c>schtasks /Query ... /XML</c> output and returns the task's state from
85+
/// the locale-independent <c>Task/Settings/Enabled</c> element: <c>false</c> means
86+
/// Disabled; <c>true</c> or absent means Enabled. Trigger elements can also carry an
87+
/// <c>Enabled</c> flag, so this targets <c>Settings</c> specifically. Exposed for
88+
/// unit testing without spawning schtasks.
8089
/// </summary>
81-
public static ScheduledTaskState ParseState(string queryOutput)
90+
public static ScheduledTaskState ParseEnabledState(string taskXml)
8291
{
83-
if (string.IsNullOrWhiteSpace(queryOutput)) return ScheduledTaskState.NotPresent;
84-
foreach (var rawLine in queryOutput.Split('\n'))
92+
if (string.IsNullOrWhiteSpace(taskXml)) return ScheduledTaskState.NotPresent;
93+
try
8594
{
86-
var line = rawLine.Trim();
87-
if (line.StartsWith("Status:", StringComparison.OrdinalIgnoreCase))
88-
{
89-
var value = line.Substring("Status:".Length).Trim();
90-
return value.StartsWith("Disabled", StringComparison.OrdinalIgnoreCase)
91-
? ScheduledTaskState.Disabled
92-
: ScheduledTaskState.Enabled;
93-
}
95+
var root = XDocument.Parse(taskXml).Root;
96+
if (root is null) return ScheduledTaskState.NotPresent;
97+
var ns = root.Name.Namespace;
98+
var enabled = root.Element(ns + "Settings")?.Element(ns + "Enabled");
99+
return enabled is not null
100+
&& string.Equals(enabled.Value.Trim(), "false", StringComparison.OrdinalIgnoreCase)
101+
? ScheduledTaskState.Disabled
102+
: ScheduledTaskState.Enabled;
103+
}
104+
catch
105+
{
106+
return ScheduledTaskState.NotPresent;
94107
}
95-
return ScheduledTaskState.NotPresent;
96108
}
97109

98110
/// <summary>Disable one or more tasks in a single elevation (one UAC prompt).</summary>

src/GamerGuardian/Services/SettingDocs.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public static string MechanismFor(string settingId)
2222
return $"reg add HKLM\\{po.PolicyKey} /v {po.PolicyValue} (Group Policy override; Windows reverts the standard sc.exe path)";
2323
return $"sc.exe stop / config (writes HKLM\\SYSTEM\\CurrentControlSet\\Services\\{name}\\Start)";
2424
}
25+
if (settingId.StartsWith("task:"))
26+
{
27+
var path = settingId["task:".Length..];
28+
return $"schtasks /Change /TN \"{path}\" /Disable (sets the Task Scheduler enabled flag)";
29+
}
2530
if (settingId.StartsWith("ai.app:"))
2631
{
2732
var pkg = settingId["ai.app:".Length..];
@@ -116,6 +121,11 @@ public static string ApplyCommandFor(string settingId, string rawDesired = "")
116121
var pkg = settingId["ai.app:".Length..];
117122
return $"Get-AppxPackage -Name '{pkg}' | Remove-AppxPackage";
118123
}
124+
if (settingId.StartsWith("task:"))
125+
{
126+
var path = settingId["task:".Length..];
127+
return $"schtasks /Change /TN \"{path}\" /Disable";
128+
}
119129
if (settingId.StartsWith("hdr:"))
120130
return "(no direct PowerShell equivalent; uses DisplayConfigSetDeviceInfo via the CCD API)";
121131
if (settingId.StartsWith("refresh:"))
@@ -249,6 +259,11 @@ public static string VerifyCommandFor(string settingId)
249259
return $"(Get-ItemProperty 'HKLM:\\{po.PolicyKey}' -Name {po.PolicyValue} -EA SilentlyContinue).{po.PolicyValue}";
250260
return $"sc qc \"{name}\" # look for START_TYPE";
251261
}
262+
if (settingId.StartsWith("task:"))
263+
{
264+
var path = settingId["task:".Length..];
265+
return $"schtasks /Query /TN \"{path}\" /XML # <Settings><Enabled>false</Enabled> = disabled";
266+
}
252267
return settingId switch
253268
{
254269
"hags" => @"(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\GraphicsDrivers' -Name HwSchMode).HwSchMode",
@@ -325,6 +340,11 @@ public static string ReverseCommandFor(string settingId)
325340
}
326341
if (settingId.StartsWith("ai.app:"))
327342
return "(no command-line restore -- reinstall from the Microsoft Store, or wait for Windows Update to re-provision)";
343+
if (settingId.StartsWith("task:"))
344+
{
345+
var path = settingId["task:".Length..];
346+
return $"schtasks /Change /TN \"{path}\" /Enable # re-enable the scheduled task";
347+
}
328348
if (settingId.StartsWith("hdr:") || settingId.StartsWith("refresh:")
329349
|| settingId.StartsWith("resolution:") || settingId.StartsWith("drr:"))
330350
return "(no PowerShell equivalent -- flip it back in Settings > System > Display)";

src/GamerGuardian/UI/SettingsWindow.xaml.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,22 @@ private void LoadServices()
209209
private void LoadScheduledTasks()
210210
{
211211
ScheduledTaskRows.Clear();
212-
foreach (var def in ScheduledTaskCatalog.All)
212+
213+
// QueryState spawns schtasks.exe; run the five queries in parallel so opening
214+
// Settings blocks on the slowest single spawn, not the sum of all five.
215+
var states = ScheduledTaskCatalog.All
216+
.AsParallel().AsOrdered()
217+
.Select(def => (def, state: ScheduledTaskController.QueryState(def.TaskPath)))
218+
.ToList();
219+
220+
foreach (var (def, state) in states)
213221
{
214222
if (!_draft.ScheduledTasks.TryGetValue(def.TaskPath, out var pref) || pref is null)
215223
{
216224
pref = new ScheduledTaskPref();
217225
_draft.ScheduledTasks[def.TaskPath] = pref;
218226
}
219227

220-
var state = ScheduledTaskController.QueryState(def.TaskPath);
221228
bool present = state != ScheduledTaskState.NotPresent;
222229

223230
// For tasks the user hasn't opted into monitoring, mirror the live state into

tests/GamerGuardian.Tests/AppConfigClonerTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,29 @@ public void CopyInto_OverwritesFieldsWithoutReplacingTargetReference()
7777
Assert.False(live.Global.Hags.DesiredOn);
7878
}
7979

80+
[Fact]
81+
public void CopyInto_CommitsScheduledTaskPrefs()
82+
{
83+
// Regression guard: CopyInto must carry ScheduledTasks, or the scheduled-task
84+
// feature silently no-ops on Apply (draft prefs are dropped on commit).
85+
const string taskPath = @"\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser";
86+
var live = new AppConfig();
87+
var draft = AppConfigCloner.Clone(live);
88+
draft.ScheduledTasks[taskPath] = new ScheduledTaskPref
89+
{
90+
Desired = ScheduledTaskTarget.Disabled,
91+
Monitor = true,
92+
AutoApply = true,
93+
};
94+
95+
AppConfigCloner.CopyInto(draft, live);
96+
97+
Assert.True(live.ScheduledTasks.TryGetValue(taskPath, out var pref));
98+
Assert.Equal(ScheduledTaskTarget.Disabled, pref!.Desired);
99+
Assert.True(pref.Monitor);
100+
Assert.True(pref.AutoApply);
101+
}
102+
80103
[Fact]
81104
public void Clone_NullSource_Throws()
82105
{

tests/GamerGuardian.Tests/ScheduledTaskControllerTests.cs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,32 +49,53 @@ public void BuildChangeBatch_RejectsShellMetacharacters(string maliciousPath)
4949
ScheduledTaskController.BuildChangeBatch(new[] { maliciousPath }, enable: false));
5050
}
5151

52+
private const string Ns = "http://schemas.microsoft.com/windows/2004/02/mit/task";
53+
5254
[Fact]
53-
public void ParseState_DisabledStatus_MapsToDisabled()
55+
public void ParseEnabledState_SettingsEnabledFalse_MapsToDisabled()
5456
{
55-
const string output = """
56-
Folder: \Microsoft\Windows\Application Experience
57-
HostName: DESKTOP
58-
TaskName: \Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser
59-
Next Run Time: N/A
60-
Status: Disabled
57+
var xml = $"""
58+
<?xml version="1.0" encoding="UTF-16"?>
59+
<Task version="1.4" xmlns="{Ns}">
60+
<Settings>
61+
<Enabled>false</Enabled>
62+
</Settings>
63+
</Task>
6164
""";
62-
Assert.Equal(ScheduledTaskState.Disabled, ScheduledTaskController.ParseState(output));
65+
Assert.Equal(ScheduledTaskState.Disabled, ScheduledTaskController.ParseEnabledState(xml));
6366
}
6467

65-
[Theory]
66-
[InlineData("Ready")]
67-
[InlineData("Running")]
68-
public void ParseState_EnabledStatuses_MapToEnabled(string status)
68+
[Fact]
69+
public void ParseEnabledState_SettingsEnabledTrue_MapsToEnabled()
70+
{
71+
var xml = $"""<Task xmlns="{Ns}"><Settings><Enabled>true</Enabled></Settings></Task>""";
72+
Assert.Equal(ScheduledTaskState.Enabled, ScheduledTaskController.ParseEnabledState(xml));
73+
}
74+
75+
[Fact]
76+
public void ParseEnabledState_NoSettingsEnabled_DefaultsToEnabled()
77+
{
78+
var xml = $"""<Task xmlns="{Ns}"><Settings/></Task>""";
79+
Assert.Equal(ScheduledTaskState.Enabled, ScheduledTaskController.ParseEnabledState(xml));
80+
}
81+
82+
[Fact]
83+
public void ParseEnabledState_TriggerEnabledFalse_DoesNotMaskSettingsEnabled()
6984
{
70-
var output = $"TaskName: \\foo\nStatus: {status}\n";
71-
Assert.Equal(ScheduledTaskState.Enabled, ScheduledTaskController.ParseState(output));
85+
// A disabled trigger must not be read as a disabled task; only Settings/Enabled counts.
86+
var xml = $"""
87+
<Task xmlns="{Ns}">
88+
<Triggers><CalendarTrigger><Enabled>false</Enabled></CalendarTrigger></Triggers>
89+
<Settings><Enabled>true</Enabled></Settings>
90+
</Task>
91+
""";
92+
Assert.Equal(ScheduledTaskState.Enabled, ScheduledTaskController.ParseEnabledState(xml));
7293
}
7394

7495
[Fact]
75-
public void ParseState_NoStatusLine_MapsToNotPresent()
96+
public void ParseEnabledState_EmptyOrGarbage_MapsToNotPresent()
7697
{
77-
Assert.Equal(ScheduledTaskState.NotPresent, ScheduledTaskController.ParseState("ERROR: task not found"));
78-
Assert.Equal(ScheduledTaskState.NotPresent, ScheduledTaskController.ParseState(""));
98+
Assert.Equal(ScheduledTaskState.NotPresent, ScheduledTaskController.ParseEnabledState(""));
99+
Assert.Equal(ScheduledTaskState.NotPresent, ScheduledTaskController.ParseEnabledState("ERROR: not found"));
79100
}
80101
}

0 commit comments

Comments
 (0)