将PowerShell脚本编译成EXE

将PowerShell脚本编译成EXE 23


Powergui中有个工具,可以将PowerShell脚本转换成独立的可执行程序EXE。所以,我想写一个PowerShell函数,能够将一个Ps1脚本文件转换成同名的可执行文件。

知识点分析

  • 关键应当使用到.Net动态编译类Microsoft.CSharp.CSharpCodeProvider。在内存中编译,输出为可执行程序EXE。
  • 编译不通过时,输出编译错误信息,包含行号和列号;编译通过时,输出应用程序路径。
  • 将要编译的脚本作为Resource文件嵌入到目标应用程序中。
  • 为了确保最大的兼容性,不适用c#执行PowerShell脚本,直接使用Process打开PowerShell.exe ,将脚本文件存到临时目录传递过去运行。
  • 将Process运行过程中产生的标准输出,异步重定向应用程序。

源脚本(Convert-PS1ToExe.ps1)

function Convert-PS1ToExe
{
    param(
    [Parameter(Mandatory=$true)]
    [ValidateScript({$true})]
    [ValidateNotNullOrEmpty()]    
    [IO.FileInfo]$ScriptFile
    )
    if( -not $ScriptFile.Exists)
    {
        Write-Warning "$ScriptFile not exits."
        return
    }

    [string]$csharpCode = @'
    using System;
    using System.IO;
    using System.Reflection;
    using System.Diagnostics;
    namespace LoadXmlTestConsole
    {
        public class ConsoleWriter
        {
            private static void Proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
            {
                Process pro = sender as Process;
                Console.WriteLine(e.Data);
            }
            static void Main(string[] args)
            {
                // Set title of console
                Console.Title = "Powered by PSTips.Net";

                // read script from resource
                Assembly ase = Assembly.GetExecutingAssembly();
                string scriptName = ase.GetManifestResourceNames()[0];
                string scriptContent = string.Empty;
                using (Stream stream = ase.GetManifestResourceStream(scriptName))
                using (StreamReader reader = new StreamReader(stream))
                {
                    scriptContent = reader.ReadToEnd();
                }

                string scriptFile = Environment.ExpandEnvironmentVariables(string.Format("%temp%\\{0}", scriptName));
                try
                {
                    // output script file to temp path
                    File.WriteAllText(scriptFile, scriptContent);

                    ProcessStartInfo proInfo = new ProcessStartInfo();
                    proInfo.FileName = "PowerShell.exe";
                    proInfo.CreateNoWindow = true;
                    proInfo.RedirectStandardOutput = true;
                    proInfo.UseShellExecute = false;
                    proInfo.Arguments = string.Format(" -File {0}",scriptFile);

                    var proc = Process.Start(proInfo);
                    proc.OutputDataReceived += Proc_OutputDataReceived;
                    proc.BeginOutputReadLine();
                    proc.WaitForExit();
                    Console.WriteLine("Hit any key to continue...");
                    Console.ReadKey();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Hit Exception: {0}", ex.Message);
                }
                finally
                {
                    // delete temp file
                    if (File.Exists(scriptFile))
                    {
                        File.Delete(scriptFile);
                    }
                }

            }

        }
    }
'@

    # $providerDict
    $providerDict = New-Object 'System.Collections.Generic.Dictionary[[string],[string]]'
    $providerDict.Add('CompilerVersion','v4.0')
    $codeCompiler = [Microsoft.CSharp.CSharpCodeProvider]$providerDict

    # Create the optional compiler parameters
    $compilerParameters = New-Object 'System.CodeDom.Compiler.CompilerParameters'
    $compilerParameters.GenerateExecutable = $true
    $compilerParameters.GenerateInMemory = $true
    $compilerParameters.WarningLevel = 3
    $compilerParameters.TreatWarningsAsErrors = $false
    $compilerParameters.CompilerOptions = '/optimize'
    $outputExe = Join-Path $ScriptFile.Directory "$($ScriptFile.BaseName).exe"
    $compilerParameters.OutputAssembly =  $outputExe
    $compilerParameters.EmbeddedResources.Add($ScriptFile.FullName) > $null
    $compilerParameters.ReferencedAssemblies.Add( [System.Diagnostics.Process].Assembly.Location ) > $null

    # Compile Assembly
    $compilerResult = $codeCompiler.CompileAssemblyFromSource($compilerParameters,$csharpCode)

    # Print compiler errors
    if($compilerResult.Errors.HasErrors)
    {
        Write-Host 'Compile faield. See error message as below:' -ForegroundColor Red
        $compilerResult.Errors | foreach {
            Write-Warning ('{0},[{1},{2}],{3}' -f $_.ErrorNumber,$_.Line,$_.Column,$_.ErrorText )
        }
    }
    else 
    {
         Write-Host 'Compile succeed.' -ForegroundColor Green
         "Output executable file to '$outputExe'"
    }
}

测试

新建PowerShell脚本文件,命名为second.ps1,输入内容:

1..5 | foreach {
Get-Random
sleep -Milliseconds 500
}
$date=get-date
"Hello,now is $date"

在控制台中运行:

Convert-PS1ToExe -ScriptFile .\second.ps1

输出为:

将PowerShell脚本编译成EXE

将PowerShell脚本编译成EXE

双击运行second.exe,输出:

将PowerShell脚本编译成EXE

将PowerShell脚本编译成EXE

补充

当我写完脚本,准备名称成ps2exe时,发现有人早已捷足先登。

×用微信扫描并分享
本文链接: https://www.pstips.net/convert-ps1toexe.html
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!

关于 Mooser Lee

我是一个Powershell的爱好者,创建了PowerShell中文博客,热衷于Powershell技术的搜集和分享。本站部分内容来源于互联网,不足之处敬请谅解,并欢迎您批评指正。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

23 条评论 “将PowerShell脚本编译成EXE