forked from jrsoftware/issrc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCompWizardRegistryHelper.pas
482 lines (420 loc) · 18.8 KB
/
CompWizardRegistryHelper.pas
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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
unit CompWizardRegistryHelper;
{
Inno Setup
Copyright (C) 1997-2024 Jordan Russell
Portions by Martijn Laan
For conditions of distribution and use, see LICENSE.TXT.
Helper to avoid duplicate code between CompWizard and CompRegistryDesigner
}
interface
uses
Forms, StdCtrls, ExtCtrls;
type
TPrivilegesRequired = (prAdmin, prLowest, prDynamic);
TWizardFormRegistryHelper = class
private
FForm: TForm;
FFileEdit: TEdit;
FUninsDeleteKeyCheck, FUninsDeleteKeyIfEmptyCheck,
FUninsDeleteValueCheck, FMinVerCheck: TCheckBox;
FMinVerEdit: TEdit;
FMinVerDocImage: TImage;
FPrivilegesRequired: TPrivilegesRequired;
procedure SetPrivilegesRequired(const Value: TPrivilegesRequired);
procedure UpdateImages;
procedure AfterMonitorDpiChanged(Sender: TObject; OldDPI: Integer; NewDPI: Integer);
procedure FileButtonClick(Sender: TObject);
procedure UninsDeleteKeyIfEmptyCheckClick(Sender: TObject);
procedure MinVerCheckClick(Sender: TObject);
procedure MinVerDocImageClick(Sender: TObject);
public
constructor Create(const Form: TForm; const FileEdit: TEdit;
const FileButton: TButton; const UninsDeleteKeyCheck,
UninsDeleteKeyIfEmptyCheck, UninsDeleteValueCheck, MinVerCheck: TCheckBox;
const MinVerEdit: TEdit; const MinVerDocImage: TImage);
procedure AddScript(var Registry: String; const AllowException: Boolean);
property PrivilegesRequired: TPrivilegesRequired write SetPrivilegesRequired;
end;
implementation
uses
Windows, Classes, SysUtils, StrUtils, TypInfo, Graphics, UITypes,
ComCtrls,
CompForm, CompFunc, CompMsgs2, BrowseFunc, CmnFunc2, HtmlHelpFunc;
{ TWizardFormRegistryHelper }
procedure TWizardFormRegistryHelper.SetPrivilegesRequired(
const Value: TPrivilegesRequired);
begin
FPrivilegesRequired := Value;
end;
procedure TWizardFormRegistryHelper.UpdateImages;
function GetImage(const Button: TToolButton; const WH: Integer): TWICImage;
begin
Result := CompileForm.LightToolBarImageCollection.GetSourceImage(Button.ImageIndex, WH, WH)
end;
begin
{ After a DPI change the button's Width and Height isn't yet updated, so calculate it ourselves }
var WH := MulDiv(16, FForm.CurrentPPI, 96);
FMinVerDocImage.Picture.Graphic:= GetImage(CompileForm.HelpButton, WH);
end;
constructor TWizardFormRegistryHelper.Create(const Form: TForm;
const FileEdit: TEdit; const FileButton: TButton; const UninsDeleteKeyCheck,
UninsDeleteKeyIfEmptyCheck, UninsDeleteValueCheck, MinVerCheck: TCheckBox;
const MinVerEdit: TEdit; const MinVerDocImage: TImage);
begin
FForm := Form;
FFileEdit := FileEdit;
FUninsDeleteKeyCheck := UninsDeleteKeyCheck;
FUninsDeleteKeyIfEmptyCheck := UninsDeleteKeyIfEmptyCheck;
FUninsDeleteValueCheck := UninsDeleteValueCheck;
FMinVerCheck := MinVerCheck;
FMinVerEdit := MinVerEdit;
FMinVerDocImage := MinVerDocImage;
FileButton.OnClick := FileButtonClick;
UninsDeleteKeyIfEmptyCheck.OnClick := UninsDeleteKeyIfEmptyCheckClick;
MinVerCheck.OnClick := MinVerCheckClick;
MinVerCheck.OnClick(nil);
MinVerDocImage.OnClick := MinVerDocImageClick;
MinVerDocImage.Cursor := crHandPoint;
TryEnableAutoCompleteFileSystem(FileEdit.Handle);
Form.OnAfterMonitorDpiChanged := AfterMonitorDpiChanged;
UpdateImages;
end;
procedure TWizardFormRegistryHelper.AfterMonitorDpiChanged(Sender: TObject; OldDPI: Integer; NewDPI: Integer);
begin
UpdateImages;
end;
procedure TWizardFormRegistryHelper.FileButtonClick(Sender: TObject);
begin
var FileName: String := FFileEdit.Text;
if NewGetOpenFileName('', FileName, '', SWizardAppRegFilter, SWizardAppRegDefaultExt, FForm.Handle) then
FFileEdit.Text := FileName;
end;
procedure TWizardFormRegistryHelper.UninsDeleteKeyIfEmptyCheckClick(Sender: TObject);
begin
FUninsDeleteKeyCheck.Enabled := FUninsDeleteKeyIfEmptyCheck.Checked;
if not FUninsDeleteKeyCheck.Enabled then
FUninsDeleteKeyCheck.Checked := False;
end;
procedure TWizardFormRegistryHelper.MinVerCheckClick(Sender: TObject);
begin
FMinVerEdit.Enabled := FMinVerCheck.Checked;
FMinVerDocImage.Visible := FMinVerCheck.Checked;
if FMinVerEdit.Enabled then
FForm.ActiveControl := FMinVerEdit;
end;
procedure TWizardFormRegistryHelper.MinVerDocImageClick(Sender: TObject);
begin
if Assigned(HtmlHelp) then
HtmlHelp(GetDesktopWindow, PChar(GetHelpFile), HH_DISPLAY_TOPIC, Cardinal(PChar('topic_winvernotes.htm')));
end;
procedure TWizardFormRegistryHelper.AddScript(var Registry: String;
const AllowException: Boolean);
function NextLine(const Lines: TStrings; var LineIndex: Integer): String;
begin
Inc(LineIndex);
if LineIndex < Lines.Count then
Result := Lines[LineIndex]
else
Result := ''; { Official .reg files must end with a blank line so should never get here but we support ones without }
end;
function CutStrBeginEnd(S: String; CharCount: Integer): String;
begin
Result := Copy(S, CharCount + 1, S.Length - 2 * CharCount);
end;
function StrRootRename(S: String): String;
type
TStrings = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, HKEY_USERS, HKEY_CURRENT_CONFIG);
begin
var ARoot := TStrings(GetEnumValue(TypeInfo(TStrings), S));
case ARoot of
HKEY_CURRENT_USER: Result := 'HKCU';
HKEY_LOCAL_MACHINE: Result := 'HKLM';
HKEY_CLASSES_ROOT: Result := 'HKCR';
HKEY_USERS: Result := 'HKU';
HKEY_CURRENT_CONFIG: Result := 'HKCC';
else
raise Exception.CreateFmt('Unknown root %s', [S]);
end;
end;
function UTF16LEHexStrToStr(HexStr: String): String;
begin
if HexStr.Length mod 4 <> 0 then
HexStr := HexStr + '00'; { RegEdit does this as well on import }
var UTF16LEBytes: TBytes;
SetLength(UTF16LEBytes, HexStr.Length div 2);
var i := 1;
var idx := 0;
while i <= HexStr.Length do
begin
UTF16LEBytes[idx] := StrToInt('$' + HexStr[i] + HexStr[i + 1]);
i := i + 2;
idx := idx + 1;
end;
Result := TEncoding.Unicode.GetString(UTF16LEBytes);
end;
type
TValueType = (vtSz, vtSzAsList, vtExpandSz, vtMultiSz, vtBinary, vtDWord, vtDWordAsList, vtQWord, vtNone, vtDelete, vtUnsupported);
function GetValueType(AStr: String): TValueType;
{ See https://en.wikipedia.org/wiki/Windows_Registry#.REG_files
Value formats: (we don't support I/K/L and just ignore those)
"Value A"="<REG_SZ String value data with escape characters>"
"Value B"=hex:<REG_BINARY Binary data (as comma-delimited list of hexadecimal values)>
"Value C"=dword:<REG_DWORD DWORD value integer>
"Value D"=hex(0):<REG_NONE (as comma-delimited list of hexadecimal values)>
"Value E"=hex(1):<REG_SZ (as comma-delimited list of hexadecimal values representing a UTF-16LE NUL-terminated string)>
"Value F"=hex(2):<REG_EXPAND_SZ Expandable string value data (as comma-delimited list of hexadecimal values representing a UTF-16LE NUL-terminated string)>
"Value G"=hex(3):<REG_BINARY Binary data (as comma-delimited list of hexadecimal values)> ; equal to "Value B"
"Value H"=hex(4):<REG_DWORD DWORD value (as comma-delimited list of 4 hexadecimal values, in little endian byte order)>
"Value I"=hex(5):<REG_DWORD_BIG_ENDIAN DWORD value (as comma-delimited list of 4 hexadecimal values, in big endian byte order)>
"Value J"=hex(7):<RED_MULTISZ Multi-string value data (as comma-delimited list of hexadecimal values representing UTF-16LE NUL-terminated strings)>
"Value K"=hex(8):<REG_RESOURCE_LIST (as comma-delimited list of hexadecimal values)>
"Value L"=hex(a):<REG_RESOURCE_REQUIREMENTS_LIST (as comma-delimited list of hexadecimal values)>
"Value M"=hex(b):<REG_QWORD QWORD value (as comma-delimited list of 8 hexadecimal values, in little endian byte order)>
Other notes from the article:
To remove a key (and all subkeys, values and data), the key name must be preceded by a minus sign ("-")
To remove a value (and its data), the values to be removed must have a minus sign ("-") after the equal sign ("=")
The Default Value of a key can be edited by using "@" instead of "Value Name"
Lines beginning with a semicolon are considered comments
BTW: Missing from the article is a note about multiline lists, these use "\" to continue }
begin
if Pos('"', AStr) <> 0 then
Result := vtSz //Value A
else if (Pos('hex:', AStr) <> 0) or
(Pos('hex(3):', AStr) <> 0) then
Result := vtBinary //Value B or G
else if Pos('dword:', AStr) <> 0 then
Result := vtDWord //Value C
else if Pos('hex(0):', AStr) <> 0 then
Result := vtNone //Value D
else if Pos('hex(1):', AStr) <> 0 then
Result := vtSzAsList //Value E
else if Pos('hex(2):', AStr) <> 0 then
Result := vtExpandSz //Value F
else if Pos('hex(4):', AStr) <> 0 then
Result := vtDWordAsList //Value H
else if Pos('hex(7):', AStr) <> 0 then
Result := vtMultiSz //Value J
else if Pos('hex(b):', AStr) <> 0 then
Result := vtQWord //Value M
else if AStr.StartsWith('-') then
Result := vtDelete
else
Result := vtUnsupported;
end;
type
TRegistryEntry = record
Root, Subkey, ValueName, ValueData, ValueType: String;
end;
function RequiresAdminInstallMode(AEntry: TRegistryEntry): Boolean;
begin
Result := (AEntry.Root = 'HKLM') or (AEntry.Root = 'HKCC') or
((AEntry.Root = 'HKU') and SameText(AEntry.Subkey, '.Default'));
end;
function RequiresNotAdminInstallMode(AEntry: TRegistryEntry): Boolean;
begin
Result := (AEntry.Root = 'HKCU');
end;
function TextCommon(AEntry: TRegistryEntry): String;
begin
Result := '';
if FMinVerCheck.Checked and (FMinVerEdit.Text <> '') then
Result := Result + '; MinVersion: ' + FMinVerEdit.Text;
if (FPrivilegesRequired <> prAdmin) and RequiresAdminInstallMode(AEntry) then
Result := Result + '; Check: IsAdminInstallMode'
else if (FPrivilegesRequired <> prLowest) and RequiresNotAdminInstallMode(AEntry) then
Result := Result + '; Check: not IsAdminInstallMode';
end;
function TextKeyEntry(AEntry: TRegistryEntry; ADeleteKey: Boolean): String;
begin
Result := 'Root: ' + AEntry.Root +
'; Subkey: ' + AEntry.Subkey;
if ADeleteKey then
Result := Result + '; ValueType: none' +
'; Flags: deletekey'
else begin
if FUninsDeleteKeyCheck.Checked then
Result := Result + '; Flags: uninsdeletekey'
else if FUninsDeleteKeyIfEmptyCheck.Checked then
Result := Result + '; Flags: uninsdeletekeyifempty';
end;
Result := Result + TextCommon(AEntry);
end;
function TextValueEntry(AEntry: TRegistryEntry; AValueType: TValueType): String;
begin
Result := 'Root: ' + AEntry.Root +
'; Subkey: ' + AEntry.Subkey +
'; ValueType: ' + AEntry.ValueType +
'; ValueName: ' + AEntry.ValueName;
if AValueType = vtDelete then
Result := Result + '; Flags: deletevalue'
else begin
if AValueType <> vtNone then
Result := Result + '; ValueData: ' + AEntry.ValueData;
if FUninsDeleteValueCheck.Checked then
Result := Result + '; Flags: uninsdeletevalue';
end;
Result := Result + TextCommon(AEntry);
end;
function TextHeader: String;
begin
Result := ';Registry data from file ' + ExtractFileName(FFileEdit.Text);
end;
function TextBadHeader: String;
begin
Result := ';COULD NOT IMPORT ' + ExtractFileName(FFileEdit.Text);
end;
function TextFooter(const HadFilteredKeys, HadUnsupportedValueTypes: Boolean): String;
begin
Result := ';End of registry data from file ' + ExtractFileName(FFileEdit.Text);
if HadFilteredKeys then
Result := Result + SNewLine + ';SOME KEYS FILTERED DUE TO PRIVILEGESREQUIRED SETTINGS!';
if HadUnsupportedValueTypes then
Result := Result + SNewLine + ';SOME VALUES WITH UNSUPPORTED TYPES SKIPPED!'
end;
begin
if FFileEdit.Text = '' then
Exit;
var Lines := TStringList.Create;
var OutLines := TStringList.Create;
try
Lines.LoadFromFile(FFileEdit.Text);
{ Official .reg files must have blank lines as second and last lines but we
don't require that so we just check for the header on the first line }
const Header = 'Windows Registry Editor Version 5.00'; { don't localize }
if (Lines.Count = 0) or (Lines[0] <> Header) then begin
if AllowException then
raise Exception.Create('Invalid file format.')
else begin
Registry := Registry + TextBadHeader + SNewLine;
Exit;
end;
end;
var LineIndex := 1;
var HadFilteredKeys := False;
var HadUnsupportedValueTypes := False;
while LineIndex <= Lines.Count-1 do
begin
var Line := Lines[LineIndex];
if (Length(Line) > 2) and (Line[1] = '[') and (Line[Line.Length] = ']') then
begin
{ Got a new section, first handle the key }
Line := CutStrBeginEnd(Line, 1);
var DeleteKey := Line.StartsWith('-');
if DeleteKey then
Delete(Line, 1, 1);
var P := Pos('\', Line);
var Entry: TRegistryEntry;
Entry.Root := StrRootRename(Copy(Line, 1, P - 1));
Entry.Subkey := Copy(Line, P + 1, MaxInt);
if Entry.Root = 'HKCR' then begin
Entry.Root := 'HKA';
Entry.Subkey := 'Software\Classes\' + Entry.Subkey;
end;
Entry.Subkey := Entry.Subkey.Replace('\WOW6432Node', '')
.Replace('{', '{{')
.QuotedString('"');
var FilterKey := ((FPrivilegesRequired = prAdmin) and RequiresNotAdminInstallMode(Entry)) or
((FPrivilegesRequired = prLowest) and RequiresAdminInstallMode(Entry));
if not FilterKey then
OutLines.Add(TextKeyEntry(Entry, DeleteKey))
else
HadFilteredKeys := True;
{ Key done, handle values }
Line := NextLine(Lines, LineIndex);
while Line <> '' do begin
if not FilterKey and not DeleteKey and (Line[1] <> ';') then begin
P := Pos('=', Line);
if (P = 2) and (Line[1] = '@') then
Entry.ValueName := '""'
else begin
Entry.ValueName := CutStrBeginEnd(Copy(Line, 1, P - 1), 1);
Entry.ValueName := Entry.ValueName.Replace('\\', '\')
.Replace('{', '{{')
.QuotedString('"');
end;
var ValueTypeAndData := Copy(Line, P + 1, MaxInt);
var ValueType := GetValueType(ValueTypeAndData);
case ValueType of
vtSz:
begin
Entry.ValueData := CutStrBeginEnd(ValueTypeAndData, 1);
Entry.ValueData := Entry.ValueData.Replace('\\', '\')
.Replace('{', '{{')
.QuotedString('"');
Entry.ValueType := 'string';
end;
vtSzAsList, vtExpandSz, vtMultiSz, vtBinary:
begin
P := Pos(':', ValueTypeAndData);
var ValueData := Copy(ValueTypeAndData, P + 1, MaxInt);
var HasMoreLines := ValueData[ValueData.Length] = '\';
if HasMoreLines then
Delete(ValueData, ValueData.Length, 1);
Entry.ValueData := ValueData;
while HasMoreLines do
begin
ValueData := NextLine(Lines, LineIndex).TrimLeft;
HasMoreLines := ValueData[ValueData.Length] = '\';
if HasMoreLines then
Delete(ValueData, ValueData.Length, 1);
Entry.ValueData := Entry.ValueData + ValueData;
end;
Entry.ValueData := Entry.ValueData.Replace(',', ' ');
if ValueType <> vtBinary then
begin
Entry.ValueData := Entry.ValueData.Replace(' ', '');
Entry.ValueData := UTF16LEHexStrToStr(Entry.ValueData);
end;
if ValueType in [vtSzAsList, vtExpandSz] then
begin
Entry.ValueData := Entry.ValueData.Replace(#0, '');
Entry.ValueType := IfThen(ValueType = vtSzAsList, 'string', 'expandsz');
end else if ValueType = vtMultiSz then
begin
Entry.ValueData := Entry.ValueData.Replace(#0, '{break}');
Entry.ValueType := 'multisz';
end else
Entry.ValueType := 'binary';
Entry.ValueData := Entry.ValueData.QuotedString('"');
end;
vtDWord, vtDWordAsList, vtQWord:
begin
P := Pos(':', ValueTypeAndData);
Entry.ValueData := Copy(ValueTypeAndData, P + 1, MaxInt);
if ValueType in [vtDWordAsList, vtQWord] then
begin
{ ValueData is in reverse order, fix this }
var ReverseValueData := Entry.ValueData.Replace(',', '');
Entry.ValueData := '';
for var I := 0 to ReverseValueData.Length div 2 do
Entry.ValueData := Copy(ReverseValueData, (I * 2) + 1, 2) + Entry.ValueData;
Entry.ValueType := IfThen(ValueType = vtDWordAsList, 'dword', 'qword');
end else
Entry.ValueType := 'dword';
Entry.ValueData := '$' + Entry.ValueData;
end;
vtNone, vtDelete:
begin
Entry.ValueType := 'none';
Entry.ValueData := ''; { value doesn't matter }
end;
end;
if ValueType <> vtUnsupported then
OutLines.Add(TextValueEntry(Entry, ValueType))
else
HadUnsupportedValueTypes := True;
end;
Line := NextLine(Lines, LineIndex); { Go to the next line - should be the next value or a comment }
end; { Out of values }
end;
Inc(LineIndex); { Go to the next line - should be the next key section or a comment }
end;
OutLines.Insert(0, TextHeader);
OutLines.Add(TextFooter(HadFilteredKeys, HadUnsupportedValueTypes));
Registry := Registry + OutLines.Text;
finally
Lines.Free;
OutLines.Free;
end;
end;
end.