-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.iss
More file actions
334 lines (309 loc) · 14.4 KB
/
Copy pathsetup.iss
File metadata and controls
334 lines (309 loc) · 14.4 KB
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#ifndef AppVersion
#define AppVersion "1.0.0-beta"
#endif
#ifndef AppVersionInfoVersion
#define AppVersionInfoVersion "1.0.0.0"
#endif
[Setup]
AppName=LocalTelemetry
AppVerName=LocalTelemetry
AppVersion={#AppVersion}
AppPublisher=Nicconike
AppPublisherURL=https://github.com/Nicconike/LocalTelemetry
AppSupportURL=https://github.com/Nicconike/LocalTelemetry/issues
AppUpdatesURL=https://github.com/Nicconike/LocalTelemetry/releases
VersionInfoVersion={#AppVersionInfoVersion}
VersionInfoProductVersion={#AppVersionInfoVersion}
VersionInfoDescription=LocalTelemetry Setup
VersionInfoCopyright=Copyright (C) 2026 Nicconike
VersionInfoOriginalFileName=LocalTelemetrySetup.exe
DefaultDirName={commonpf}\LocalTelemetry
DefaultGroupName=LocalTelemetry
OutputDir=.
OutputBaseFilename=LocalTelemetrySetup
Compression=lzma2
AppId={{LocalTelemetry_TaskbarApp}}
SetupIconFile=src\LocalTelemetry.App\app.ico
UninstallDisplayIcon={app}\LocalTelemetry.exe
WizardStyle=modern
SolidCompression=yes
ArchitecturesInstallIn64BitMode=x64compatible
ArchitecturesAllowed=x64compatible
MinVersion=10.0.19041
DisableDirPage=auto
DisableProgramGroupPage=yes
PrivilegesRequired=admin
SetupLogging=yes
CloseApplications=yes
CloseApplicationsFilter=LocalTelemetry.exe,LocalTelemetry.Notifier.exe
[Messages]
StatusExtractFiles=Extracting application files...
StatusCreateIcons=Creating Start Menu & Desktop shortcuts...
[Tasks]
Name: startmenuicon; Description: "Create a &Start Menu shortcut"; GroupDescription: "Shortcuts:"
Name: desktopicon; Description: "Create a &Desktop shortcut"; GroupDescription: "Shortcuts:"
Name: startup; Description: "Run LocalTelemetry at Windows &startup"; GroupDescription: "System options:"
[Files]
Source: "publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
Name: "{group}\LocalTelemetry"; Filename: "{app}\LocalTelemetry.exe"; Tasks: startmenuicon
Name: "{commondesktop}\LocalTelemetry"; Filename: "{app}\LocalTelemetry.exe"; Tasks: desktopicon
[Run]
Filename: "{app}\LocalTelemetry.exe"; Description: "{cm:LaunchProgram,LocalTelemetry}"; Flags: nowait postinstall skipifsilent
[UninstallDelete]
Type: filesandordirs; Name: "{app}"
[Code]
const
StartupTaskName = 'LocalTelemetry Startup';
CSIDL_LOCAL_APPDATA = $001C;
ProfileListKey = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList';
PawnIoCleanupScript =
'$log = $env:ProgramData + ''\LocalTelemetry\pawnio_cleanup.log''' + #13#10 +
'''[PawnIO cleanup] started: '' + (Get-Date) | Set-Content $log' + #13#10 +
'$repo = ''C:\Windows\System32\DriverStore\FileRepository''' + #13#10 +
'if ($env:PROCESSOR_ARCHITECTURE -eq ''x86'' -or $env:PROCESSOR_ARCHITECTURE -eq ''ARM64'') { $pnp = Join-Path $env:windir ''Sysnative\pnputil.exe''; if (-not (Test-Path $pnp)) { $pnp = Join-Path $env:windir ''System32\pnputil.exe'' } } else { $pnp = Join-Path $env:windir ''System32\pnputil.exe'' }' + #13#10 +
'''[PawnIO cleanup] pnputil: '' + $pnp + '' exists: '' + (Test-Path $pnp) | Add-Content $log' + #13#10 +
'Start-Sleep -Seconds 2' + #13#10 +
'for ($attempt = 1; $attempt -le 5; $attempt++) {' + #13#10 +
' $dirs = @(Get-ChildItem $repo -Directory -Filter ''pawnio.inf*'' -ErrorAction SilentlyContinue)' + #13#10 +
' if ($dirs.Count -eq 0) { ''[PawnIO cleanup] no package found'' | Add-Content $log; break }' + #13#10 +
' foreach ($d in $dirs) {' + #13#10 +
' $inf = Join-Path $d.FullName ''pawnio.inf''' + #13#10 +
' ''[PawnIO cleanup] deleting: '' + $inf + '' (attempt '' + $attempt + '')'' | Add-Content $log' + #13#10 +
' & $pnp /delete-driver $inf /uninstall /force *>&1 | Add-Content $log' + #13#10 +
' ''[PawnIO cleanup] pnputil exit code: '' + $LASTEXITCODE | Add-Content $log' + #13#10 +
' }' + #13#10 +
' if (-not (Test-Path (Join-Path $repo ''pawnio.inf*''))) { ''[PawnIO cleanup] package removed'' | Add-Content $log; break }' + #13#10 +
' ''[PawnIO cleanup] retrying...'' | Add-Content $log' + #13#10 +
' Start-Sleep -Seconds 3' + #13#10 +
'}' + #13#10 +
'exit 0';
var
PendingDataDirs: TStringList;
function CreateStartupTask: Boolean;
var
Scheduler, RootFolder, Task, Trigger, Action: Variant;
begin
Result := False;
try
Scheduler := CreateOleObject('Schedule.Service');
Scheduler.Connect;
RootFolder := Scheduler.GetFolder('\');
Task := Scheduler.NewTask(0);
Task.RegistrationInfo.Description := 'Starts LocalTelemetry at user logon (silent, highest privileges).';
Task.Principal.LogonType := 3; // TASK_LOGON_INTERACTIVE_TOKEN
Task.Principal.RunLevel := 1; // TASK_RUNLEVEL_HIGHEST
Trigger := Task.Triggers.Create(9); // TASK_TRIGGER_LOGON
Trigger.Enabled := True;
Action := Task.Actions.Create(0); // TASK_ACTION_EXEC
Action.Path := ExpandConstant('{app}\LocalTelemetry.exe');
Action.Arguments := '--minimized';
RootFolder.RegisterTaskDefinition(StartupTaskName, Task, 6, '', '', 3, '');
Result := True;
except
Log('CreateStartupTask failed: ' + GetExceptionMessage);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
MarkerPath: string;
begin
if CurStep = ssPostInstall then
begin
// Create 'app.mode' marker file inside application directory.
// AppSettings.cs checks for this file's existence to route settings to LocalAppData.
MarkerPath := ExpandConstant('{app}\app.mode');
SaveStringToFile(MarkerPath, 'standard', False);
// Create the "Run at Windows startup" scheduled task if the user selected it.
// Uses the Task Scheduler COM API because schtasks.exe /Create /SC ONLOGON
// silently fails with 0x80004005 on some Windows 11 builds.
if WizardIsTaskSelected('startup') then
begin
if not CreateStartupTask then
MsgBox('LocalTelemetry could not be added to Windows startup. You can enable it later in Settings > General > "Start with Windows".', mbError, MB_OK);
end;
end;
end;
function IsAppRunning(const ExeName: string): Boolean;
var
ResultCode: Integer;
begin
Exec('cmd.exe', '/c tasklist /FI "IMAGENAME eq ' + ExeName + '" 2>NUL | find /I /N "' + ExeName + '" >NUL', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Result := (ResultCode = 0);
end;
procedure WaitForAppExit;
var
Waited: Integer;
begin
Waited := 0;
while (IsAppRunning('LocalTelemetry.exe') or IsAppRunning('LocalTelemetry.Notifier.exe')) and (Waited < 40) do
begin
Sleep(250);
Waited := Waited + 1;
end;
end;
function DeleteUserData(const Dir: string): Boolean;
var
Attempt: Integer;
begin
Result := False;
for Attempt := 1 to 3 do
begin
if not DirExists(Dir) then
begin
Result := True;
Exit;
end;
if DelTree(Dir, True, True, True) then
begin
Result := True;
Exit;
end;
Sleep(500);
end;
end;
function CollectLocalTelemetryDataDirs(const Dirs: TStringList): Integer;
var
Profiles: TArrayOfString;
I: Integer;
ProfilePath, DataDir: string;
begin
Result := 0;
if RegGetSubkeyNames(HKLM, ProfileListKey, Profiles) then
for I := 0 to GetArrayLength(Profiles) - 1 do
begin
ProfilePath := '';
if RegQueryStringValue(HKLM, ProfileListKey + '\' + Profiles[I], 'ProfileImagePath', ProfilePath) then
begin
DataDir := ProfilePath + '\AppData\Local\LocalTelemetry';
if DirExists(DataDir) and (Dirs.IndexOf(DataDir) = -1) then
begin
Dirs.Add(DataDir);
Result := Result + 1;
end;
end;
end;
end;
function InitializeUninstall: Boolean;
var
ResultCode: Integer;
begin
Result := True;
if IsAppRunning('LocalTelemetry.exe') or IsAppRunning('LocalTelemetry.Notifier.exe') then
begin
if MsgBox('LocalTelemetry is currently running in the background.' + #13#10 + #13#10 +
'To proceed with uninstallation, the application needs to be closed.' + #13#10 + #13#10 +
'Do you want to close LocalTelemetry and continue uninstalling?',
mbConfirmation, MB_YESNO) = IDYES then
begin
Exec('taskkill.exe', '/f /im LocalTelemetry.exe /t', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('taskkill.exe', '/f /im LocalTelemetry.Notifier.exe /t', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end
else
begin
Result := False; // Cancel uninstallation
end;
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
TempDir, AppDir, PawnIoLog, PawnIoScript, PreservedMsg: string;
DataDirs: TStringList;
Count, I: Integer;
ResultCode: Integer;
begin
if CurUninstallStep = usUninstall then
begin
// Remove the startup scheduled task created during install
Exec('schtasks.exe', '/Delete /TN "' + StartupTaskName + '" /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Force kill any running instances of main app and background notifier before file deletion
Exec('taskkill.exe', '/f /im LocalTelemetry.exe /t', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('taskkill.exe', '/f /im LocalTelemetry.Notifier.exe /t', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
WaitForAppExit;
// Remove the PawnIO kernel driver service, uninstall registry keys and files
// that the app installed at runtime (see PawnIoDevice.TryInstall). The app is
// killed above so the driver handle is closed before stopping the service.
Exec('sc.exe', 'stop PawnIO', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec('sc.exe', 'delete PawnIO', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
// Remove the PawnIO kernel driver package from the Windows DriverStore via pnputil.
// The kernel driver ignores the SCM stop control (sc stop returns 1052), so only
// pnputil /uninstall can unload it and delete the package. A generated PowerShell
// script (with retries) is used because right after the app is force-killed the
// driver may still be settling and pnputil may report it as in use. A script file
// is used instead of a cmd for /d loop because cmd mangling of the do-clause
// breaks quoted pnputil paths.
PawnIoLog := ExpandConstant('{commonappdata}\LocalTelemetry\pawnio_cleanup.log');
PawnIoScript := ExpandConstant('{tmp}\pawnio_cleanup.ps1');
ForceDirectories(ExpandConstant('{commonappdata}\LocalTelemetry'));
SaveStringToFile(PawnIoScript, PawnIoCleanupScript, False);
Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -File "' + PawnIoScript + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
RegDeleteKeyIncludingSubkeys(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO');
RegDeleteKeyIncludingSubkeys(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\PawnIO');
if DirExists('C:\Program Files\PawnIO') then
DelTree('C:\Program Files\PawnIO', True, True, True);
// Delete all application files (LocalTelemetry.exe, LocalTelemetry.Notifier.exe, DLLs & runtime files) inside {app}
AppDir := ExpandConstant('{app}');
if DirExists(AppDir) then
DelTree(AppDir, True, True, True);
// Prompt user to remove AppData user settings, logs & traffic history.
// Defaults to No (MB_DEFBUTTON2) - nothing is deleted unless the user
// explicitly approves. The uninstaller runs elevated
// (PrivilegesRequired=admin), so {localappdata} would resolve to the
// admin account rather than the user who actually used the app. Instead
// of relying on that constant, enumerate every Windows user profile and
// collect the LocalTelemetry data folder from each one.
PendingDataDirs := TStringList.Create;
DataDirs := TStringList.Create;
try
Count := CollectLocalTelemetryDataDirs(DataDirs);
if Count > 0 then
begin
if Count > 1 then
PreservedMsg := 'Do you also want to remove all saved settings, traffic logs and local hardware monitoring history for all ' +
IntToStr(Count) + ' user profiles on this system?'
else
PreservedMsg := 'Do you also want to remove all saved settings, traffic logs and local hardware monitoring history?';
if MsgBox(PreservedMsg, mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
begin
for I := 0 to DataDirs.Count - 1 do
if not DeleteUserData(DataDirs[I]) then
PendingDataDirs.Add(DataDirs[I]);
TempDir := GetShellFolderByCSIDL(CSIDL_LOCAL_APPDATA, False);
if TempDir <> '' then
begin
TempDir := TempDir + '\Temp\PawnIo';
if DirExists(TempDir) then
DelTree(TempDir, True, True, True);
end;
end
else
begin
PreservedMsg := 'Your settings, logs and monitoring history have been preserved at:' + #13#10;
for I := 0 to DataDirs.Count - 1 do
begin
if I > 0 then
PreservedMsg := PreservedMsg + #13#10;
PreservedMsg := PreservedMsg + DataDirs[I];
end;
MsgBox(PreservedMsg, mbInformation, MB_OK);
end;
end;
finally
DataDirs.Free;
end;
end
else if CurUninstallStep = usPostUninstall then
begin
// Final retry for any user data folders that could not be deleted above
// (e.g. files still locked while the app processes were shutting down).
if Assigned(PendingDataDirs) then
begin
for I := 0 to PendingDataDirs.Count - 1 do
if DirExists(PendingDataDirs[I]) then
DeleteUserData(PendingDataDirs[I]);
PendingDataDirs.Free;
PendingDataDirs := nil;
end;
end;
end;