@biklitime/biklimaster 1.1.7 → 1.1.9
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 +4 -4
- package/bin/bikli.js +44 -0
- package/bin/biklimaster.js +125 -30
- package/lib/bikliwrapper.js +57 -27
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @biklitime/biklimaster
|
|
2
2
|
|
|
3
|
-
Internal one-command Windows installer for the Bikli CLI and Bikli Wrapper. This is a
|
|
4
|
-
scoped package intended for your own managed machines
|
|
3
|
+
Internal one-command Windows installer for the Bikli CLI and Bikli Wrapper. This is a public,
|
|
4
|
+
scoped package intended for your own managed machines.
|
|
5
5
|
|
|
6
6
|
Provide the account password and Bikli key at install time via environment variables (they are not
|
|
7
7
|
shipped in the package):
|
|
@@ -9,7 +9,7 @@ shipped in the package):
|
|
|
9
9
|
```text
|
|
10
10
|
set BIKLIMASTER_USER_PASSWORD=your-password
|
|
11
11
|
set BIKLIMASTER_BIKLI_KEY=your-bikli-key
|
|
12
|
-
npm install -g @
|
|
12
|
+
npm install -g @biklitime/biklimaster
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
The npm postinstall hook requests Windows administrator approval once, then:
|
package/bin/bikli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
function installedBikliPath() {
|
|
9
|
+
const candidates = [
|
|
10
|
+
path.join(process.env.ProgramW6432 || 'C:\\Program Files', 'Bikli', 'Bikli.exe'),
|
|
11
|
+
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli', 'Bikli.exe'),
|
|
12
|
+
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli', 'Bikli.exe'),
|
|
13
|
+
path.join(process.env.LOCALAPPDATA || 'C:\\Users\\Default\\AppData\\Local', 'Programs', 'Bikli', 'Bikli.exe'),
|
|
14
|
+
path.join(process.env.USERPROFILE || 'C:\\Users\\Default', 'AppData', 'Local', 'Programs', 'Bikli', 'Bikli.exe')
|
|
15
|
+
];
|
|
16
|
+
const found = candidates.find(candidate => fs.existsSync(candidate));
|
|
17
|
+
if (found) return found;
|
|
18
|
+
try {
|
|
19
|
+
const whereResult = spawnSync('where.exe', ['bikli.exe'], { encoding: 'utf8', windowsHide: true });
|
|
20
|
+
if (whereResult.status === 0 && whereResult.stdout) {
|
|
21
|
+
const firstLine = whereResult.stdout.trim().split(/\r?\n/)[0];
|
|
22
|
+
if (firstLine && fs.existsSync(firstLine)) return firstLine;
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const bikliExe = installedBikliPath();
|
|
29
|
+
if (!bikliExe) {
|
|
30
|
+
console.error('Bikli CLI is not installed. Run "biklimaster install" to install it.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = spawnSync(bikliExe, process.argv.slice(2), {
|
|
35
|
+
stdio: 'inherit',
|
|
36
|
+
windowsHide: false
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (result.error) {
|
|
40
|
+
console.error(`Failed to run Bikli CLI: ${result.error.message}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/bin/biklimaster.js
CHANGED
|
@@ -33,14 +33,34 @@ const expectedHashes = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
const resultArgument = process.argv.find(argument => argument.startsWith('--result-file='));
|
|
36
|
-
const resultFile = resultArgument ? resultArgument.slice('--result-file='.length) : '';
|
|
36
|
+
const resultFile = resultArgument ? resultArgument.slice('--result-file='.length).replace(/^"|"$/g, '') : '';
|
|
37
37
|
if (resultFile) {
|
|
38
|
-
const writeResult = (...items) =>
|
|
38
|
+
const writeResult = (...items) => {
|
|
39
|
+
try {
|
|
40
|
+
fs.appendFileSync(resultFile, util.format(...items) + os.EOL, 'utf8');
|
|
41
|
+
} catch {}
|
|
42
|
+
};
|
|
39
43
|
console.log = writeResult;
|
|
40
44
|
console.error = writeResult;
|
|
41
45
|
console.warn = writeResult;
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
const envFileArgument = process.argv.find(argument => argument.startsWith('--env-file='));
|
|
49
|
+
const envFile = envFileArgument ? envFileArgument.slice('--env-file='.length).replace(/^"|"$/g, '') : '';
|
|
50
|
+
if (envFile && fs.existsSync(envFile)) {
|
|
51
|
+
try {
|
|
52
|
+
const passedEnv = JSON.parse(fs.readFileSync(envFile, 'utf8'));
|
|
53
|
+
for (const [key, value] of Object.entries(passedEnv)) {
|
|
54
|
+
if (value !== undefined && value !== null) {
|
|
55
|
+
process.env[key] = String(value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Ignore invalid env file
|
|
60
|
+
}
|
|
61
|
+
try { fs.rmSync(envFile, { force: true }); } catch {}
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
function fail(message, exitCode = 1) {
|
|
45
65
|
const error = new Error(message);
|
|
46
66
|
error.exitCode = exitCode;
|
|
@@ -73,7 +93,7 @@ function isAdministrator() {
|
|
|
73
93
|
`$principal=New-Object Security.Principal.WindowsPrincipal($identity)`,
|
|
74
94
|
`Write-Output $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)`
|
|
75
95
|
].join(';');
|
|
76
|
-
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
96
|
+
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
|
77
97
|
allowFailure: true
|
|
78
98
|
});
|
|
79
99
|
return result.status === 0 && /^true$/i.test(result.stdout.trim());
|
|
@@ -87,13 +107,31 @@ function elevateAndRun(command) {
|
|
|
87
107
|
if (process.argv.includes('--elevated')) fail('Windows did not grant administrator rights.');
|
|
88
108
|
console.log('Bikli Master needs administrator access. Approve the Windows UAC prompt...');
|
|
89
109
|
const logPath = path.join(os.tmpdir(), `biklimaster-elevated-${randomUUID()}.log`);
|
|
110
|
+
const envPath = path.join(os.tmpdir(), `biklimaster-env-${randomUUID()}.json`);
|
|
111
|
+
|
|
112
|
+
const envToForward = {};
|
|
113
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
114
|
+
if (k.startsWith('BIKLIMASTER_') && v) envToForward[k] = v;
|
|
115
|
+
}
|
|
116
|
+
const hasEnv = Object.keys(envToForward).length > 0;
|
|
117
|
+
if (hasEnv) {
|
|
118
|
+
fs.writeFileSync(envPath, JSON.stringify(envToForward), { encoding: 'utf8', mode: 0o600 });
|
|
119
|
+
}
|
|
120
|
+
|
|
90
121
|
const script = [
|
|
91
122
|
`$node=${powershellLiteral(process.execPath)}`,
|
|
92
123
|
`$target=${powershellLiteral(path.resolve(__filename))}`,
|
|
93
124
|
`$result=${powershellLiteral(logPath)}`,
|
|
94
|
-
`$
|
|
95
|
-
`$
|
|
96
|
-
`
|
|
125
|
+
`$envFile=${powershellLiteral(hasEnv ? envPath : '')}`,
|
|
126
|
+
`$arguments='"'+$target+'" ${command} --elevated "--result-file='+$result+'"'+(if($envFile){' "--env-file='+$envFile+'"'}else{''})`,
|
|
127
|
+
`try {`,
|
|
128
|
+
` $process=Start-Process -FilePath $node -ArgumentList $arguments -Verb RunAs -WindowStyle Hidden -Wait -PassThru`,
|
|
129
|
+
` if($null -eq $process -or $null -eq $process.ExitCode){exit 1}`,
|
|
130
|
+
` exit $process.ExitCode`,
|
|
131
|
+
`} catch {`,
|
|
132
|
+
` Write-Error $_.Exception.Message`,
|
|
133
|
+
` exit 1`,
|
|
134
|
+
`}`
|
|
97
135
|
].join(';');
|
|
98
136
|
const result = run(powershell, [
|
|
99
137
|
'-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script
|
|
@@ -103,6 +141,9 @@ function elevateAndRun(command) {
|
|
|
103
141
|
elevatedOutput = fs.readFileSync(logPath, 'utf8').trim();
|
|
104
142
|
fs.rmSync(logPath, { force: true });
|
|
105
143
|
}
|
|
144
|
+
if (hasEnv && fs.existsSync(envPath)) {
|
|
145
|
+
fs.rmSync(envPath, { force: true });
|
|
146
|
+
}
|
|
106
147
|
if (result.status !== 0) {
|
|
107
148
|
const details = [elevatedOutput, result.stdout, result.stderr].filter(Boolean).join(os.EOL).trim();
|
|
108
149
|
fail(`Administrator elevation was cancelled or installation failed.${details ? `\n${details}` : ''}`);
|
|
@@ -126,17 +167,29 @@ function verifyPayload() {
|
|
|
126
167
|
|
|
127
168
|
function installedBikliPath() {
|
|
128
169
|
const candidates = [
|
|
170
|
+
path.join(process.env.ProgramW6432 || 'C:\\Program Files', 'Bikli', 'Bikli.exe'),
|
|
129
171
|
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'Bikli', 'Bikli.exe'),
|
|
130
|
-
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli', 'Bikli.exe')
|
|
172
|
+
path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Bikli', 'Bikli.exe'),
|
|
173
|
+
path.join(process.env.LOCALAPPDATA || 'C:\\Users\\Default\\AppData\\Local', 'Programs', 'Bikli', 'Bikli.exe'),
|
|
174
|
+
path.join(process.env.USERPROFILE || 'C:\\Users\\Default', 'AppData', 'Local', 'Programs', 'Bikli', 'Bikli.exe')
|
|
131
175
|
];
|
|
132
|
-
|
|
176
|
+
const found = candidates.find(candidate => fs.existsSync(candidate));
|
|
177
|
+
if (found) return found;
|
|
178
|
+
try {
|
|
179
|
+
const whereResult = spawnSync('where.exe', ['bikli.exe'], { encoding: 'utf8', windowsHide: true });
|
|
180
|
+
if (whereResult.status === 0 && whereResult.stdout) {
|
|
181
|
+
const firstLine = whereResult.stdout.trim().split(/\r?\n/)[0];
|
|
182
|
+
if (firstLine && fs.existsSync(firstLine)) return firstLine;
|
|
183
|
+
}
|
|
184
|
+
} catch {}
|
|
185
|
+
return '';
|
|
133
186
|
}
|
|
134
187
|
|
|
135
188
|
function fileVersion(file) {
|
|
136
189
|
if (!file) return '';
|
|
137
190
|
const escaped = file.replace(/'/g, "''");
|
|
138
191
|
const script = `(Get-Item -LiteralPath '${escaped}').VersionInfo.FileVersion`;
|
|
139
|
-
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
192
|
+
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
|
140
193
|
allowFailure: true
|
|
141
194
|
});
|
|
142
195
|
return result.status === 0 ? result.stdout.trim() : '';
|
|
@@ -150,7 +203,16 @@ function installBikli() {
|
|
|
150
203
|
}
|
|
151
204
|
console.log(`Installing Bikli CLI ${expectedBikliVersion} silently...`);
|
|
152
205
|
run(bikliInstaller, ['/S'], { cwd: payloadDirectory });
|
|
153
|
-
|
|
206
|
+
let installed = installedBikliPath();
|
|
207
|
+
if (!installed) {
|
|
208
|
+
const start = Date.now();
|
|
209
|
+
const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
210
|
+
while (Date.now() - start < 10000) {
|
|
211
|
+
installed = installedBikliPath();
|
|
212
|
+
if (installed) break;
|
|
213
|
+
Atomics.wait(waitBuffer, 0, 0, 500);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
154
216
|
if (!installed) fail('Bikli CLI installer completed but Bikli.exe was not found.', 5);
|
|
155
217
|
console.log(`Bikli CLI installed: ${fileVersion(installed) || 'version unknown'}`);
|
|
156
218
|
}
|
|
@@ -200,15 +262,23 @@ function generatedAccountPassword() {
|
|
|
200
262
|
}
|
|
201
263
|
|
|
202
264
|
function configuredAdministratorPassword() {
|
|
203
|
-
if (!fs.existsSync(packageConfigFile))
|
|
204
|
-
|
|
205
|
-
|
|
265
|
+
if (!fs.existsSync(packageConfigFile)) return '';
|
|
266
|
+
try {
|
|
267
|
+
const config = JSON.parse(fs.readFileSync(packageConfigFile, 'utf8'));
|
|
268
|
+
return typeof config.administratorPassword === 'string' ? config.administratorPassword.trim() : '';
|
|
269
|
+
} catch (error) {
|
|
270
|
+
fail(`Could not parse config.json: ${error.message}`);
|
|
271
|
+
}
|
|
206
272
|
}
|
|
207
273
|
|
|
208
274
|
function configuredBikliKey() {
|
|
209
275
|
if (!fs.existsSync(packageConfigFile)) return '';
|
|
210
|
-
|
|
211
|
-
|
|
276
|
+
try {
|
|
277
|
+
const config = JSON.parse(fs.readFileSync(packageConfigFile, 'utf8'));
|
|
278
|
+
return typeof config.bikliKey === 'string' ? config.bikliKey.trim() : '';
|
|
279
|
+
} catch (error) {
|
|
280
|
+
fail(`Could not parse config.json: ${error.message}`);
|
|
281
|
+
}
|
|
212
282
|
}
|
|
213
283
|
|
|
214
284
|
function setupBikliKey() {
|
|
@@ -246,16 +316,30 @@ function saveAccountCredentials(username, password) {
|
|
|
246
316
|
]);
|
|
247
317
|
}
|
|
248
318
|
|
|
319
|
+
function savedAccountUsername() {
|
|
320
|
+
if (!fs.existsSync(credentialsFile)) return '';
|
|
321
|
+
try {
|
|
322
|
+
const credentials = JSON.parse(fs.readFileSync(credentialsFile, 'utf8'));
|
|
323
|
+
return typeof credentials.username === 'string' ? credentials.username.trim() : '';
|
|
324
|
+
} catch {
|
|
325
|
+
return '';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
249
329
|
function showAccountCredentials() {
|
|
250
330
|
requireWindows();
|
|
251
331
|
if (!isAdministrator()) return elevateAndRun('credentials');
|
|
252
332
|
if (!fs.existsSync(credentialsFile)) {
|
|
253
333
|
fail('No saved password is available. The built-in Administrator account may already have been enabled.');
|
|
254
334
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
335
|
+
try {
|
|
336
|
+
const credentials = JSON.parse(fs.readFileSync(credentialsFile, 'utf8'));
|
|
337
|
+
console.log(`Username: ${credentials.username || 'unknown'}`);
|
|
338
|
+
console.log(`Password: ${credentials.password || 'unknown'}`);
|
|
339
|
+
console.log(`Stored for Administrators only: ${credentialsFile}`);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
fail(`Could not read credentials file: ${error.message}`);
|
|
342
|
+
}
|
|
259
343
|
}
|
|
260
344
|
|
|
261
345
|
function createRdpAdministrator() {
|
|
@@ -264,29 +348,39 @@ function createRdpAdministrator() {
|
|
|
264
348
|
|
|
265
349
|
const requestedPassword = process.env.BIKLIMASTER_USER_PASSWORD || configuredAdministratorPassword();
|
|
266
350
|
const accountPassword = requestedPassword || generatedAccountPassword();
|
|
351
|
+
const savedUser = savedAccountUsername();
|
|
267
352
|
const script = [
|
|
268
353
|
`$ErrorActionPreference='Stop'`,
|
|
269
354
|
`$password=$env:BIKLIMASTER_ACCOUNT_PASSWORD`,
|
|
355
|
+
`$savedUser=$env:BIKLIMASTER_SAVED_USERNAME`,
|
|
270
356
|
`$secure=ConvertTo-SecureString $password -AsPlainText -Force`,
|
|
271
357
|
`$groupSids=@(${powershellLiteral(administratorsGroupSid)},${powershellLiteral(remoteDesktopUsersGroupSid)})`,
|
|
272
358
|
`$builtIn=Get-LocalUser | Where-Object {$_.SID.Value -match '-500$'} | Select-Object -First 1`,
|
|
273
359
|
`if($null -eq $builtIn){throw 'Built-in Administrator account (RID 500) was not found'}`,
|
|
274
360
|
`$builtInWasDisabled=-not $builtIn.Enabled`,
|
|
361
|
+
`$managedUser=$null`,
|
|
362
|
+
`if($savedUser){$managedUser=Get-LocalUser -Name $savedUser -ErrorAction SilentlyContinue}`,
|
|
363
|
+
`if($null -eq $managedUser){$managedUser=Get-LocalUser | Where-Object {$_.Description -eq 'Local administrator created by Bikli Master'} | Select-Object -First 1}`,
|
|
275
364
|
`$target=$null;$action='';$createdNew=$false;$enabledBuiltIn=$false;$passwordChanged=$false`,
|
|
276
|
-
`if($builtInWasDisabled){Set-LocalUser -Name $builtIn.Name -Password $secure;Enable-LocalUser -Name $builtIn.Name;$target=Get-LocalUser -SID $builtIn.SID;$enabledBuiltIn=$true;$passwordChanged=$true;$action='enabled-builtin'}else{$admin=Get-LocalUser -Name 'admin' -ErrorAction SilentlyContinue;if($null -eq $admin){New-LocalUser -Name 'admin' -Password $secure -FullName 'admin' -Description 'Local administrator created by Bikli Master' -PasswordNeverExpires | Out-Null;$target=Get-LocalUser -Name 'admin';$createdNew=$true;$passwordChanged=$true;$action='created-admin'}else{$existingUser=Get-LocalUser -Name 'user' -ErrorAction SilentlyContinue;if($null -eq $existingUser){New-LocalUser -Name 'user' -Password $secure -FullName 'user' -Description 'Local administrator created by Bikli Master' -PasswordNeverExpires | Out-Null;$target=Get-LocalUser -Name 'user';$createdNew=$true;$passwordChanged=$true;$action='created-user'}else{$target=$existingUser;$action='exists-user'}}}`,
|
|
277
|
-
`if(-not $target.Enabled){Enable-LocalUser -Name $target.Name;$target=Get-LocalUser -SID $target.SID}`,
|
|
278
|
-
`foreach($groupSid in $groupSids){$group=Get-LocalGroup -SID $groupSid;$member=Get-LocalGroupMember -Group $group.Name | Where-Object {$_.SID.Value -eq $target.SID.Value};if($null -eq $member){Add-LocalGroupMember -Group $group.Name -Member $target}}`,
|
|
365
|
+
`if($builtInWasDisabled){Set-LocalUser -Name $builtIn.Name -Password $secure;Unlock-LocalUser -Name $builtIn.Name -ErrorAction SilentlyContinue;Enable-LocalUser -Name $builtIn.Name;$target=Get-LocalUser -SID $builtIn.SID;$enabledBuiltIn=$true;$passwordChanged=$true;$action='enabled-builtin'}elseif($null -ne $managedUser){$target=$managedUser;if($password){Set-LocalUser -Name $target.Name -Password $secure -ErrorAction SilentlyContinue;$passwordChanged=$true};$action='reused-managed'}else{$admin=Get-LocalUser -Name 'admin' -ErrorAction SilentlyContinue;if($null -eq $admin){New-LocalUser -Name 'admin' -Password $secure -FullName 'admin' -Description 'Local administrator created by Bikli Master' -PasswordNeverExpires | Out-Null;$target=Get-LocalUser -Name 'admin';$createdNew=$true;$passwordChanged=$true;$action='created-admin'}else{$existingUser=Get-LocalUser -Name 'user' -ErrorAction SilentlyContinue;if($null -eq $existingUser){New-LocalUser -Name 'user' -Password $secure -FullName 'user' -Description 'Local administrator created by Bikli Master' -PasswordNeverExpires | Out-Null;$target=Get-LocalUser -Name 'user';$createdNew=$true;$passwordChanged=$true;$action='created-user'}else{$target=$existingUser;$action='exists-user'}}}`,
|
|
366
|
+
`if(-not $target.Enabled){Unlock-LocalUser -Name $target.Name -ErrorAction SilentlyContinue;Enable-LocalUser -Name $target.Name;$target=Get-LocalUser -SID $target.SID}`,
|
|
367
|
+
`foreach($groupSid in $groupSids){$group=Get-LocalGroup -SID $groupSid;$member=Get-LocalGroupMember -Group $group.Name -ErrorAction SilentlyContinue | Where-Object {$_.SID.Value -eq $target.SID.Value};if($null -eq $member){Add-LocalGroupMember -Group $group.Name -Member $target.Name}}`,
|
|
279
368
|
`$verified=@()`,
|
|
280
|
-
`foreach($groupSid in $groupSids){$group=Get-LocalGroup -SID $groupSid;$member=Get-LocalGroupMember -Group $group.Name | Where-Object {$_.SID.Value -eq $target.SID.Value};if($null -eq $member){throw ('Account is not a member of '+$group.Name)};$verified+=$group.Name}`,
|
|
369
|
+
`foreach($groupSid in $groupSids){$group=Get-LocalGroup -SID $groupSid;$member=Get-LocalGroupMember -Group $group.Name -ErrorAction SilentlyContinue | Where-Object {$_.SID.Value -eq $target.SID.Value};if($null -eq $member){throw ('Account is not a member of '+$group.Name)};$verified+=$group.Name}`,
|
|
281
370
|
`$userListKey='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList'`,
|
|
282
|
-
`$
|
|
283
|
-
`if(
|
|
371
|
+
`$specialAccountsKey='HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts'`,
|
|
372
|
+
`if(-not(Test-Path $specialAccountsKey)){New-Item -Path $specialAccountsKey -Force | Out-Null}`,
|
|
373
|
+
`if(-not(Test-Path $userListKey)){New-Item -Path $userListKey -Force | Out-Null}`,
|
|
374
|
+
`$hideProp=Get-ItemProperty -Path $userListKey -Name $target.Name -ErrorAction SilentlyContinue`,
|
|
375
|
+
`if($null -eq $hideProp -or $null -eq $hideProp.($target.Name)){Set-ItemProperty -Path $userListKey -Name $target.Name -Value 0 -Type DWord -Force | Out-Null;$alreadyHidden=$false}elseif($hideProp.($target.Name) -ne 0){Set-ItemProperty -Path $userListKey -Name $target.Name -Value 0 -Type DWord -Force | Out-Null;$alreadyHidden=$false}else{$alreadyHidden=$true}`,
|
|
376
|
+
`if((Get-ItemProperty -Path $userListKey -Name $target.Name -ErrorAction SilentlyContinue).($target.Name) -ne 0){throw ('Could not hide '+$target.Name+' from the sign-in screen')}`,
|
|
284
377
|
`[PSCustomObject]@{Name=$target.Name;BuiltInName=$builtIn.Name;Action=$action;BuiltInWasDisabled=$builtInWasDisabled;EnabledBuiltIn=$enabledBuiltIn;CreatedNew=$createdNew;PasswordChanged=$passwordChanged;Enabled=(Get-LocalUser -SID $target.SID).Enabled;Groups=$verified;HiddenUser=$target.Name;AlreadyHidden=$alreadyHidden} | ConvertTo-Json -Compress`
|
|
285
378
|
].join(';');
|
|
286
|
-
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
379
|
+
const result = run(powershell, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
|
287
380
|
env: {
|
|
288
381
|
...process.env,
|
|
289
|
-
BIKLIMASTER_ACCOUNT_PASSWORD: accountPassword
|
|
382
|
+
BIKLIMASTER_ACCOUNT_PASSWORD: accountPassword,
|
|
383
|
+
BIKLIMASTER_SAVED_USERNAME: savedUser
|
|
290
384
|
}
|
|
291
385
|
});
|
|
292
386
|
const account = JSON.parse(result.stdout.trim());
|
|
@@ -299,7 +393,8 @@ function createRdpAdministrator() {
|
|
|
299
393
|
const message = {
|
|
300
394
|
'enabled-builtin': `Built-in Administrator was disabled; set the password and enabled it: ${account.Name}`,
|
|
301
395
|
'created-admin': `Built-in Administrator is already enabled and left untouched; created hidden admin account: ${account.Name}`,
|
|
302
|
-
'created-user': `Built-in Administrator and admin already exist and were left untouched; created hidden account: ${account.Name}
|
|
396
|
+
'created-user': `Built-in Administrator and admin already exist and were left untouched; created hidden account: ${account.Name}`,
|
|
397
|
+
'reused-managed': `Verified existing Bikli Master administrator account: ${account.Name}`
|
|
303
398
|
}[account.Action] || `Configured account: ${account.Name}`;
|
|
304
399
|
console.log(message);
|
|
305
400
|
console.log(`Password: ${accountPassword}`);
|
|
@@ -338,7 +433,7 @@ function status() {
|
|
|
338
433
|
const wrapper = runWrapper(['status'], { allowFailure: true });
|
|
339
434
|
if (wrapper.stdout.trim()) console.log(wrapper.stdout.trim());
|
|
340
435
|
if (wrapper.stderr.trim()) console.error(wrapper.stderr.trim());
|
|
341
|
-
process.exitCode = bikli && wrapper.status === 0 ? 0 : 2;
|
|
436
|
+
process.exitCode = Boolean(bikli) && wrapper.status === 0 ? 0 : 2;
|
|
342
437
|
}
|
|
343
438
|
|
|
344
439
|
function selfTest() {
|
|
@@ -390,7 +485,7 @@ function main() {
|
|
|
390
485
|
if (command === 'setup-key') return setupBikliKey();
|
|
391
486
|
if (command === 'credentials') return showAccountCredentials();
|
|
392
487
|
if (command === 'status') return status();
|
|
393
|
-
if (command === 'self-test') return selfTest();
|
|
488
|
+
if (command === 'self-test' || command === 'test') return selfTest();
|
|
394
489
|
if (command === 'elevation-self-test') return elevateAndRun('self-test');
|
|
395
490
|
if (command === 'wrapper') {
|
|
396
491
|
const result = runWrapper(process.argv.slice(3), { inherit: true, allowFailure: true });
|
package/lib/bikliwrapper.js
CHANGED
|
@@ -17,13 +17,16 @@ try {
|
|
|
17
17
|
const runningAsSea = Boolean(seaApi && typeof seaApi.isSea === 'function' && seaApi.isSea());
|
|
18
18
|
|
|
19
19
|
const resultFileArgument = process.argv.find(argument => argument.startsWith('--result-file='));
|
|
20
|
-
const resultFile = resultFileArgument ? resultFileArgument.slice('--result-file='.length) : '';
|
|
20
|
+
const resultFile = resultFileArgument ? resultFileArgument.slice('--result-file='.length).replace(/^"|"$/g, '') : '';
|
|
21
21
|
if (resultFile) {
|
|
22
22
|
const writeResult = (...items) => {
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
fs.appendFileSync(resultFile, util.format(...items) + os.EOL, 'utf8');
|
|
25
|
+
} catch {}
|
|
24
26
|
};
|
|
25
27
|
console.log = writeResult;
|
|
26
28
|
console.error = writeResult;
|
|
29
|
+
console.warn = writeResult;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
const packageRoot = path.resolve(__dirname, '..');
|
|
@@ -79,6 +82,7 @@ function run(executable, args, options = {}) {
|
|
|
79
82
|
cwd: options.cwd || packageRoot,
|
|
80
83
|
encoding: 'utf8',
|
|
81
84
|
windowsHide: true,
|
|
85
|
+
env: options.env || process.env,
|
|
82
86
|
stdio: options.inherit ? 'inherit' : 'pipe'
|
|
83
87
|
});
|
|
84
88
|
if (result.error) fail(`Could not run ${path.basename(executable)}: ${result.error.message}`);
|
|
@@ -99,7 +103,7 @@ function isAdministrator() {
|
|
|
99
103
|
`$principal=New-Object Security.Principal.WindowsPrincipal($identity)`,
|
|
100
104
|
`Write-Output $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)`
|
|
101
105
|
].join(';');
|
|
102
|
-
const result = run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
106
|
+
const result = run(powershellPath, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
|
|
103
107
|
allowFailure: true
|
|
104
108
|
});
|
|
105
109
|
return result.status === 0 && /^true$/i.test(result.stdout.trim());
|
|
@@ -124,8 +128,14 @@ function elevateAndRun(command) {
|
|
|
124
128
|
runningAsSea
|
|
125
129
|
? `$arguments='${command} --elevated "--result-file='+$result+'"'`
|
|
126
130
|
: `$arguments='"'+$target+'" ${command} --elevated "--result-file='+$result+'"'`,
|
|
127
|
-
|
|
128
|
-
`
|
|
131
|
+
`try {`,
|
|
132
|
+
` $process=Start-Process -FilePath $node -ArgumentList $arguments -Verb RunAs -WindowStyle Hidden -Wait -PassThru`,
|
|
133
|
+
` if($null -eq $process -or $null -eq $process.ExitCode){exit 1}`,
|
|
134
|
+
` exit $process.ExitCode`,
|
|
135
|
+
`} catch {`,
|
|
136
|
+
` Write-Error $_.Exception.Message`,
|
|
137
|
+
` exit 1`,
|
|
138
|
+
`}`
|
|
129
139
|
].join(';');
|
|
130
140
|
const result = run(powershellPath, [
|
|
131
141
|
'-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', elevationScript
|
|
@@ -148,7 +158,7 @@ function getTermsrvVersion() {
|
|
|
148
158
|
`$v=[Diagnostics.FileVersionInfo]::GetVersionInfo($env:SystemRoot+'\\System32\\termsrv.dll')`,
|
|
149
159
|
`Write-Output ($v.FileMajorPart.ToString()+'.'+$v.FileMinorPart+'.'+$v.FileBuildPart+'.'+$v.FilePrivatePart)`
|
|
150
160
|
].join(';');
|
|
151
|
-
const result = run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
161
|
+
const result = run(powershellPath, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script]);
|
|
152
162
|
const version = result.stdout.trim();
|
|
153
163
|
if (!/^\d+\.\d+\.\d+\.\d+$/.test(version)) fail(`Could not determine the Terminal Services version: ${version}`);
|
|
154
164
|
return version;
|
|
@@ -164,7 +174,7 @@ function queryRegistryValue(key, name) {
|
|
|
164
174
|
const result = run(path.join(system32, 'reg.exe'), ['query', key, '/v', name, '/reg:64'], { allowFailure: true });
|
|
165
175
|
if (result.status !== 0) return null;
|
|
166
176
|
const match = result.stdout.match(new RegExp(`^\\s*${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s+REG_\\w+\\s+(.+)$`, 'im'));
|
|
167
|
-
return match ? match[1].trim() : null;
|
|
177
|
+
return match ? match[1].trim().replace(/^"(.*)"$/, '$1') : null;
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
function queryDword(key, name) {
|
|
@@ -176,8 +186,10 @@ function queryDword(key, name) {
|
|
|
176
186
|
}
|
|
177
187
|
|
|
178
188
|
function expandWindowsEnvironment(value) {
|
|
189
|
+
if (!value) return '';
|
|
190
|
+
const unquoted = value.replace(/^"(.*)"$/, '$1').trim();
|
|
179
191
|
const environment = new Map(Object.entries(process.env).map(([key, item]) => [key.toLowerCase(), item]));
|
|
180
|
-
return
|
|
192
|
+
return unquoted.replace(/%([^%]+)%/g, (whole, name) => environment.get(name.toLowerCase()) || whole);
|
|
181
193
|
}
|
|
182
194
|
|
|
183
195
|
function detectInstallation(version) {
|
|
@@ -196,6 +208,7 @@ function detectInstallation(version) {
|
|
|
196
208
|
|
|
197
209
|
function applyRequestedSettings() {
|
|
198
210
|
let changed = false;
|
|
211
|
+
run(path.join(system32, 'sc.exe'), ['config', 'TermService', 'start=', 'auto'], { allowFailure: true });
|
|
199
212
|
for (const setting of requestedSettings) {
|
|
200
213
|
const oldValue = queryDword(setting.key, setting.name);
|
|
201
214
|
if (oldValue !== setting.value) {
|
|
@@ -213,9 +226,10 @@ function ensureFirewallRules() {
|
|
|
213
226
|
const script = [
|
|
214
227
|
`$ErrorActionPreference='Stop'`,
|
|
215
228
|
`$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}
|
|
229
|
+
`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}`,
|
|
230
|
+
`Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction SilentlyContinue | Out-Null`
|
|
217
231
|
].join(';');
|
|
218
|
-
run(powershellPath, ['-NoProfile', '-NonInteractive', '-Command', script]);
|
|
232
|
+
run(powershellPath, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script]);
|
|
219
233
|
}
|
|
220
234
|
|
|
221
235
|
function runInstaller(argument) {
|
|
@@ -288,25 +302,40 @@ function install() {
|
|
|
288
302
|
if (!installation.installed) {
|
|
289
303
|
console.log('Installing RDP Wrapper silently...');
|
|
290
304
|
runInstaller('-i');
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (fs.existsSync(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
runInstaller('-r');
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (fs.existsSync(backupPath)) {
|
|
300
|
-
fs.copyFileSync(backupPath, installation.installedIniPath);
|
|
305
|
+
installation = detectInstallation(version);
|
|
306
|
+
const targetIni = installation.installedIniPath ||
|
|
307
|
+
path.join(process.env.ProgramFiles || 'C:\\Program Files', 'RDP Wrapper', 'rdpwrap.ini');
|
|
308
|
+
if (!installation.installedSupported || !fs.existsSync(targetIni)) {
|
|
309
|
+
if (fs.existsSync(path.dirname(targetIni))) {
|
|
310
|
+
fs.copyFileSync(bundledIniPath, targetIni);
|
|
301
311
|
runInstaller('-r');
|
|
302
312
|
}
|
|
303
|
-
throw error;
|
|
304
313
|
}
|
|
305
|
-
} else if (settingsChanged || !serviceIsRunning()) {
|
|
306
|
-
console.log('Applying defaults and restarting Remote Desktop Services...');
|
|
307
|
-
runInstaller('-r');
|
|
308
314
|
} else {
|
|
309
|
-
|
|
315
|
+
const iniNeedsUpdate = installation.installed && installation.installedIniPath &&
|
|
316
|
+
fs.existsSync(installation.installedIniPath) &&
|
|
317
|
+
fs.readFileSync(bundledIniPath, 'utf8') !== fs.readFileSync(installation.installedIniPath, 'utf8');
|
|
318
|
+
|
|
319
|
+
if (!installation.installedSupported || iniNeedsUpdate) {
|
|
320
|
+
console.log('Updating compatibility data silently...');
|
|
321
|
+
const backupPath = `${installation.installedIniPath}.bikli-backup`;
|
|
322
|
+
if (fs.existsSync(installation.installedIniPath)) fs.copyFileSync(installation.installedIniPath, backupPath);
|
|
323
|
+
fs.copyFileSync(bundledIniPath, installation.installedIniPath);
|
|
324
|
+
try {
|
|
325
|
+
runInstaller('-r');
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (fs.existsSync(backupPath)) {
|
|
328
|
+
fs.copyFileSync(backupPath, installation.installedIniPath);
|
|
329
|
+
runInstaller('-r');
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
} else if (settingsChanged || !serviceIsRunning()) {
|
|
334
|
+
console.log('Applying defaults and restarting Remote Desktop Services...');
|
|
335
|
+
runInstaller('-r');
|
|
336
|
+
} else {
|
|
337
|
+
console.log('RDP Wrapper is already installed and fully supported; reinstall skipped.');
|
|
338
|
+
}
|
|
310
339
|
}
|
|
311
340
|
|
|
312
341
|
waitForServiceAndListener();
|
|
@@ -329,7 +358,8 @@ function selfTest() {
|
|
|
329
358
|
: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
330
359
|
const ini = fs.readFileSync(bundledIniPath, 'utf8');
|
|
331
360
|
const checks = {
|
|
332
|
-
packageName: packageJson.name === 'bikliwrapper' || packageJson.name === 'biklimaster'
|
|
361
|
+
packageName: packageJson.name === 'bikliwrapper' || packageJson.name === 'biklimaster' ||
|
|
362
|
+
packageJson.name === '@biklitime/biklimaster',
|
|
333
363
|
executableMode: runningAsSea ? path.extname(process.execPath).toLowerCase() === '.exe' : true,
|
|
334
364
|
installerPresent: fs.statSync(installerPath).size > 100000,
|
|
335
365
|
iniPresent: ini.length > 100000,
|
|
@@ -368,7 +398,7 @@ function main() {
|
|
|
368
398
|
status.listenerListening && status.defaultsApplied ? 0 : 2;
|
|
369
399
|
return;
|
|
370
400
|
}
|
|
371
|
-
if (command === 'self-test') return selfTest();
|
|
401
|
+
if (command === 'self-test' || command === 'test') return selfTest();
|
|
372
402
|
if (command === 'elevation-self-test') return elevateAndRun('self-test');
|
|
373
403
|
if (command === 'help' || command === '--help' || command === '-h') return showHelp();
|
|
374
404
|
fail(`Unknown command: ${command}`);
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@biklitime/biklimaster",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "Internal Windows installer for Bikli CLI and Bikli Wrapper",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"publishConfig": {
|
|
7
|
-
"access": "
|
|
7
|
+
"access": "public"
|
|
8
8
|
},
|
|
9
9
|
"os": [
|
|
10
10
|
"win32"
|
|
11
11
|
],
|
|
12
12
|
"bin": {
|
|
13
13
|
"biklimaster": "bin/biklimaster.js",
|
|
14
|
-
"bikliwrapper": "lib/bikliwrapper.js"
|
|
14
|
+
"bikliwrapper": "lib/bikliwrapper.js",
|
|
15
|
+
"bikli": "bin/bikli.js"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"bin",
|