Make-PS1ExeWrapper

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
        }
    }  
}
This entry was posted in PowerShell 2.0. Bookmark the permalink.

82 Responses to Make-PS1ExeWrapper

  1. Pingback: Make-PS1ExeWrapper « MS Tech BLOG

  2. Brad Blaylock says:

    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

  3. Brad Blaylock says:

    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.

    • NagaSuresh says:

      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

    • ChrisQuestionMark says:

      Very cool Keith! I’ve been looking for something just like this.
      Brad, could you share how to hide the console window?

  4. Samer says:

    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

  5. David Halliday says:

    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

  6. cass says:

    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

  7. Rick says:

    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

  8. rkeithhill says:

    The newly uploaded version of the script will now also set the runspace’s apartment state to STA (if you specify the -STA switch).

  9. alexandre augagneur says:

    Hi,

    really nice !! but how do you hide the windows console ?

  10. Nice, i have to try it

  11. rkeithhill says:

    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.

  12. Avivit says:

    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

    • rkeithhill says:

      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.

  13. Pham Phong says:

    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

  14. Pingback: POWERSHELL | COPIERNETEYE

  15. jpbruckler says:

    Just wondering – should this be creating an EXE as well as a PDB file?

  16. joostman says:

    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?

  17. David Halliday says:

    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

    • rkeithhill says:

      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

  18. Denis says:

    Hello How to install Gzip? I’m download arhive Pscx and when i must unzip it?
    And How hide console?

    • rkeithhill says:

      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’.

      • Denis says:

        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.

  19. Pingback: Powershell: Prevent Users To View and Change Input or Config Files That Are Used by a Script | Tao Yang's System Management Blog

  20. Yann says:

    Thanks a lot for your huge job.

  21. kiquenet says:

    It’s great. Any zip with all code??, its more clear.

    • rkeithhill says:

      Yes. See the 6-21 update at the top of the post.

      • SK says:

        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

      • rkeithhill says:

        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.

  22. Sudowin says:

    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

    • rkeithhill says:

      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'

  23. hecticuk says:

    Keith you are a ‘legend’ for producing this amazing powershell script and even more so for sharing it. You rock man!

  24. David Wilkerson says:

    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. 🙂

  25. Dmitriy says:

    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.

  26. Greg says:

    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

    • rkeithhill says:

      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.

  27. khemry says:

    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

    • khemry says:

      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.

      • rkeithhill says:

        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.

      • Khemry says:

        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.

    • rkeithhill says:

      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.

  28. TrenTech says:

    Link appears to be broken..:(

  29. dorou says:

    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?

  30. Sree says:

    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

  31. Pingback: PS Fab:> PowerShell blog

  32. Greg says:

    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

    • rkeithhill says:

      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.

  33. Robert says:

    How do I use the -NET40 parameter?

  34. Robert says:

    Thank you. I used Make-PS1exewrapperwithArgs.ps1 with -net40 and it works. It does not work with the file without “withArgs”.

  35. dan holmes says:

    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.

    • dan holmes says:

      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.

  36. dan holmes says:

    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.

  37. 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.

  38. Richard says:

    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?

  39. Alexandre says:

    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!

    • rkeithhill says:

      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.

  40. Pingback: Summary of 2nd Dutch PowerShell User Group – DuPSUG meeting with additional resources | blog.bjornhouben.com

  41. Pingback: PowerShell-Skripte in Exe-Dateien einbetten | Peters's PowerShell Blog

  42. Robert says:

    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.

  43. aanotheruser says:

    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

  44. 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?

Leave a reply to rkeithhill Cancel reply