google / process.dart

BSD 3-Clause "New" or "Revised" License
52 stars 26 forks source link

[Bug] Can't run system commands in Windows. #66

Closed Tokenyet closed 1 year ago

Tokenyet commented 3 years ago

I'm wondering If run designed to run executable?

example:

// Not working
processManager.run(['tasklist | findstr svchost.exe'], runInShell: true);
// error: '\"tasklist | findstr svchost.exe\"' is not recognized as an internal or external command,
// Neither(note)
processManager.run(['', 'tasklist | findstr svchost.exe'], runInShell: true);

So there is no possible to use these kind of commands, only for pure Process.

// Working
Process.run('tasklist | findstr svchost.exe', [], runInShell: true);
// Not working (note)
Process.run('', ['tasklist | findstr svchost.exe'], runInShell: true); 

Note: After viewing source code, I want to bypass this change, but It's not possible, Process.run with empty executable will give a double quote with empty space, see below:

'" "tasklist' is not recognized as an internal or external command,

The code affect this issue for Sanitizes the executable path on Windows.

  if (executable.contains(' ') && !executable.contains('"')) {
    // Use quoted strings to indicate where the file name ends and the arguments begin;
    // otherwise, the file name is ambiguous.
    return '"$executable"';
  }
Tokenyet commented 3 years ago

My suggestion, but not sure how to add tests. My fork for the workaround.

// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:io'
    show
        Process,
        ProcessResult,
        ProcessSignal,
        ProcessStartMode,
        ProcessException,
        systemEncoding;

import 'package:process/process.dart';

import 'common.dart';
import 'exceptions.dart';
import 'process_manager.dart';

/// Local implementation of the `ProcessManager` interface.
///
/// This implementation delegates directly to the corresponding static methods
/// in `dart:io`.
///
/// All methods that take a `command` will run `toString()` on the command
/// elements to derive the executable and arguments that should be passed to
/// the underlying `dart:io` methods. Thus, the degenerate case of
/// `List<String>` will trivially work as expected.
class LocalProcessManager implements ProcessManager {
  /// Creates a new `LocalProcessManager`.
  const LocalProcessManager();

  @override
  Future<Process> start(
    List<Object> command, {
    String? workingDirectory,
    Map<String, String>? environment,
    bool includeParentEnvironment = true,
    bool runInShell = false,
    ProcessStartMode mode = ProcessStartMode.normal,
    bool sanitize = true,
  }) {
    try {
      final String executable = _getExecutable(
        command,
        workingDirectory,
        runInShell,
      );
      return Process.start(
        sanitize ? sanitizeExecutablePath(executable) : executable,
        _getArguments(command),
        workingDirectory: workingDirectory,
        environment: environment,
        includeParentEnvironment: includeParentEnvironment,
        runInShell: runInShell,
        mode: mode,
      );
    } on ProcessException catch (exception) {
      throw ProcessPackageException.fromProcessException(exception,
          workingDirectory: workingDirectory);
    }
  }

  @override
  Future<ProcessResult> run(
    List<Object> command, {
    String? workingDirectory,
    Map<String, String>? environment,
    bool includeParentEnvironment = true,
    bool runInShell = false,
    Encoding stdoutEncoding = systemEncoding,
    Encoding stderrEncoding = systemEncoding,
    bool sanitize = true,
  }) {
    try {
      final String executable = _getExecutable(
        command,
        workingDirectory,
        runInShell,
      );
      return Process.run(
        sanitize ? sanitizeExecutablePath(executable) : executable,
        _getArguments(command),
        workingDirectory: workingDirectory,
        environment: environment,
        includeParentEnvironment: includeParentEnvironment,
        runInShell: runInShell,
        stdoutEncoding: stdoutEncoding,
        stderrEncoding: stderrEncoding,
      );
    } on ProcessException catch (exception) {
      throw ProcessPackageException.fromProcessException(exception,
          workingDirectory: workingDirectory);
    }
  }

  @override
  ProcessResult runSync(
    List<Object> command, {
    String? workingDirectory,
    Map<String, String>? environment,
    bool includeParentEnvironment = true,
    bool runInShell = false,
    Encoding stdoutEncoding = systemEncoding,
    Encoding stderrEncoding = systemEncoding,
    bool sanitize = true,
  }) {
    try {
      final String executable = _getExecutable(
        command,
        workingDirectory,
        runInShell,
      );
      return Process.runSync(
        sanitize ? sanitizeExecutablePath(executable) : executable,
        _getArguments(command),
        workingDirectory: workingDirectory,
        environment: environment,
        includeParentEnvironment: includeParentEnvironment,
        runInShell: runInShell,
        stdoutEncoding: stdoutEncoding,
        stderrEncoding: stderrEncoding,
      );
    } on ProcessException catch (exception) {
      throw ProcessPackageException.fromProcessException(exception,
          workingDirectory: workingDirectory);
    }
  }

  @override
  bool canRun(covariant String executable, {String? workingDirectory}) =>
      getExecutablePath(executable, workingDirectory, throwOnFailure: false) !=
      null;

  @override
  bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) {
    return Process.killPid(pid, signal);
  }
}

String _getExecutable(
    List<dynamic> command, String? workingDirectory, bool runInShell) {
  String commandName = command.first.toString();
  if (runInShell) {
    return commandName;
  }
  return getExecutablePath(
    commandName,
    workingDirectory,
    throwOnFailure: true,
  )!;
}

List<String> _getArguments(List<Object> command) =>
    // Adding a specific type to map in order to workaround dart issue
    // https://github.com/dart-lang/sdk/issues/32414
    command
        .skip(1)
        .map<String>((dynamic element) => element.toString())
        .toList();
stuartmorgan commented 1 year ago

Closing, as this package now lives in flutter/packages. Please file an issue in the Flutter issue tracker if this is still an issue.