1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using System.IO;
using Microsoft.Win32;
namespace AdamsToolkit.Core;
/// <summary>
/// Gestor de programas que arrancam com o Windows — mesmas fontes e semântica
/// do Gestor de Tarefas: chaves Run (HKCU/HKLM/Wow6432Node) e pastas Arranque,
/// com o estado ligado/desligado guardado em Explorer\StartupApproved (byte 0
/// par = ligado, ímpar = desligado). Desligar NÃO apaga a entrada do programa —
/// é reversível e o Gestor de Tarefas mostra o mesmo estado. Entradas de HKLM
/// precisam de admin para mudar; sem admin ficam só de leitura.
/// A própria app fica de fora (tem o toggle "Iniciar com o Windows" próprio).
/// </summary>
public static class StartupAppsManager
{
private const string RunPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
private const string Run32Path = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run";
private const string ApprovedRun = @"Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run";
private const string ApprovedRun32 = @"Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run32";
private const string ApprovedFolder = @"Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\StartupFolder";
public sealed class StartupApp
{
public string Name { get; init; } = "";
public string Command { get; init; } = "";
public string SourceLabel { get; init; } = "";
public bool Enabled { get; set; }
public bool Machine { get; init; } // HKLM → mudar exige admin
internal string ApprovedKeyPath { get; init; } = "";
}
public static List<StartupApp> List()
{
var list = new List<StartupApp>();
CollectRun(Registry.CurrentUser, RunPath, ApprovedRun, "Utilizador", machine: false, list);
CollectRun(Registry.LocalMachine, RunPath, ApprovedRun, "Sistema", machine: true, list);
CollectRun(Registry.LocalMachine, Run32Path, ApprovedRun32, "Sistema", machine: true, list);
CollectFolder(Environment.GetFolderPath(Environment.SpecialFolder.Startup),
Registry.CurrentUser, "Pasta Arranque", machine: false, list);
CollectFolder(Environment.GetFolderPath(Environment.SpecialFolder.CommonStartup),
Registry.LocalMachine, "Pasta Arranque (todos)", machine: true, list);
return list
.Where(a => !a.Name.Equals("AdamsToolkit", StringComparison.OrdinalIgnoreCase))
.OrderBy(a => a.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static void CollectRun(RegistryKey hive, string runPath, string approvedPath,
string source, bool machine, List<StartupApp> list)
{
try
{
using var run = hive.OpenSubKey(runPath);
if (run == null) return;
using var approved = hive.OpenSubKey(approvedPath);
foreach (var name in run.GetValueNames())
{
if (string.IsNullOrWhiteSpace(name)) continue;
list.Add(new StartupApp
{
Name = name,
Command = run.GetValue(name)?.ToString() ?? "",
SourceLabel = source,
Machine = machine,
Enabled = IsApproved(approved, name),
ApprovedKeyPath = approvedPath,
});
}
}
catch { }
}
private static void CollectFolder(string folder, RegistryKey hive,
string source, bool machine, List<StartupApp> list)
{
try
{
if (string.IsNullOrEmpty(folder) || !Directory.Exists(folder)) return;
using var approved = hive.OpenSubKey(ApprovedFolder);
foreach (var file in Directory.GetFiles(folder))
{
var fileName = Path.GetFileName(file);
if (fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase)) continue;
list.Add(new StartupApp
{
Name = Path.GetFileNameWithoutExtension(file),
Command = file,
SourceLabel = source,
Machine = machine,
Enabled = IsApproved(approved, fileName),
ApprovedKeyPath = ApprovedFolder,
});
}
}
catch { }
}
// Sem valor gravado = ligado; byte 0 par = ligado, ímpar = desligado.
private static bool IsApproved(RegistryKey? approved, string valueName)
{
try
{
if (approved?.GetValue(valueName) is byte[] data && data.Length > 0)
return (data[0] & 1) == 0;
}
catch { }
return true;
}
/// <summary>Liga/desliga uma entrada. Devolve erro ou null se ok.</summary>
public static string? SetEnabled(StartupApp app, bool enabled)
{
try
{
var hive = app.Machine ? Registry.LocalMachine : Registry.CurrentUser;
using var key = hive.OpenSubKey(app.ApprovedKeyPath, writable: true)
?? hive.CreateSubKey(app.ApprovedKeyPath);
if (key == null) return "chave de registo inacessível";
var valueName = app.ApprovedKeyPath == ApprovedFolder
? Path.GetFileName(app.Command) : app.Name;
var data = new byte[12];
if (enabled) data[0] = 0x02;
else
{
// 0x03 + FILETIME de quando foi desligado (formato do Gestor de Tarefas)
data[0] = 0x03;
BitConverter.GetBytes(DateTime.Now.ToFileTime()).CopyTo(data, 4);
}
key.SetValue(valueName, data, RegistryValueKind.Binary);
app.Enabled = enabled;
return null;
}
catch (UnauthorizedAccessException) { return "precisa de admin"; }
catch (Exception e) { return e.Message; }
}
}