Occasionally folks want to be able to create an EXE from PoweShell. PowerShell can’t do this by itself but this can be done with PowerShell script. Essentially what you can do is create a simple console EXE program that embeds the script as a resource and the EXE, upon loading retrieves the script and throws it at a PowerShell runspace to execute. Here’s the script for a feasibility test of doing this very thing.
Note that this script depends on Write-GZip from the PowerShell Community Extensions.
Updated 6-21-2011: The migration from Windows Live Spaces to WordPress seems to have messed with the formatting of the script. You can now download the script from my SkyDrive.
Updated 3-4-2012: I have added the ability to handle positional parameters passed into the EXE as well as a -NET40 switch to compile using the v4.0 C# compiler. The script is beside the original and is named Make-PS1ExeWrapperWithArgs.ps1:
#requires -version 2.0 <# .SYNOPSIS Creates an EXE wrapper from a PowerShell script by compressing the script and embedding into a newly generated assembly. .DESCRIPTION Creates an EXE wrapper from a PowerShell script by compressing the script and embedding into a newly generated assembly. .PARAMETER Path The path to the . .PARAMETER LiteralPath Specifies a path to one or more locations. Unlike Path, the value of LiteralPath is used exactly as it is typed. No characters are interpreted as wildcards. If the path includes escape characters, enclose it in single quotation marks. Single quotation marks tell Windows PowerShell not to interpret any characters as escape sequences. .PARAMETER OutputAssembly The name (including path) of the EXE to generate. .PARAMETER IconPath The path to an optional icon to be embedded as the application icon for the EXE. .PARAMETER STA By default the console app created uses MTAThread. If this switch is specified, then it uses STAThread. .PARAMETER NET40 By default the console app is compiled against .NET 3.5. If this switch is specified, then it uses .NET 4.0. .EXAMPLE C:\PS> .\Make-PS1ExeWrapper.ps1 .\MyScript.ps1 .\MyScript.exe .\app.ico -Sta This creates an console application called MyScript.exe that internally hosts the PowerShell engine and runs the script specified by MyScript.ps1. Optionally the file app.ico is embedded into the EXE as the application's icon. .NOTES Author: Keith Hill Date: Aug 7, 2010 Issues: This implementation is more of a feasibility test and isn't fully functional. It doesn't support an number of PSHostUserInterface members as well as a number of PSHostRawUserInterface members. This approach also suffers from the same problem of running script "interactively" and not loading it from a file. That is, the entire script output is run through Out-Default and PowerShell gets confused. It formats the first types it sees correctly but after that the formatting is off. To correct this, you have to append | Out-Default where you script outputs to the host without using a Write-* cmdlet e.g.: MyScript.ps1: ------------------------------- Get-Process svchost Get-Date | Out-Default Dir C:\ | Out-Default Dir c:\idontexist | Out-Default $DebugPreference = 'Continue' $VerbosePreference = 'Continue' Write-Host "host" Write-Warning "warning" Write-Verbose "verbose" Write-Debug "debug" Write-Error "error" #> [CmdletBinding(DefaultParameterSetName="Path")] param( [Parameter(Mandatory=$true, Position=0, ParameterSetName="Path", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, HelpMessage="Path to bitmap file")] [ValidateNotNullOrEmpty()] [string[]] $Path, [Alias("PSPath")] [Parameter(Mandatory=$true, Position=0, ParameterSetName="LiteralPath", ValueFromPipelineByPropertyName=$true, HelpMessage="Path to bitmap file")] [ValidateNotNullOrEmpty()] [string[]] $LiteralPath, [Parameter(Mandatory = $true, Position = 1)] [string] $OutputAssembly, [Parameter(Position = 2)] [string] $IconPath, [Parameter()] [switch] $STA, [Parameter()] [switch] $NET40 ) Begin { Set-StrictMode -Version latest $MainAttribute = '' $ApartmentState = 'System.Threading.ApartmentState.MTA' if ($Sta) { $MainAttribute = '[STAThread]' $ApartmentState = 'System.Threading.ApartmentState.STA' } $src = @' using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.IO.Compression; using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Reflection; using System.Security; using System.Text; using System.Threading; [assembly: AssemblyTitle("")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] //[assembly: AssemblyCopyright("Copyright © 2013")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] namespace PS1ToExeTemplate { class Program { private static object _powerShellLock = new object(); private static readonly Host _host = new Host(); private static PowerShell _powerShellEngine; '@ + @" $MainAttribute "@ + @' static void Main(string[] args) { Console.CancelKeyPress += Console_CancelKeyPress; Console.TreatControlCAsInput = false; string script = GetScript(); RunScript(script, args, null); } private static string GetScript() { string script = String.Empty; Assembly assembly = Assembly.GetExecutingAssembly(); using (Stream stream = assembly.GetManifestResourceStream("Resources.Script.ps1.gz")) { var gZipStream = new GZipStream(stream, CompressionMode.Decompress, true); var streamReader = new StreamReader(gZipStream); script = streamReader.ReadToEnd(); } return script; } private static void RunScript(string script, string[] args, object input) { lock (_powerShellLock) { _powerShellEngine = PowerShell.Create(); } try { _powerShellEngine.Runspace = RunspaceFactory.CreateRunspace(_host); _powerShellEngine.Runspace.ApartmentState = '@ + @" $ApartmentState; "@ + @' _powerShellEngine.Runspace.Open(); _powerShellEngine.AddScript(script); if (args.Length > 0) { _powerShellEngine.AddParameters(args); } _powerShellEngine.AddCommand("Out-Default"); _powerShellEngine.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); if (input != null) { _powerShellEngine.Invoke(new[] { input }); } else { _powerShellEngine.Invoke(); } } finally { lock (_powerShellLock) { _powerShellEngine.Dispose(); _powerShellEngine = null; } } } private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) { try { lock (_powerShellLock) { if (_powerShellEngine != null && _powerShellEngine.InvocationStateInfo.State == PSInvocationState.Running) { _powerShellEngine.Stop(); } } e.Cancel = true; } catch (Exception ex) { _host.UI.WriteErrorLine(ex.ToString()); } } } class Host : PSHost { private PSHostUserInterface _psHostUserInterface = new HostUserInterface(); public override void SetShouldExit(int exitCode) { Environment.Exit(exitCode); } public override void EnterNestedPrompt() { throw new NotImplementedException(); } public override void ExitNestedPrompt() { throw new NotImplementedException(); } public override void NotifyBeginApplication() { } public override void NotifyEndApplication() { } public override string Name { get { return "PSCX-PS1ToExeHost"; } } public override Version Version { get { return new Version(1, 0); } } public override Guid InstanceId { get { return new Guid("E4673B42-84B6-4C43-9589-95FAB8E00EB2"); } } public override PSHostUserInterface UI { get { return _psHostUserInterface; } } public override CultureInfo CurrentCulture { get { return Thread.CurrentThread.CurrentCulture; } } public override CultureInfo CurrentUICulture { get { return Thread.CurrentThread.CurrentUICulture; } } } class HostUserInterface : PSHostUserInterface, IHostUISupportsMultipleChoiceSelection { private PSHostRawUserInterface _psRawUserInterface = new HostRawUserInterface(); public override PSHostRawUserInterface RawUI { get { return _psRawUserInterface; } } public override string ReadLine() { return Console.ReadLine(); } public override SecureString ReadLineAsSecureString() { throw new NotImplementedException(); } public override void Write(string value) { string output = value ?? "null"; Console.Write(output); } public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { string output = value ?? "null"; var origFgColor = Console.ForegroundColor; var origBgColor = Console.BackgroundColor; Console.ForegroundColor = foregroundColor; Console.BackgroundColor = backgroundColor; Console.Write(output); Console.ForegroundColor = origFgColor; Console.BackgroundColor = origBgColor; } public override void WriteLine(string value) { string output = value ?? "null"; Console.WriteLine(output); } public override void WriteErrorLine(string value) { string output = value ?? "null"; var origFgColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(output); Console.ForegroundColor = origFgColor; } public override void WriteDebugLine(string message) { WriteYellowAnnotatedLine(message, "DEBUG"); } public override void WriteVerboseLine(string message) { WriteYellowAnnotatedLine(message, "VERBOSE"); } public override void WriteWarningLine(string message) { WriteYellowAnnotatedLine(message, "WARNING"); } private void WriteYellowAnnotatedLine(string message, string annotation) { string output = message ?? "null"; var origFgColor = Console.ForegroundColor; var origBgColor = Console.BackgroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.BackgroundColor = ConsoleColor.Black; WriteLine(String.Format(CultureInfo.CurrentCulture, "{0}: {1}", annotation, output)); Console.ForegroundColor = origFgColor; Console.BackgroundColor = origBgColor; } public override void WriteProgress(long sourceId, ProgressRecord record) { throw new NotImplementedException(); } public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions) { if (String.IsNullOrEmpty(caption) && String.IsNullOrEmpty(message) && descriptions.Count > 0) { Console.Write(descriptions[0].Name + ": "); } else { this.Write(ConsoleColor.DarkCyan, ConsoleColor.Black, caption + "\n" + message + " "); } var results = new Dictionary<string, PSObject>(); foreach (FieldDescription fd in descriptions) { string[] label = GetHotkeyAndLabel(fd.Label); this.WriteLine(label[1]); string userData = Console.ReadLine(); if (userData == null) { return null; } results[fd.Name] = PSObject.AsPSObject(userData); } return results; } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) { throw new NotImplementedException(); } public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) { throw new NotImplementedException(); } public override int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice) { // Write the caption and message strings in Blue. this.WriteLine(ConsoleColor.Blue, ConsoleColor.Black, caption + "\n" + message + "\n"); // Convert the choice collection into something that is // easier to work with. See the BuildHotkeysAndPlainLabels // method for details. string[,] promptData = BuildHotkeysAndPlainLabels(choices); // Format the overall choice prompt string to display. var sb = new StringBuilder(); for (int element = 0; element < choices.Count; element++) { sb.Append(String.Format(CultureInfo.CurrentCulture, "|{0}> {1} ", promptData[0, element], promptData[1, element])); } sb.Append(String.Format(CultureInfo.CurrentCulture, "[Default is ({0}]", promptData[0, defaultChoice])); // Read prompts until a match is made, the default is // chosen, or the loop is interrupted with ctrl-C. while (true) { this.WriteLine(sb.ToString()); string data = Console.ReadLine().Trim().ToUpper(CultureInfo.CurrentCulture); // If the choice string was empty, use the default selection. if (data.Length == 0) { return defaultChoice; } // See if the selection matched and return the // corresponding index if it did. for (int i = 0; i < choices.Count; i++) { if (promptData[0, i] == data) { return i; } } this.WriteErrorLine("Invalid choice: " + data); } } #region IHostUISupportsMultipleChoiceSelection Members public Collection<int> PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, IEnumerable<int> defaultChoices) { this.WriteLine(ConsoleColor.Blue, ConsoleColor.Black, caption + "\n" + message + "\n"); string[,] promptData = BuildHotkeysAndPlainLabels(choices); var sb = new StringBuilder(); for (int element = 0; element < choices.Count; element++) { sb.Append(String.Format(CultureInfo.CurrentCulture, "|{0}> {1} ", promptData[0, element], promptData[1, element])); } var defaultResults = new Collection<int>(); if (defaultChoices != null) { int countDefaults = 0; foreach (int defaultChoice in defaultChoices) { ++countDefaults; defaultResults.Add(defaultChoice); } if (countDefaults != 0) { sb.Append(countDefaults == 1 ? "[Default choice is " : "[Default choices are "); foreach (int defaultChoice in defaultChoices) { sb.AppendFormat(CultureInfo.CurrentCulture, "\"{0}\",", promptData[0, defaultChoice]); } sb.Remove(sb.Length - 1, 1); sb.Append("]"); } } this.WriteLine(ConsoleColor.Cyan, ConsoleColor.Black, sb.ToString()); var results = new Collection<int>(); while (true) { ReadNext: string prompt = string.Format(CultureInfo.CurrentCulture, "Choice[{0}]:", results.Count); this.Write(ConsoleColor.Cyan, ConsoleColor.Black, prompt); string data = Console.ReadLine().Trim().ToUpper(CultureInfo.CurrentCulture); if (data.Length == 0) { return (results.Count == 0) ? defaultResults : results; } for (int i = 0; i < choices.Count; i++) { if (promptData[0, i] == data) { results.Add(i); goto ReadNext; } } this.WriteErrorLine("Invalid choice: " + data); } } #endregion private static string[,] BuildHotkeysAndPlainLabels(Collection<ChoiceDescription> choices) { // Allocate the result array string[,] hotkeysAndPlainLabels = new string[2, choices.Count]; for (int i = 0; i < choices.Count; ++i) { string[] hotkeyAndLabel = GetHotkeyAndLabel(choices[i].Label); hotkeysAndPlainLabels[0, i] = hotkeyAndLabel[0]; hotkeysAndPlainLabels[1, i] = hotkeyAndLabel[1]; } return hotkeysAndPlainLabels; } private static string[] GetHotkeyAndLabel(string input) { string[] result = new string[] { String.Empty, String.Empty }; string[] fragments = input.Split('&'); if (fragments.Length == 2) { if (fragments[1].Length > 0) { result[0] = fragments[1][0].ToString(). ToUpper(CultureInfo.CurrentCulture); } result[1] = (fragments[0] + fragments[1]).Trim(); } else { result[1] = input; } return result; } } class HostRawUserInterface : PSHostRawUserInterface { public override KeyInfo ReadKey(ReadKeyOptions options) { throw new NotImplementedException(); } public override void FlushInputBuffer() { } public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) { throw new NotImplementedException(); } public override void SetBufferContents(Rectangle rectangle, BufferCell fill) { throw new NotImplementedException(); } public override BufferCell[,] GetBufferContents(Rectangle rectangle) { throw new NotImplementedException(); } public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) { throw new NotImplementedException(); } public override ConsoleColor ForegroundColor { get { return Console.ForegroundColor; } set { Console.ForegroundColor = value; } } public override ConsoleColor BackgroundColor { get { return Console.BackgroundColor; } set { Console.BackgroundColor = value; } } public override Coordinates CursorPosition { get { return new Coordinates(Console.CursorLeft, Console.CursorTop); } set { Console.SetCursorPosition(value.X, value.Y); } } public override Coordinates WindowPosition { get { return new Coordinates(Console.WindowLeft, Console.WindowTop); } set { Console.SetWindowPosition(value.X, value.Y); } } public override int CursorSize { get { return Console.CursorSize; } set { Console.CursorSize = value; } } public override Size BufferSize { get { return new Size(Console.BufferWidth, Console.BufferHeight); } set { Console.SetBufferSize(value.Width, value.Height); } } public override Size WindowSize { get { return new Size(Console.WindowWidth, Console.WindowHeight); } set { Console.SetWindowSize(value.Width, value.Height); } } public override Size MaxWindowSize { get { return new Size(Console.LargestWindowWidth, Console.LargestWindowHeight); } } public override Size MaxPhysicalWindowSize { get { return new Size(Console.LargestWindowWidth, Console.LargestWindowHeight); } } public override bool KeyAvailable { get { return Console.KeyAvailable; } } public override string WindowTitle { get { return Console.Title; } set { Console.Title = value; } } } } '@ } Process { if ($psCmdlet.ParameterSetName -eq "Path") { # In the -Path (non-literal) case we may need to resolve a wildcarded path $resolvedPaths = @($Path | Resolve-Path | Convert-Path) } else { # Must be -LiteralPath $resolvedPaths = @($LiteralPath | Convert-Path) } foreach ($rpath in $resolvedPaths) { Write-Verbose "Processing $rpath" $gzItem = Get-ChildItem $rpath | Write-GZip -Quiet $resourcePath = "$($gzItem.Directory)\Resources.Script.ps1.gz" if (Test-Path $resourcePath) { Remove-Item $resourcePath } Rename-Item $gzItem $resourcePath # Configure the compiler parameters $compilerVersion = 'v3.5' $referenceAssemblies = 'System.dll',([psobject].Assembly.Location) if ($NET40) { $compilerVersion = 'v4.0' $referenceAssemblies += 'System.Core.dll' } $outputPath = $OutputAssembly if (![IO.Path]::IsPathRooted($outputPath)) { $outputPath = [IO.Path]::GetFullPath((Join-Path $pwd $outputPath)) } if ($rpath -eq $outputPath) { throw 'Oops, you don''t really want to overwrite your script with an EXE.' } $cp = new-object System.CodeDom.Compiler.CompilerParameters $referenceAssemblies,$outputPath,$true $cp.TempFiles = new-object System.CodeDom.Compiler.TempFileCollection ([IO.Path]::GetTempPath()) $cp.GenerateExecutable = $true $cp.GenerateInMemory = $false $cp.IncludeDebugInformation = $true if ($IconPath) { $rIconPath = Resolve-Path $IconPath $cp.CompilerOptions = " /win32icon:$rIconPath" } [void]$cp.EmbeddedResources.Add($resourcePath) # Create the C# codedom compiler $dict = new-object 'System.Collections.Generic.Dictionary[string,string]' $dict.Add('CompilerVersion', $compilerVersion) $provider = new-object Microsoft.CSharp.CSharpCodeProvider $dict # Compile the source and report errors $results = $provider.CompileAssemblyFromSource($cp, $src) if ($results.Errors.Count) { $errorLines = "" foreach ($error in $results.Errors) { $errorLines += "`n`t" + $error.Line + ":`t" + $error.ErrorText } Write-Error $errorLines } } }
Pingback: Make-PS1ExeWrapper « MS Tech BLOG
Keith,
I have found this script very useful. I was wondering if you could add functionality to launch the powershell environment in STA mode. Would that be possible and if so, could you share with the world?
Thank you
Brad Blaylock
Love this script! I use it quite often. I modified it slightly to allow for 1) Hiding the console window, and 2) starting the Powershell runspace in STA mode. Thank you for making this available.
Hi,
The following error is coming while using.
————————
PS C:\scripts> .\Make-PS1ExeWrapper.ps1 .\test.ps1 .\test.exe .\arp.ico
C:\scripts\Make-PS1ExeWrapper.ps1 :
357: Unexpected character ‘-‘
410: Unexpected character ‘`’
410: Unexpected character ”’
At line:1 char:25
+ .\Make-PS1ExeWrapper.ps1 <<<< .\test.ps1 .\test.exe .\arp.ico
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Make-PS1ExeWrapper.ps1
——————————–
Can you help me in resolving my error here ?
Thanks,
Naga Suresh
Very cool Keith! I’ve been looking for something just like this.
Brad, could you share how to hide the console window?
When running the following command :
.\Make-PS1ExeWrapper.ps1 .\ByEntityTotal.ps1 .\ByEntityTotal.exe .\app.ico
I have added .\app.ico with my own icon file , and I’m experiencing the following error:
PS C:\Users\sshehadeh\Desktop\PS Scripts> C:\Users\sshehadeh\Desktop\PS Scripts\MAKE EXE.ps1
C:\Users\sshehadeh\Desktop\PS Scripts\Make-PS1ExeWrapper.ps1 :
0: Source file ‘Scripts\app.ico’ could not be found
At C:\Users\sshehadeh\Desktop\PS Scripts\MAKE EXE.ps1:2 char:25
+ .\Make-PS1ExeWrapper.ps1 <<<< .\ByEntityTotal.ps1 .\ByEntityTotal.exe .\app.ico
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Make-PS1ExeWrapper.ps1
Kieth,
I am encountering an issue when copying this over to a script editor.
Would you happen to have this script in a txt format?
Thanks
hello man ! well ,your script seems to be really nice, but i have problems when i try to make an exe with my scripts
1) i had to replace
$dict.Add(‘CompilerVersion’,’v3.5`) by $dict.Add(‘CompilerVersion’,’v3.5′)
2)
when i do
.\Make-PS1ExeWrapper.ps1 .\ExtracteurAdaptateurVisualisationMigration.ps1 .\MyScript.exe .\logo.bmp
I have this problem
—————————————————————
D:\wamp\www\ganesha\formation\Make-PS1ExeWrapper.ps1 :
357: Unexpected character ‘–’
410: Unexpected character ‘‘’
410: Unexpected character ‘’’
At line:1 char:25
+ .\Make-PS1ExeWrapper.ps1 <<<< .\ExtracteurAdaptateurVisualisationMigration.ps1 .\MyScript.exe
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Make-PS1ExeWrapper.ps1
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i am just a beginner on powershell, but i need absolutely to pu it in EXE. I am french, could you help me on this probleme or give me a link ?
thank you
I too have had the same issue as Brad Blaylock and cass, what you have done is really quite good, well beyond me, could you pls let me know how to correct the issue.
Regards,
Rick
The newly uploaded version of the script will now also set the runspace’s apartment state to STA (if you specify the -STA switch).
Hi,
What code has to be added for hidding the console windows?
Cheers
Hi,
really nice !! but how do you hide the windows console ?
Nice, i have to try it
I’ve looked at this a bit and since it is a console app it is better to use a start mechanism that hides the console app like Start-Process myscriptapp.exe -WindowStyle Hidden. The other thing that could be done is within the Main entry point is to try to grab the MainWindowHandle and pinvoke the ShowWindow(handle, SW_HIDE) api. But the worry there is that the window may appear briefly before hiding. Hence the recommendation to use a command to start the app that hides the window from the very start.
I love the idea but when tryign to actually use it, I got error message saying that the make script can not be loaded as it is not digitally signed.
My execution policy is set to remoteSigned.
Can you help out? Thanks
Unblock the script file. Right click on the script file in Windows Explorer, select Properties and on the General tab there should be an Unblock button in the lower right. Press that button and try running the script again.
I got the error message that involve Write-GZip. How can I fix it? Please hep! The error as folllow:
The term ‘Write-GZip’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At D:\Make-PS1ExeWrapper.ps1:647 char:52
+ $gzItem = Get-ChildItem $rpath | Write-GZip <<<< -Quiet
+ CategoryInfo : ObjectNotFound: (Write-GZip:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
You need to install the PowerShell Community Extensions to get this cmdlet. You can download it from http://pscx.codeplex.com
Pingback: POWERSHELL | COPIERNETEYE
Just wondering – should this be creating an EXE as well as a PDB file?
Yes but make sure you grab the script from the download link instead of trying to copy it from the mess WordPress made out of the source.
Hi,
really nice work! One question: is it possible to pass on parameters extended to the executable commandline so they can actually be used by the wrapped script?
Yes, see the updated blog post above and the comment response to David Halliday.
I have been using this a bunch, but am in a situation where i need to pass a variable into the exe. Is there anything that can be done to get the exe to accept that variable?
Thanks
Go to the SkyDrive download area and there is now a new script called Make-PS1ExeWrapperWithArgs.ps1. It only takes positional parameters at this point but it does seem to handle that well:
param($a, [int]$b, [DateTime]$c)
"`$a is $a"
"`$b is $b"
"`$c is $c"
Results in this when executed:
PS C:\> .\foo.exe 'hello world' 45 (get-date)
$a is hello world
$b is 45
$c is 03/04/2012 18:18:55
Keith,
It works great!
Thanks for the update
Hello How to install Gzip? I’m download arhive Pscx and when i must unzip it?
And How hide console?
Use the Windows Explorer ability to extract the files in the PSCX zip. Be sure to unblock the ZIP file first before extracting into your PowerShell modules directory. Regarding how to hide the console, keep in mind that the script builds a console EXE program. You control whether or not that console is displayed in terms of how you launch the EXE. If you use PowerShell’s Start-Process cmdlet there is a WindowStyle parameter – pass it the value ‘Hidden’.
rkeithhill
Thank you! but I do not understand how to hide the console window … I have a script with Object Windows.Forms. and I need to see the GUI interface was not visible to the console window … Lika a standard windows program.
Pingback: Powershell: Prevent Users To View and Change Input or Config Files That Are Used by a Script | Tao Yang's System Management Blog
Thanks a lot for your huge job.
It’s great. Any zip with all code??, its more clear.
Yes. See the 6-21 update at the top of the post.
Hi Keith,
I was able to use your code and its helpful in creating the exe. But i am seeing the below issue when i try to hide the console window. I tried and made it hidden by adding it in the begin block as below
Begin {
Set-StrictMode -Version latest
Start-Process MyScript.exe -WindowStyle Hidden
$MainAttribute = ”
$ApartmentState = ‘System.Threading.ApartmentState.MTA’
if ($Sta)
{
$MainAttribute = ‘[STAThread]’
$ApartmentState = ‘System.Threading.ApartmentState.STA’
}
But see the below error when i execute it. Can u please help.
D:\Make-PS1ExeWrapperWithArgs.ps1 :
0: Could not write to output file ‘d:\MyScript.exe’ — ‘The process cannot access the file because it is being u
sed by another process. ‘
At line:1 char:33
+ .\Make-PS1ExeWrapperWithArgs.ps1 <<<< .\MyScript.ps1 .\MyScript.exe .\app.ico -Sta
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Make-PS1ExeWrapperWithArgs.ps1
That’s not going to work because the code in that Begin block executes only as a part of “creating/compiling” the EXE. See my response to other comments on using Start-Process on the generated EXE with -WindowStyle Hidden.
Hi great script.
Can you tell me were I can add the -WindowStyle Hidden option so that I don’t get the command windows when running my EXE.
Thanks
This script creates a console executable that internally hosts the engine piece of PowerShell. If you don’t want this window to appear then you have to start it in a way you would any other exe so that it is hidden. Something like this should work. In place of cmd.exe, substitute the exe you created with this script. You can get rid of the -ArgumentList parameter unless your EXE takes args.
Start-Process cmd.exe -WindowStyle Hidden -ArgumentList '/c ipconfig.exe > c:\users\keith\ipconfig.txt'
Keith you are a ‘legend’ for producing this amazing powershell script and even more so for sharing it. You rock man!
Thanks for the kind words! Much appreciated.
This is a great script and works perfectly, my program runs great and I have found no after affects at all. I know you are tired of the question but I am also trying to hide the console window. I have a large (6000+ lines) powershell program with a GUI that runs and I need to see it until I exit the program. Originally I had a cmd file that started my program and hid the console window allowing only the GUI to show, but I can’t seem to get it to work with the exe compiled version. The above Start-Process method hides both windows, I only need to hide the powershell console window. I am thinking that would have to be added to the Make-Ps1ExeWrapper script some how, or if there is another way I would love to hear it. Anyways sorry for bothering you with this same question, hopefully you are in a good mood today. 🙂
Hi David,
Check out my answer to this StackOverflow question: http://stackoverflow.com/questions/11942032/unhiding-a-powershell-window-thats-been-run-with-windowstyle-hidden You could implement this in your script to quickly hide the console window. Perhaps you would have better luck with this approach (ie the GUI window would hopefully stay visible).
I cannot thank you enough, it works exactly like I wanted it to! I open it up and the console window briefly flashes and then is hidden leaving the GUI visible, awesome! My original post had disappeared from your blog so I had thought you were aggravated with it and deleted it. You are the friggin man! I have been learning powershell over the last few months and have found multitudes of other posts you have made and they have helped me greatly, thank you!
Hi, I have a script that contains this piece of code:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://EexchangeServer/PowerShell/ -Authentication Kerberos -ea:stop
Import-PSSession $Session
Script itself works fine.
I’ve created an EXE from this script but when I tryed to run that EXE it threw an error:
Import-PSSession : The method or operation is not implemented.
Hi Dmitriy,
It is hard to tell what could be the problem but I would suspect it is something about the way the PowerShell runspace is configured in the generated EXE. It is a pretty minimal setup. Have you tried appending “?serializationLevel=Full” to the ConnectionUri? There’s also some interesting reading in these two blog posts:
http://blogs.msdn.com/b/akashb/archive/2010/03/25/how-to-migrating-exchange-2007-powershell-managed-code-to-work-with-exchange-2010.aspx
http://blogs.msdn.com/b/dvespa/archive/2010/02/25/local-runspaces-are-not-supported-in-exchange-2010.aspx
I don’t think I need to create a remote runspace in the exe since you’re script is importing the cmdlets from the server but I’ll check some of my MVP buddies.
I found the problem of loading external modules.
(In my case Exchange Online).
Apparently you need to implement:
– WriteProgress
– ReadLineAsSecureString
– PromptForCredential
– ReadKey
Surely this is no longer useful to you, but perhaps may be of interest to others.
Regards
Hi, Thanks for this great script. One question, How easy would it be to decompile one of these exe’s? There are some purposes I would like to use these where i would like the script contents to remain resonably secure.
Thansk
Greg
The script is stored as a ZIP that is added as an embedded resource in the EXE. It would not be hard to extract and unzip. In general, protecting the viewing of the script is going to be hard because at some point it has to be provided to PowerShell in a way that PowerShell can understand it. Right now, it gets provided as a string (script) after extracting the resource and unzipping it.
What exactly are you trying to protect? Intellectual property in the script? Or does the script contain a secret e.g. password? If it is the latter, there are other ways to secure passwords like using DPAPI.
Hi,
Thank you very much for your sharing. When I try it, I found this error:
Exception calling “CompileAssemblyFromSource” with “2” argument(s): “Compiler executable file csc.exe cannot be found.”
Would you please help me? I really need to work on this and your solution is really needed.
Looking forward to hearing from you soon
This one is solved. But I have a new one. After I successfully create the Setup.exe file, I run it and got this error:
The term ‘PrintMessageAndExit’ is not recognized as the name of a cmdlet, function, script file, or operable program.
That function/command doesn’t appear in the Make-PS1ExeWrapper script. I’m guessing that is in your script. You’ll need to debug your script before wrapping it in an exe.
Thank you very much for your reply.
Yes, that function is in my own script. However, It works completely fine alone.
By the way, I use Windows 8 and I already have a complete version of visual studio 2012.
You need to have the C# compiler installed. I think that used to require you have at least .NET installed – if not the .NET SDK. You could also install the free Visual Studio Express and get the C# compiler.
Link appears to be broken..:(
Which link? Both the link to the script on SkyDrive and the PSCX link work.
I am having an issue running the downloaded scripts. once I get the PSCX module loaded and run your script i am getting the following error:
.\Make-PS1ExeWrapperWithArgs.ps1 :
248: The type ‘System.Dynamic.IDynamicMetaObjectProvider’ is defined in an assembly that is not referenced. You
must add a reference to assembly ‘System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.
At line:1 char:1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Make-PS1ExeWrapperWithArgs.ps1
Any ideas what might be causing this?
Which version of PSCX did you load? You can check with $pscx:version.
Major Minor Build Revision
—– —– —– ——–
3 0 4676 33986
Same error for me 😦 Any help??
Use the -NET40 parameter on the script.
I am getting the same exact issue as dorou and my $pscx:version is
Major Minor Build Revision
—– —– —– ——–
2 1 4676 34063
Error is:
C:\Users\sganesh\Documents\Scripts\Practice\Add_User\Make-PS1ExeWrapper.ps1 :
244: The type ‘System.Dynamic.IDynamicMetaObjectProvider’ is defined in an assembly that is not referenced. You
must add a reference to assembly ‘System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’.
At line:1 char:1
+ .\Make-PS1ExeWrapper.ps1 .\Add_User_Final_Nov_2012.ps1 .\AddUserScript.exe .\add …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Make-PS1ExeWrapper.ps1
Pingback: PS Fab:> PowerShell blog
Hi There,
I am getting the same error as Dorou and the same Pscx version. I had this working ok under windows7/pscx 2.1.0 but now on windows8/pscx 3.0. Any ideas?
Thanks
Greg
PowerShell 3.0 and more specifically System.Management.Automation.dll is compiled against .NET 4.0 which requires a reference to System.Core.dll. In order to fix this issue just specify the -NET40 parameter.
Thanks, Make-PS1exewrapperwithArgs.ps1 with -net40 worked perfectly 🙂
How do I use the -NET40 parameter?
It is a switch parameter to the Make-PS1ExeWrapper script e.g.: Make-Ps1ExeWrapper.ps1 foo.ps1 -out foo.exe -net40
Thank you. I used Make-PS1exewrapperwithArgs.ps1 with -net40 and it works. It does not work with the file without “withArgs”.
Has anyone run into this problem? Script execution finishes without error but there isn’t any output; no exe file is created. With verbose on i get this on the console.
.\Make-PS1ExeWrapperWithArgs.ps1 \tools\Import.ps1 \tools\Import.exe
VERBOSE: Processing D:\tools\gtfs\Import.ps1
VERBOSE: ProcessFile: Import.ps1
VERBOSE: WritePath inputPath -> outputPath
VERBOSE: Excluding D:\tools\Import.gz
VERBOSE: Opening GZip stream.
VERBOSE: New input: D:\tools\Import.ps1
VERBOSE: WriteStream input -> output
VERBOSE: Excluding D:\tools\Import.ps1
VERBOSE: Closing GZip stream.
Dumping $results from line 698 of ‘Make…withargs’ yeilds this: Apparently the compiler can fail without populating the ‘errors’ collection’.
PS D:\temp\test> .\Make-PS1ExeWrapperWithArgs.ps1 .\import.ps1 .\import.exe
VERBOSE: Processing D:\temp\test\import.ps1
VERBOSE: ProcessFile: import.ps1
VERBOSE: WritePath inputPath -> outputPath
VERBOSE: Excluding D:\temp\test\import.ps1.gz
VERBOSE: Opening GZip stream.
VERBOSE: New input: D:\temp\test\import.ps1
VERBOSE: WriteStream input -> output
VERBOSE: Excluding D:\temp\test\import.ps1
VERBOSE: Closing GZip stream.
TempFiles : {}
Evidence :
CompiledAssembly :
Errors : {}
Output : {D:\Tools\test> “C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe” /t:exe /utf8output /R:”System.dll”
/R:”C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll” /out:”D:\
temp\test\import.exe” /D:DEBUG /debug+ /optimize- /res:”D:\temp\test\Resources.Script.ps1.gz” “C:\Users\..\AppData\Local\Temp
\qyzha8ud.0.cs”, , }
PathToAssembly : D:\temp\test\import.exe
NativeCompilerReturnValue : -1073741502
…
Well after trying a couple things i figured it out. I didn’t unblock the Pscx module before i expanded it. That meant when i loaded it i had to answer the ‘Run this this time’ question for each thing in the module. I did but that apparently doesn’t work as well as you would like. There is still something different about answering all those questions in the affirmative and unblocking the content before extracting.
Hope this helps someone else.
and thanks for this. more than for just the output though. your solution has a general purpose solution to potentially more than just this problem.
I’m not sure where you’re getting your information, but
good topic. I needs to spend some time learning much more or understanding more.
Thanks for wonderful info I was looking for this info for my mission.
Hello Keith, when running the compiled exe (PS 3.0) it wants to interprete the comment lines from the script resulting in errors like
System.Management.Automation.CmdletInvocationException: Das Laufwerk wurde nicht gefunden. Ein Laufwerk mit dem Namen “” ist nicht vorhanden.
How can this be?
Hi Richard, from that error message how do you know the problem lies with comments being interpreted as script?
Thank you very much for the great script! Awesome!
I have one more question. If my script which I try to compile uses external modules, which are imported to the main script with “Import-Module” statement, how is it possible to pack them into the executable too? Is there a predefined way to do that?
The modules are normally in my “%USERPROFILE%\Documents\WindowsPowerShell\Modules” directory.
The problem is, that the executable throws error messages saying the module was not found about the statements from that imported module if I start it on the other PC.
Thanks in advance!
No there isn’t a way to package modules with the primary script. You’ll have to make sure the modules are installed on the target machines. It would get kind of ugly to add that support to the Make-PS1ExeWrapper script. You use “Import-Module module_name” and that expects to find the specified module in a well-known path (unless you plan to update the PSModulePath environment variable on the target machines). This means that the EXE would need to unpack the required modules from embedded resources and then copy them to one of the well known paths (retaining the module dir structure). While perhaps doable, I don’t think this is a direction I want to take – at this time.
Pingback: Summary of 2nd Dutch PowerShell User Group – DuPSUG meeting with additional resources | blog.bjornhouben.com
Pingback: PowerShell-Skripte in Exe-Dateien einbetten | Peters's PowerShell Blog
This script is amazing. Thank you for making it. I do have a question. In my script, I am using:
$SCRIPTPATH = Split-Path -parent $MyInvocation.MyCommand.Definition
to determine the script path. It fails now in the EXE. How can I call external files in the same folder as the exe? Thank you in advance.
It is best to use something like:
$exePath = [Reflection.Assembly]::GetExecutingAssembly().Location
Hello Keith
Great utility script, thanks very much. One last thing that would be nice
when the my script runs (once it has been compiled to an exe) the black console window appears in the background (my script is a WPF script so uses a GUI), is it therefore possible to add the PowerShell option -WindowStyle Hidden so no PowerShell window appears when running the script
Thank you very much
AAnotherUser
Hello Keith,
While I want to thank you for this amazing script, I am stuck at an issue. The script version works fine and does my job. But when i compile my script to EXE, it fails rather.
In the script I am trying to fetch list of all RAW partitions. When i run the bare command or through script, it passes. But with executable, it fails. Unable to understand, why.
Also, this generates two .PDB file. Is that required? Can I just delete it and use the .EXE file?
You don’t need to distribute the PDB files. Those are only needed for debugging. How does it fail when it runs as an executable?