()
| 808 | }); |
| 809 | |
| 810 | const makeWindowsBackend = (): ServiceBackend => { |
| 811 | const controlDir = (path: Path.Path): string => |
| 812 | path.join(resolveExecutorDataDir(path), "server-control"); |
| 813 | const taskXmlPath = (path: Path.Path): string => path.join(controlDir(path), "run-daemon.xml"); |
| 814 | const wrapperCmdPath = (path: Path.Path): string => path.join(controlDir(path), "run-daemon.cmd"); |
| 815 | const launcherVbsPath = (path: Path.Path): string => |
| 816 | path.join(controlDir(path), "run-daemon.vbs"); |
| 817 | |
| 818 | return { |
| 819 | platform: "win32", |
| 820 | automated: true, |
| 821 | install: (descriptor) => |
| 822 | Effect.gen(function* () { |
| 823 | const fs = yield* FileSystem.FileSystem; |
| 824 | const path = yield* Path.Path; |
| 825 | const dataDir = resolveExecutorDataDir(path); |
| 826 | const logs = serviceLogDir(path); |
| 827 | const control = path.join(dataDir, "server-control"); |
| 828 | yield* fs.makeDirectory(logs, { recursive: true }); |
| 829 | yield* fs.makeDirectory(control, { recursive: true }); |
| 830 | |
| 831 | const wrapperPath = wrapperCmdPath(path); |
| 832 | yield* fs.writeFileString( |
| 833 | wrapperPath, |
| 834 | generateWindowsDaemonWrapper(descriptor, dataDir, logs), |
| 835 | ); |
| 836 | |
| 837 | // Launch the wrapper through a hidden wscript shim so the logon task |
| 838 | // doesn't flash a console window on the interactive desktop each login. |
| 839 | const vbsPath = launcherVbsPath(path); |
| 840 | yield* fs.writeFileString(vbsPath, generateWindowsHiddenLauncherVbs(wrapperPath)); |
| 841 | |
| 842 | const xmlPath = taskXmlPath(path); |
| 843 | yield* writeUtf16File( |
| 844 | xmlPath, |
| 845 | generateWindowsTaskXml({ |
| 846 | command: "wscript.exe", |
| 847 | arguments: `"${vbsPath}"`, |
| 848 | userId: windowsTaskUserId(), |
| 849 | boot: descriptor.boot ?? false, |
| 850 | }), |
| 851 | ); |
| 852 | |
| 853 | const create = yield* runSchtasks([ |
| 854 | "/create", |
| 855 | "/tn", |
| 856 | WINDOWS_TASK_NAME, |
| 857 | "/xml", |
| 858 | xmlPath, |
| 859 | "/f", |
| 860 | ]); |
| 861 | if (create.code !== 0) { |
| 862 | // schtasks ends its messages with a period; drop it so the assembled |
| 863 | // sentence doesn't read "denied.. <hint>". |
| 864 | const detail = (create.stderr.trim() || create.stdout.trim()).replace(/\.\s*$/, ""); |
| 865 | // The default (logon) task needs no elevation. Only the --boot path |
| 866 | // registers a boot/S4U task, which Task Scheduler refuses without |
| 867 | // Administrator — so point access-denial at the relevant fix. |
no test coverage detected