將PowerShell指令碼編譯成EXE
阿新 • • 發佈:2019-01-07
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