2023年11月12日 星期日

Java中如何委託執行 PowerShell 代碼

Java中如何委託執行 PowerShell 代碼

遇到的情形是PowerShell中有現成的函式庫可以用Java中似乎沒有,不想自己實現直接套過去用的話可以暫時先這樣擋著。

廢話不多說直接上代碼

package com.excucmd;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class App {
    public static void main(String[] args) {
        System.out.println("Hello executeCommand!");
        int exitCode = -1;

        // 執行 PowerShell 測試1
        String program = "PowerShell";
        String[] params = {"-nop", "(Get-Date); 1..3 | ForEach-Object { Write-Host Counting down: $_; Start-Sleep -Seconds 1 }; 0/0"};
        exitCode = executeCommand(program, params);
        System.out.println("Exit code: " + exitCode);

        // 執行 PowerShell 測試2
        String[] params2 = {"powershell", "-nop", "Write-Error 'Pwsh:: throw a Exception' -EA 1; Write-Host Pwsh:: After the Exception"};
        try {
            exitCode = executeCommand2(params2);
        } catch (Exception e) { e.printStackTrace(); }
        System.out.println("Exit code: " + exitCode);
    }

    /**
     * 執行指定的命令並返回結果
     * 
     * @param program 要執行的程序名稱,例如 "powershell"
     * @param params  可變參數,傳遞給程序的參數
     *                例如,對於 PowerShell 命令,可以是 "-nop", "您的 PowerShell 命令"
     * @return        程序執行後的退出碼。通常,0 表示正常結束,非0 表示有錯誤發生
     * 
     * @throws IllegalArgumentException 如果程序名稱為空或 null,或者參數為空或 null。
     */
    private static int executeCommand(String program, String... params) {
        // 檢查參數
        if (program == null || program.trim().isEmpty()) {
            throw new IllegalArgumentException("Program cannot be null or empty.");
        }
        if (params == null || params.length == 0) {
            throw new IllegalArgumentException("Arguments cannot be null or empty.");
        }

        // 設定基本參數
        int exitCode = -1;
        final String encoding = "BIG5"; /* [UTF-8, Shift_JIS, BIG5] */
        final String workDir = System.getProperty("user.dir"); /* 獲取Java當前工作路徑 */

        // 構建輸入參數
        List<String> commandList = new ArrayList<>();
        commandList.add(program);
        Collections.addAll(commandList, params);

        // 構建處理程序
        ProcessBuilder processBuilder = new ProcessBuilder(commandList);
        processBuilder.environment().remove("PSModulePath");
        processBuilder.directory(new File(workDir));

        try {
            // 執行命令
            Process process = processBuilder.start();
            // 處理輸出流
            try (
                BufferedReader stdoutReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), encoding));
                BufferedReader stderrReader = new BufferedReader(
                    new InputStreamReader(process.getErrorStream(), encoding));
            ) {
                String line;
                // 讀取標準輸出流
                while ((line = stdoutReader.readLine()) != null) {
                    System.out.println(line);
                }
                // 讀取錯誤輸出流
                while ((line = stderrReader.readLine()) != null) {
                    final String ANSI_RESET = "\u001B[0m";
                    final String ANSI_RED = "\u001B[31m";
                    System.out.println(ANSI_RED + line + ANSI_RESET);
                }
            }
            // 獲取錯誤代碼
            exitCode = process.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }

        return exitCode;
    }

    /**
     * 執行指定的命令並返回結果
     * 這是一個簡化版本的方法,將錯誤輸出重定向至標準輸出,只用一個進程監控打印的內容
     * 
     * @param command 命令和其參數,作為可變長度參數傳入。例如:"cmd.exe", "/c", "dir"
     * @return 命令執行後的退出碼。通常,0 表示正常結束,非0 表示有錯誤發生。
     * @throws Exception 如果退出碼非0,則拋出異常。
     */
    private static int executeCommand2(String... command)
            throws Exception
    {
        int exitCode = -1;
        final String encoding = "BIG5"; /* [UTF-8, Shift_JIS, BIG5] */

        ProcessBuilder processBuilder = new ProcessBuilder(command);
        processBuilder.redirectErrorStream(true); // 將錯誤輸出重定向至標準輸出

        try { // 啟動進程
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), encoding))) {
                String line;// 使用 BufferedReader 逐行讀取進程的輸出並打印到控制台
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }
            exitCode = process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

        // 退出代碼不為零則拋出例外
        if (exitCode != 0) {
            throw new Exception("PowerShell command execution failed with exit code " + exitCode);
        }

        return exitCode;
    }
}

這裡面解決的一個問題是 PowerShell 被這樣委託執行貌似會出錯PS環境變數的問題,解決辦法就是清空他自己會重新讀取

參考這邊獲得的解答
Cert:\ PSDrive is unavailable on PowerShell 5.1 launched by cmd when cmd is launched on PowerShell 7.3 · Issue #18530 · PowerShell/PowerShell · GitHub


這個問題我記得我在別的情況下也遇過,具體錯誤大概會長這樣子

ConvertTo-SecureString : 已在模組 'Microsoft.PowerShell.Security' 上找到 'ConvertTo-SecureString' 
命令,但無法載入該模組。如需詳細資訊,請執行 'Import-Module Microsoft.PowerShell.Security'。
位於 線路:1 字元:22
+ Get-ExecutionPolicy; ConvertTo-SecureString $password -AsPlainText -F ...
+                      ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (ConvertTo-SecureString:String) [], CommandNotFoundE 
   xception
    + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule


寫得很不清不楚就是了,總的來說就是很多內建的函式變得不可使用,但明明就有找到模組


沒有留言:

張貼留言