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

Posted

技术标签:

【中文标题】如何使用 Nodejs 和 spawn 在 Windows 10 上以管理员身份运行一些 CMD 命令?【英文标题】:How to run some CMD commands on Windows 10 as administrator using Nodejs and spawn? 【发布时间】:2019-04-05 00:27:56 【问题描述】:

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

assoc .ext=CustomExt
ftype CustomExt=app.exe %1 %*

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

我已经阅读了this thread,其中 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`);
);

在 Nodejs 中创建扩展关联的任何其他方式也将不胜感激。

2020 年 1 月 21 日更新

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

2021 年 6 月 24 日更新

我找到了useful thread in VSCode issues。使用sudo-prompt 可能是解决方案。我还没试过,但是依赖还在package.json,所以看起来很有希望。

【问题讨论】:

你试过用管理员权限运行节点脚本吗? 是的,但这是我在@prabhat 问题中写的“最不理想”的解决方案 带管理员的节点执行?为什么我们不在批处理文件上使用 npm start 更早的管理员? 是的,但这是我在@gumuruh 问题中所写的“最不理想”的解决方案。感谢您的评论 【参考方案1】:

新的和改进的答案

有关背景,请参阅旧答案中的注释。我找到了一种方法, 不必允许在用户机器上运行脚本:

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()

使用示例:

const unzip = async (
  zipPath: string,
  destinationPath: string
): Promise<string> =>
  await runAsAdmin(
    `"Expand-Archive -Path '$zipPath' -DestinationPath '$destinationPath'"`
  )

旧答案

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

您可以在子进程中使用 PowerShell 的 Start-Process 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]

警告

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

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
dir "$env:USERPROFILE\AppData\Local\Programs\path\to\our\scripts" | Unblock-File

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

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

【讨论】:

【参考方案2】:

我知道它的旧帖子,但我遇到了这个问题并找到了一个替代解决方案,因此考虑分享。

    创建一个 bat 文件,让我们将其命名为 disabletask.bat,使用您要运行的命令,例如禁用 TaskScheduler "SCHTASKS.EXE /change /disable /TN "Test"" 中的任务

    在 TaskScheduler 中创建一个以最高权限运行的任务,请参阅图像中的复选框

    使用 Nodejs 来执行这个任务

        var child_process = require('child_process');
        child_process.exec('SCHTASKS.EXE /RUN /TN "disabletask"', function(error, stdout, stderr) 
            console.log(stdout);
        );
    
    

【讨论】:

感谢您的解决方案。但我认为解决方法有点丑哈哈 我从 VSCode 添加了一张图片。我还是没有看他们的源代码。 我在我的问题底部添加了另一个更新,我在 GitHub 中找到了一个新线程。看看它是否对你有用。

以上是关于如何使用 Nodejs 和 spawn 在 Windows 10 上以管理员身份运行一些 CMD 命令?的主要内容,如果未能解决你的问题,请参考以下文章

如何在“spawn”中运行没有参数的命令

使用子进程 NodeJS 运行 exe 文件

如何从nodejs刷新子进程

youtube-dl nodejs 有问题 Error: spawn EACCES

nodejs child_process.spawnSync 或 child_process.spawn 包裹在 yieldable 生成器中,它返回输出

“sls dynamodb start”抛出 spawn java ENOENT