Làm thế nào để bạn thực hiện một lệnh gốc tùy ý từ một chuỗi?


208

Tôi có thể diễn đạt nhu cầu của mình với kịch bản sau: Viết hàm chấp nhận một chuỗi được chạy dưới dạng lệnh gốc.

Nó không quá xa vời với một ý tưởng: nếu bạn đang can thiệp vào các tiện ích dòng lệnh khác từ nơi khác trong công ty cung cấp cho bạn một lệnh để chạy nguyên văn. Vì bạn không kiểm soát lệnh, bạn cần chấp nhận bất kỳ lệnh hợp lệ nào làm đầu vào . Đây là những trục trặc chính mà tôi không thể vượt qua:

  1. Lệnh có thể thực thi một chương trình sống trong một đường dẫn có khoảng trắng trong đó:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
  2. Lệnh có thể có các tham số có khoảng trắng trong chúng:

    $command = 'echo "hello world!"';
  3. Lệnh có thể có cả dấu tick đơn và kép:

    $command = "echo `"it`'s`"";

Có cách nào sạch sẽ để thực hiện điều này? Tôi chỉ có thể nghĩ ra những cách giải quyết xa hoa và xấu xí, nhưng đối với một ngôn ngữ kịch bản, tôi cảm thấy việc này thật đơn giản.

Câu trả lời:


318

Invoke-Expression, cũng có bí danh là iex. Sau đây sẽ hoạt động trên các ví dụ # 2 và # 3 của bạn:

iex $command

Một số chuỗi sẽ không chạy như hiện tại, chẳng hạn như ví dụ số 1 của bạn vì exe nằm trong dấu ngoặc kép. Điều này sẽ hoạt động như bình thường, bởi vì nội dung của chuỗi chính xác là cách bạn chạy nó trực tiếp từ dấu nhắc lệnh Powershell:

$command = 'C:\somepath\someexe.exe somearg'
iex $command

Tuy nhiên, nếu exe nằm trong dấu ngoặc kép, bạn cần sự trợ giúp của &để nó chạy, như trong ví dụ này, như chạy từ dòng lệnh:

>> &"C:\Program Files\Some Product\SomeExe.exe" "C:\some other path\file.ext"

Và sau đó trong kịch bản:

$command = '"C:\Program Files\Some Product\SomeExe.exe" "C:\some other path\file.ext"'
iex "& $command"

Có khả năng, bạn có thể xử lý gần như tất cả các trường hợp bằng cách phát hiện xem ký tự đầu tiên của chuỗi lệnh có "giống như trong triển khai ngây thơ này không:

function myeval($command) {
    if ($command[0] -eq '"') { iex "& $command" }
    else { iex $command }
}

Nhưng bạn có thể tìm thấy một số trường hợp khác phải được gọi theo một cách khác. Trong trường hợp đó, bạn sẽ cần phải sử dụng try{}catch{}, có lẽ đối với các loại / thông báo ngoại lệ cụ thể hoặc kiểm tra chuỗi lệnh.

Nếu bạn luôn nhận được các đường dẫn tuyệt đối thay vì các đường dẫn tương đối, bạn không nên có nhiều trường hợp đặc biệt, nếu có, ngoài 2 trường hợp trên.


3
Bí danh là tuyệt vời. Hãy nhớ rằng, nếu bạn chuyển sang một máy khác hoặc gửi tập lệnh đó cho người khác mà bí danh có thể sẽ không được thiết lập. Thích tên đầy đủ của các chức năng PowerShell.
Doug Finke

1
@Doug: Hầu hết thời gian tôi làm điều đó hoặc sử dụng các bí danh tích hợp (đặc biệt là để ngắn gọn trên dòng lệnh). Mọi evalthứ chỉ nửa đùa nửa thật vì đó là những gì nó được gọi bằng rất nhiều ngôn ngữ kịch bản khác, và đây không phải là câu hỏi đầu tiên tôi thấy ở đâu mà ai đó không biết invoke-expression. Và trường hợp của OP nghe giống như kịch bản nội bộ.
Joel B Fant

Tôi đã thử điều này, nhưng nó không hoạt động với: $ command = '"C: \ Program Files \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Johnny Kauffman

3
Bạn có thể phải đặt "&" hoặc "." ký trước lệnh thực tế nếu nó không phải là powershell Invoke-Expression "& $command"
-igen

Torbjorn Bergstedt thắng! Phép thuật thêm "&" đã giải quyết vấn đề của tôi! Kết quả là tôi vừa hạnh phúc vừa bối rối.
Johnny Kauffman

19

Ngoài ra, vui lòng xem báo cáo Microsoft Connect này về cơ bản, việc sử dụng PowerShell khó khăn như thế nào để chạy các lệnh shell (ồ, thật trớ trêu).

http://connect.microsoft.com/PowerShell/feedback/details/376207/

Họ đề nghị sử dụng --% như một cách để buộc PowerShell ngừng cố gắng diễn giải văn bản sang phải.

Ví dụ:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj

1
Ah, và Microsoft đã phá vỡ internet và liên kết không còn hiệu lực nữa.
Johan Boulé

1
Trên liên kết là trên archive.org tại web.archive.org/web/20131122050220/http://...
mwfearnley

4

Câu trả lời được chấp nhận không hoạt động với tôi khi cố phân tích sổ đăng ký để gỡ cài đặt chuỗi và thực thi chúng. Hóa ra tôi không cần cuộc gọi đến Invoke-Expressionsau tất cả.

Cuối cùng tôi đã bắt gặp mẫu đẹp này để xem cách thực hiện gỡ cài đặt chuỗi:

$path = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
$app = 'MyApp'
$apps= @{}
Get-ChildItem $path | 
    Where-Object -FilterScript {$_.getvalue('DisplayName') -like $app} | 
    ForEach-Object -process {$apps.Set_Item(
        $_.getvalue('UninstallString'),
        $_.getvalue('DisplayName'))
    }

foreach ($uninstall_string in $apps.GetEnumerator()) {
    $uninstall_app, $uninstall_arg = $uninstall_string.name.split(' ')
    & $uninstall_app $uninstall_arg
}

Điều này làm việc cho tôi, cụ thể là vì $applà một ứng dụng nội bộ mà tôi biết sẽ chỉ có hai đối số. Đối với các chuỗi gỡ cài đặt phức tạp hơn, bạn có thể muốn sử dụng toán tử nối . Ngoài ra, tôi chỉ sử dụng một bản đồ băm, nhưng thực sự, có lẽ bạn sẽ muốn sử dụng một mảng.

Ngoài ra, nếu bạn đã cài đặt nhiều phiên bản của cùng một ứng dụng, trình gỡ cài đặt này sẽ quay vòng tất cả chúng cùng một lúc, điều này gây nhầm lẫn MsiExec.exe, do đó cũng vậy.


1

Nếu bạn muốn sử dụng toán tử cuộc gọi, các đối số có thể là một mảng được lưu trữ trong một biến:

$prog = 'c:\windows\system32\cmd.exe'
$myargs = '/c','dir','/x'
& $prog $myargs

Toán tử cuộc gọi cũng hoạt động với các đối tượng ApplicationInfo.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.