aardio + PowerShell 視覺化快速開發獨立 EXE 桌面程式

2022-09-30 12:01:42

aardio 可以方便地呼叫 PowerShell ,PowerShell 中也可以自由呼叫 aardio 物件與函數。不用帶上體積很大的
System.Management.Automation.dll,直接呼叫系統元件,可以生成體積很小的獨立 EXE。向下相容到 .NET 2.0、PowerShell 2.0,支援 Win7,Win8,Win10,Win11 ……

▶ dotNet.ps 擴充套件庫

aardio 呼叫 PowerShelll 的功能由基於 dotNet 庫 實現的 dotNet.ps 庫提供。請參考:aardio + .NET 快速開發獨立 EXE 程式,可防 ILSpy 反編譯 

 aardio 呼叫 PowerShell 命令

我們直接上程式碼看範例:

import console;
import dotNet.ps;

console.showLoading(" 正在執行PowerShell命令");
console.log( 
  dotNet.ps.command("Get-Command",{"ListImported"}) 
);

console.pause();

dotNet.ps.command 的第一個引數指定要呼叫的 PowerShell 命令名,第二個引數用一個表物件指定 PowerShell 命令引數 —— 可以包含僅由引數名字組成的陣列成員。

參數列也可以包含由名值對指定的命名引數,例如:

dotNet.ps.command("Get-Command"
                  ,{Name="*Process"} );

要注意引數名前面不需要加 $ 或 - 字首。
等號前面是引數名(必須是字串),等號後面是引數值(可傳入 .NET 物件、COM 物件、aardio 物件)。

▶ aardio 呼叫 PowerShell 指令碼

使用匿名引數呼叫 PowerShell 指令碼的範例:

var ps1 = /*   
for ( $i=0; $i -lt $args.count; $i++){
    write-host $args[$i]
}  
*/

import dotNet.ps;
var output = dotNet.ps(ps1,{  
  "匿名引數1","匿名引數2","匿名引數3","匿名引數4"
});
  
import console;
console.log(output);  
console.pause();

匿名引數也可以這樣寫:

dotNet.ps(ps1,"匿名引數1","匿名引數2","匿名引數3","匿名引數4");

也可以指定命名引數,如下:

var ps1 = /* 
    # 定義命名引數,引數前加$號,aardio 參數列裡去掉$號
    param($username,$password)
    
    Write-host $username,$password
*/
 
import dotNet.ps;
var output = dotNet.ps(ps1,{ 
    username = "名字";//引數名前不要加$
    password = "密碼";//引數名前不要加$
});

import console;
console.log(output);  
console.pause();

這裡請注意:

1、PowerShell 通常用 param 宣告引數名稱(函數裡也可以這樣寫)。

2、PowerShell 要在變數(或引數名)前加上 $ 符號,在 aardio 中指定 PowerShell 引數時要去掉這個 $ 符號。

dotNet.ps() , dotNet.ps.command() 的傳引數規則是完全一樣的。

▶ PowerShell 呼叫 aardio 物件

下面就要進入最神奇的部分了,在 PowerShell 裡還可以方便地呼叫 aardio 物件。

我們直接看 aardio 程式碼範例:

var ps1 = /* 
    # 定義命名引數
    param($win,$external,$username)
    
    # 自由呼叫引數傳進來的 aardio 物件
    $win.msgboxTest("這是 PowerShell 呼叫 aardio 開啟的對話方塊。") #返回值會自動輸出一行
    
    # 自由呼叫 aardio 函數 
    $external.func("引數1","引數2")  
*/

import win;
import dotNet.ps;
var output = dotNet.ps(ps1,{ 
    win = win;
    external = {
        func = function(title,text){
            win.msgbox(text,title)
        }
    };  
});

Win10 / Win11 自帶的 PowerShell 5.1 可以支援這種舒服的寫法。如果要相容 Win7 只要簡單地呼叫 dotNet.ps.export( aardio物件 ) 匯出引數給 PowerShell 就可以了,不過 Win7 的市場份額已經很小,這種事太追求完美也不好。

▶ 用 JSON 解析 PowerShell 輸出

aardio 程式碼範例:

import console;
import dotNet.ps;

var psVersion = dotNet.ps.json(
  `ConvertTo-Json $PSVersionTable.PSVersion`)
console.dumpJson(psVersion);    

console.pause(true);

▶ 捕獲 PowerShell 輸出

很簡單,我們直接看 aardio 程式碼範例:

import console;
import dotNet.ps;

// PowerShell 輸入都會傳給這個 aardio 函數
dotNet.ps.onWrite = function(str){
  console.log(str);
}
 
dotNet.ps.command("Get-Command",{Name="*Process"});
console.pause(output);

我們也可以指定 dotNet.ps.onWriteProgress 回撥以自定義 PowerShell 進度條,一個例子:

 

這個進度條範例的原始碼在這裡:
aardio 自帶範例 / 呼叫其他語言 / PowerShell / 進度條

 

上面範例裡有一些方便的小工具,例如作業系統預設禁止單獨執行 *.ps1 指令碼檔案。上面範例裡就提供了一個小工具 —— 可以一鍵開啟或關閉這個許可權:

 

