编写 PowerShell Server 加快 PowerShell 脚本启动速度 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
goreliu
V2EX    PowerShell

编写 PowerShell Server 加快 PowerShell 脚本启动速度

  •  
  •   goreliu 2019-04-29 09:40:33 +08:00 5068 次点击
    这是一个创建于 2425 天前的主题,其中的信息可能已经有所发展或是发生改变。

    PowerShell 功能很强大,但对 Linux Shell 用户来说易用性依然达不到满意程度(虽然几经改进后比以前强了许多)。那么就有一个办法是平时使用 WSL 中的 Shell,并通过 PowerShell 脚本实现一些功能。但问是 PowerShell 启动时间很慢(在我这里要 130ms 以上)。那就意味着在 WSL 中调用 PowerShell 脚本会因为启动速度慢而体验极差。

    看着功能强大的 PowerShell 却没办法舒服地使用还是比较遗憾的。不过有一个折衷的办法,运行一个常驻的 PowerShell Server,然后在 WSL 下运行命令时直接连接到这个 Server,这样就省去了 PowerShell 的启动时间。

    运行效果

    % time o '$PSversionTable.PSVersion' 5.1.18362.1 o '$PSversionTable.PSVersion' 0.00s user 0.00s system 0% cpu 0.006 total % time o '11+22+33' 66 o '11+22+33' 0.00s user 0.00s system 0% cpu 0.006 total % cat test.ps1 #!/home/goreliu/.bin/o -f $var=@{Name = "小明"; Age = "12"; sex = "男"} echo $var["Name"] % time ./test.ps1 小明 ./test.ps1 0.00s user 0.02s system 249% cpu 0.006 total % time bash -c '' bash -c '' 0.00s user 0.02s system 193% cpu 0.008 total % o PS> 33*99 3267 PS> 12mb 12582912 PS> 

    重点在于运行时间,只有惊人的 6ms,要比 WSL 下直接运行 bash 脚本还快。

    其中 o 是用于连接 Server 的 Client,代码见下文。

    用 PowerShell 写的 Server

    #!/bin/powershell $endpoint = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Loopback, 12345) $Listener = New-Object System.Net.Sockets.TcpListener $endpoint $Listener.Start() $RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 10) $RunspacePool.Open() while ($true) { $client = $Listener.AcceptTcpClient() $job = [PowerShell]::Create().AddScript({ Param($client, $buffer) $encoding = new-object System.Text.Utf8Encoding $stream = $client.GetStream() $cmd = '' $buffer = new-object byte[] 102400 while ($true) { try { $read_size = $stream.Read($buffer, 0, $buffer.Count) $cmd = $encoding.GetString($buffer, 0, $read_size) } catch { echo $_ break } if ($cmd.length -eq 0) { break } <# if ($cmd -match '@gui@') { $stream.Write([BitConverter]::GetBytes(0), 0, 4) [PowerShell]::Create().AddScript($cmd).BeginInvoke() break } elseif ($cmd -match '@guip@') { $stream.Write([BitConverter]::GetBytes(0), 0, 4) # 用进程 Start-Job -ScriptBlock {iex $args[0]} -ArgumentList $cmd break } #> iex $cmd -OutVariable out -ErrorVariable err $all = $err if ($all.Count -eq 0) { $all = $out } if ($all.Count -eq 0) { $stream.Write([BitConverter]::GetBytes(0), 0, 4) } else { $result = $encoding.GetBytes(($all -join "`n") + "`n") $stream.Write([BitConverter]::GetBytes($result.length) + $result,` 0, 4 + $result.length) } } $stream.Close() $client.Close() }).AddParameter('client', $client).AddParameter('buffer', $buffer) $job.RunspacePool = $RunspacePool $job.BeginInvoke() } 

    分别运行于 WSL 和 Windows 下的 Client

    #include <arpa/inet.h> #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <unistd.h> #define buf_size 1048576 char buf[buf_size] = ""; int main(int argc, char *argv[]) { // -f file: run file if (argc == 3 && argv[1][0] == '-' && argv[1][1] == 'f') { int fd = open(argv[2], O_RDONLY); if (fd < 0) { printf("Failed to open %s.\n", argv[2]); return 1; } if (read(fd, buf, buf_size) <= 1) { printf("Failed to read %s.\n", argv[2]); close(fd); return 1; } close(fd); } else { for (int i = 1; i < argc; ++i) { strcat(buf, argv[i]); strcat(buf, " "); } } struct sockaddr_in their_addr; their_addr.sin_family = AF_INET; their_addr.sin_port = htons(12345); their_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); bzero(&(their_addr.sin_zero), 8); int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) != 0) { printf("Failed to connect.\n"); return 1; } int manual = 0; if (argc == 1) { readlink("/proc/self/fd/0", buf, buf_size); // pipe:[...] if (buf[0] != 'p') { // run as a shell manual = 1; } } while (1) { if (manual) { write(1, "PS> ", 4); } int numbytes; if (argc == 1) { numbytes = read(0, buf, buf_size); if (numbytes == 1) { // 只读到一个换行符 continue; } else if (numbytes < 1) { if (manual) { write(1, "\n", 1); } close(sockfd); return 0; } } else { numbytes = strlen(buf); } if (numbytes > 102400) { printf("To long: %d.\n", numbytes); close(sockfd); return 0; } send(sockfd, buf, numbytes, 0); int length; if (recv(sockfd, &length, 4, 0) != 4) { printf("Failed to parse length.\n"); if (argc == 1) { break; } continue; } while (length > 0) { numbytes = recv(sockfd, buf, length > buf_size ? buf_size : length, 0); length -= numbytes; write(1, buf, numbytes); } if (!manual) { break; } } close(sockfd); return 0; } 

    还有一个运行在 Windows 的 Client,功能很简单,只用来运行图形界面(或者不需要结果的脚本),可以关联到 .ps1 (或者用新扩展名)文件上。可以用 tcc 或者 MinGW 编译。

    // tcc -lws2_32 wino.c #include <fcntl.h> #include <io.h> #include <stdio.h> #include <windows.h> #define buf_size 102400 char buf[buf_size] = ""; char *filename = NULL; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { filename = lpCmdLine; if (filename[0] == '\0') { MessageBoxA(0, "Usage:\n o filename", "Error", MB_ICONERROR); return 1; } if (filename[0] == '"') { ++filename; filename[strlen(filename) - 1] = '\0'; } int fd = open(filename, O_RDONLY); if (fd < 0) { sprintf(buf, "Failed to open %s", lpCmdLine); MessageBoxA(0, buf, "Error", MB_ICONERROR); return 1; } int numbytes = read(fd, buf, buf_size); if (numbytes <= 1) { sprintf(buf, "Failed to read %s", lpCmdLine); MessageBoxA(0, buf, "Error", MB_ICONERROR); close(fd); return 1; } WSADATA wsaData; SOCKET COnnectSocket= INVALID_SOCKET; int iResult; struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(12345); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); iResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (iResult != 0) { sprintf(buf, "WSAStartup failed with error: %d", iResult); MessageBoxA(0, buf, "Error", MB_ICONERROR); return 1; } COnnectSocket= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (COnnectSocket== INVALID_SOCKET) { sprintf(buf, "socket failed with error: %ld", WSAGetLastError()); MessageBoxA(0, buf, "Error", MB_ICONERROR); WSACleanup(); return 1; } iResult = connect(ConnectSocket, (const struct sockaddr *)&server_addr, sizeof(server_addr)); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); WSACleanup(); MessageBoxA(0, "Unable to connect to server", "Error", MB_ICONERROR); return 1; } iResult = send(ConnectSocket, buf, numbytes, 0); if (iResult == SOCKET_ERROR) { sprintf(buf, "send failed with error: %d", WSAGetLastError()); MessageBoxA(0, buf, "Error", MB_ICONERROR); closesocket(ConnectSocket); WSACleanup(); return 1; } iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { sprintf(buf, "shutdown failed with error: %d", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } closesocket(ConnectSocket); WSACleanup(); return 0; } 

    端口都写死在了代码里( 12345 )。

    其中 Server 是多线程的,最多 10 个线程( CreateRunspacePool(1, 10))。

    但需要注意一个事情,运行命令的输出结果和直接在 PowerShell 终端中不同:

    % o '$PSVersionTable' System.Collections.Hashtable % /init /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe '$PSVersionTable' Name Value ---- ----- PSVersion 5.1.18362.1 PSEdition Desktop PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} BuildVersion 10.0.18362.1 CLRVersion 4.0.30319.42000 WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1 

    写脚本的话基本不影响使用。

    另外需要注意安全问题。

    4 条回复    2019-06-05 20:08:39 +08:00
    ps1aniuge
        1
    ps1aniuge  
       2019-06-05 19:17:52 +08:00
    楼主这什么玩意?不知所谓!!!

    楼主的 win,powershell 慢( mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe )
    你折腾 linux ( wsl )干嘛??!

    1 130ms,启动 powershell 不算慢。

    2 可以买高速 ssd,装在 c 盘上。想快速尽量不能贫穷。

    3 可以经常手动运行 。net 优化,来提速。
    任务计划 -》
    \Microsoft\Windows\.NET Framework\.NET Framework NGEN v4.0.30319 64

    4 可以启动 Superfetch 服务,即内存缓存程序来提速。

    5 可以运行 linux 版( wsl 版) powershell,来提速 linux 中使用 pwsh。不过这个和 win 中的 powershell 没啥关系。
    goreliu
        2
    goreliu  
    OP
       2019-06-05 19:22:36 +08:00 via Android
    @ps1aniuge 我想我写得很清楚了。
    ps1aniuge
        3
    ps1aniuge  
       2019-06-05 19:47:56 +08:00
    你来回折腾,到底要干嘛?

    你要在你的 wsl 发行版的 linux 中,使用 powershell,不需要远程去使用,
    只需要在 wsl 发行版 linux 中,安装 powershell 6.2.1。
    装好后,在 linux 本地使用 /usr/bin/pwsh 即可。
    不需要联网 服务器 /客户机这样使用。

    就算是服务器 /客户机这样使用。用 ssh-ps-remoting 也行,也不需要自己开发。
    你这是过度开发。会写 c,会写锤子,看啥都是钉子。
    goreliu
        4
    goreliu  
    OP
       2019-06-05 20:08:39 +08:00 via Android
    @ps1aniuge 你如果想理解我在做什么,要先仔细了解我所说的场景。而你回复我的内容,我认为你是好心的,但无助于解决我所说的问题。如果你还没有理解,我也不想继续展开了。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3356 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 04:36 PVG 12:36 LAX 20:36 JFK 23:36
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86