如何使用 Nodejs 和 spawn 在 Windows 10 上以管理员身份运行一些 CMD 命令?

Che*_*uCR 5 windows command spawn node.js elevated-privileges

我在 Windows 10 上使用 Nodejs。每次打开应用程序时,我都想检查带有扩展名的文件ext是否与我的应用程序相关联。如果不是,我将需要在 cmd 上运行此命令以再次建立关联:

assoc .ext=CustomExt
ftype CustomExt=app.exe %1 %*
Run Code Online (Sandbox Code Playgroud)

但是我需要管理员权限才能完成这项工作。

我读过这个帖子,Jeff M 准确地说出了我想要实现的目标,但没有人回答他:

从可用性的角度来看,理想的情况是,只有当用户在我的节点程序中执行实际需要这些权限的操作时,才会提示用户提升权限。在第二个最好的选择是,如果用户被提示输入提升的权限时,他们首先启动节点计划。最不理想的是,如果根本没有提示用户,则由用户始终记住通过右键单击以管理员身份运行来启动程序。这是最不理想的,因为它会给用户带来负担,要记住始终以非典型方式启动程序。

因此,回到我原来的问题,现在如果我尝试生成一个子进程,其中生成的程序需要提升的权限(原始消息中的示例代码),当前的行为是不会提示用户输入这些权限,而是节点程序因错误而崩溃。我怎样才能让我的节点程序请求这些特权而不是死掉?

我的 nodejs 脚本:

const { spawn } = require('child_process');

const assoc = spawn(                    // how to run this as admin privileges?
    'assoc',                            // command
    ['.ext=CustomExt'],                 // args
    {'shell': true }                    // options: run this into the shell
);

assoc.stdout.on('data', (buffer) => {
    var message = buffer.toString('utf8');
    console.log(message);
});

assoc.stderr.on('data', (data) => {
    console.log(`stderr: ${data}`);
});

assoc.on('close', (code) => {
    console.log(`child process exited with code ${code}`);
});
Run Code Online (Sandbox Code Playgroud)

在 Nodejs 中创建扩展关联的任何其他方式也将受到赞赏。

2020 年 1 月 21 日更新

我相信 VSCode 可以做到这一点。所以我需要探索它的源代码来找到解决方案。现在在做其他项目,暂时没时间研究。当应该使用 root 权限保存文件时,会提示这样的消息,这正是我所需要的:

vscode 提示

2021 年 6 月 24 日更新

在 VSCode 问题中找到了一个有用的线程。使用sudo-prompt可能是解决方案。我还没有尝试过,但是依赖项仍然在 中package.json,所以它看起来很有希望。

Jac*_*rry 2

新的和改进的答案

有关背景,请参阅旧答案中的注释。我找到了一种无需允许在用户计算机上运行脚本即可实现此目的的方法:

import Shell from 'node-powershell'

/** Options to use when running an executable as admin */
interface RunAsAdminCommand {
  /** Path to the executable to run */
  path: string
  /** Working dir to run the executable from */
  workingDir?: string
}
/**
 * Runs a PowerShell command or an executable as admin
 *
 * @param command If a string is provided, it will be used as a command to
 *   execute in an elevated PowerShell. If an object with `path` is provided,
 *   the executable will be started in Run As Admin mode
 *
 * If providing a string for elevated PowerShell, ensure the command is parsed
 *   by PowerShell correctly by using an interpolated string and wrap the
 *   command in double quotes.
 *
 * Example:
 *
 * ```
 * `"Do-The-Thing -Param '${pathToFile}'"`
 * ```
 */
const runAsAdmin = async (command: string | RunAsAdminCommand): Promise<string> => {
  const usePowerShell = typeof command === 'string'
  const shell = new Shell({})
  await shell.addCommand('Start-Process')
  if (usePowerShell) await shell.addArgument('PowerShell')
  // Elevate the process
  await shell.addArgument('-Verb')
  await shell.addArgument('RunAs')
  // Hide the window for cleaner UX
  await shell.addArgument('-WindowStyle')
  await shell.addArgument('Hidden')
  // Propagate output from child process
  await shell.addArgument('-PassThru')
  // Wait for the child process to finish before exiting
  if (usePowerShell) await shell.addArgument('-Wait')

  if (usePowerShell) {
    // Pass argument list to use in elevated PowerShell
    await shell.addArgument('-ArgumentList')
    await shell.addArgument(command as string)
  } else {
    // Point to executable to run
    await shell.addArgument('-FilePath')
    await shell.addArgument(`'${(command as RunAsAdminCommand).path}'`)

    if ((command as RunAsAdminCommand).workingDir) {
      // Point to working directory to run the executable from
      await shell.addArgument('-WorkingDirectory')
      await shell.addArgument(`'${(command as RunAsAdminCommand).workingDir}'`)
    }
  }

  await shell.invoke()
  return await shell.dispose()
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

const unzip = async (
  zipPath: string,
  destinationPath: string
): Promise<string> =>
  await runAsAdmin(
    `"Expand-Archive -Path '${zipPath}' -DestinationPath '${destinationPath}'"`
  )
Run Code Online (Sandbox Code Playgroud)

旧答案

我不是安全专家,如果您看到这里发生了愚蠢的事情,请挥舞红旗。

Start-Process您可以在子进程中使用 PowerShell 的cmdlet。您需要包含-Verb RunAs它才能提升提示。这将提示用户允许在调用 cmdlet 时以管理员身份运行该进程。node-powershell如果需要,您可以通过使用来简化事情。

以下是具有适用标志的 cmdlet 示例(只需根据需要替换[PATH_TO_EXECUTABLE]和):[PATH_TO_DIR_TO_RUN_FROM]

Start-Process -FilePath [PATH_TO_EXECUTABLE] -PassThru -Verb RunAs -WorkingDirectory [PATH_TO_DIR_TO_RUN_FROM]
Run Code Online (Sandbox Code Playgroud)

警告

我们的应用程序使用.ps1打包在 Electron 应用程序中的文件而不是单行 PowerShell 脚本来执行此操作。在我们的案例中,这不是问题,因为应用程序是内部工具,因此当我们指示最终用户设置适用的 PowerShell 权限以允许运行脚本时,最终用户会信任他们。如果您不能依赖.ps1文件,您的里程可能会有所不同。如果您可以选择,那么Set-ExecutionPolicy就是您的朋友。我们让用户在安装应用程序后执行类似的操作:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
dir "$env:USERPROFILE\AppData\Local\Programs\path\to\our\scripts" | Unblock-File
Run Code Online (Sandbox Code Playgroud)

其中指向应用程序打包的文件$env:USERPROFILE\AppData\Local\Programs\path\to\our\scripts目录。.ps1这样,用户只允许 PowerShell 运行我们提供给他们的脚本,而不是做一些愚蠢的事情,比如让他们允许所有 PowerShell 脚本运行,无论来源如何。

-ExecutionPolicy RemoteSigned是这里的秘密武器;它说“仅执行来自受信任来源的下载脚本”。(您也可以使用-ExecutionPolicy AllSigned更多的限制。)下一行将脚本目录中的所有文件通过管道表示Unblock-File“此目录中的脚本来自受信任的源”。