PowerShell 裡有很多 Cmdlet 是用 C# 寫的,而 C# 寫的軟體可以用 ILSpy 直接檢視原始碼。其實看看一些 Cmdlet 的原始碼很有意思,但這個操作步驟有些多。

aardio 自帶的 PowerShell 範例裡還提供了一個快速檢視 Cmdlet 原始碼的工具,可以直接列出所有命令,可以搜尋查詢,可以一鍵呼叫 ILSpy 反編譯出原始碼:

 

▶ aardio / PowerShell / .NET 共用應用程式域

用大白話講就是這三者可以直接共用物件,相互呼叫物件非常方便。

當使用 dotNet.ps 執行 PowerShell 程式碼是在當前程序中執行( 沒有建立新程序,但建立了新執行緒),並且 PowerShell 就執行在 aardio 建立的 .NET 應用程式域中 —— 這時候 aardio / PowerShell / .NET 共用一個應用程式域,這讓我們可以做一些有趣的事。

請看 aardio 程式碼範例:

import dotNet.ps;
import dotNet.json;

var json = dotNet.ps( `
    # 雜湊表(陣列元素要用逗號分開)
    $tab = @{ Name = "張三"; Age = "20"; Array = 1,2,3 } 
    
    # PowerShell 型別放在 [] 裡面,並用 :: 存取類的靜態成員
    [Newtonsoft.Json.JsonConvert]::SerializeObject(  $tab  )
` );
var tab = web.json.parse(json);

import console
console.dump(tab)
console.pause()

aardio 庫 dotNet.json 記憶體載入了 .NET 程式集 Newtonsoft.Json.dll,然後我們在 .NET 或是 PowerShell 中就可以直接使用這個程式集匯入的類。

注意:PowerShell 將類或型別放在 [中括號] 內,PowerShell 在宣告或強制轉換型別時都使用這個 [中括號] ,存取類的靜態成員使用 :: 而不是圓點 。

下面的例子更進一步:在 aardio 中編譯 C# 程式碼,然後在 PowerShell 中呼叫該 C# 程式碼引入的類,然後在 C# 中回撥 PowerShell 函數,然後在該 PowerShell 函數中回撥 aardio 函數:

import win;
import console;
import dotNet.ps;

var compiler = dotNet.createCompiler("C#");  
compiler.Source = /****** 
namespace CSharpLibrary
{ 
    public class Object
    {
        public delegate int TestDelegateType(string str,int a);
        public event TestDelegateType onTestEvent; 
           
        public int Test()
        {   
            return onTestEvent("你好",123);
          }
          
          public static Object New(){return new Object(); }
    }
}
******/

//編譯 C# 程式碼並匯入名稱空間
compiler.import("CSharpLibrary"); 

var out,err = dotNet.ps( `
param($win) 

$obj = [CSharpLibrary.Object]::New() #建立物件

# 新增事件
$obj.add_onTestEvent( {
    param($str,$a) # 宣告引數
    
    # 呼叫 aardio 函數
    $temp = $win.msgbox("事件被回撥了",$str)
    
    # return 語句只能改最後一個返回值,與其他語言有較大區別
    return $a 
})

$obj.Test()
`,{ win = win; });

console.log(out,err);
console.pause(true);

這裡就要注意 PowerShell 有一個非常特別的『 特(大)性(坑)』—— PowerShell 的函數裡每句程式碼的預設輸入都會增加一個返回值,例如您執行下面的 PowerShell 程式碼:

# 新增事件 
$obj.add_onTestEvent( {
  param($str,$a) 
   
  $win.msgbox("事件被回撥了",$str)
   
  return $a 
})

這裡的返回值實際上有兩個,一個是 $win.msgbox() 返回的 object,另一個是 return 返回的 $a,最終返回值是一個陣列。然後就會報返回值與 C# 委託回撥的返回值型別不匹配。

避免上面這個問題也很簡單,把程式碼放到一個賦值語句裡就不會增加返回值了,正確寫法:

$temp = $win.msgbox("事件被回撥了",$str)

▶ 建立管道呼叫 PowerShell.exe

我們還可以用 aardio 中的 procee.popen 建立程序管道,這樣就可以讀寫 PowerShell.exe 的輸出輸入,並且隱藏黑視窗。

下面是一個例子:

import console;
import process.popen;
console.showLoading(" 請稍候,正在呼叫 PowerShell");

var prcs  = process.popen.ps(`-Command`,`&{
    function Get-Version {
        ConvertTo-Json( $PSVersionTable.PSVersion )
    }
    
    Get-Version
}`);

//讀取程序輸出   
var json = prcs.readAll(); 

//解析返回的 JSON 
import web.json;
var psVersion = web.json.parse(json);

console.dump(psVersion);
console.pause(); 

這裡要注意,PowerShell 會將僅用大括號包含的 PowerShell 作為字串輸出,在前面加上一個 & 字元才會執行該語句塊。

▶ 呼叫更多程式語言

aardio 中還可以非常方便地呼叫 C語言、C++、C#、Java、Python、R、Javascript、Node.Js、Fortran、VB、Flash ActionScript、PHP、VBScript、PowerShell、NewLISP、AutoLISP、Delphi、FreeBASIC、Ruby、Rust、Julia、Nim、Go 語言、批次處理 ...... 甚至可以直接嵌入組合機器碼並且轉換為普通的 aardio 函數。