How To Make PowerShell EXEs Without External Dependencies (Tutorial)

Learn how to handle external dependencies when converting PowerShell scripts into executables with PS2EXE. This approach simplifies deployment for PowerShellers looking to create robust, standalone executables.

Brien Posey

October 28, 2024

11 Min View
ITPro Today

In this video, Brien Posey answers a viewer’s question about external dependencies when converting PowerShell scripts into executable files via PS2EXE. He explains how to manage dependencies, such as image files, by embedding them directly within the PowerShell script. By the end, you’ll understand how to create a fully portable PowerShell executable without relying on external files.

For more PowerShell tips, subscribe to ITPro Today’s YouTube channel.

The transcript below has been edited for length and clarity.

Transcript:

Brien Posey: Recently, somebody sent me an email asking an interesting question about a video I created a few months ago. That video is about using PS2EXE, a utility for turning PowerShell scripts into executable files. If you haven't checked out this video, I encourage you to do so. 

Now, the question was about external dependencies. Let's say, for example, that a PowerShell script references an external file. What do you do in a situation like that if you're trying to turn that PowerShell script into an executable? 

Well, one thing you can do is continue to allow that executable to reference the outside file. However, if you plan on distributing the executable, you must consider how to guarantee the dependency files will remain in place. Typically, this will mean creating an installation package so that when you deploy the script, it installs all the dependencies. 

Related:Building Graphical PowerShell Tools: A Three-Part Guide

Another thing you can do (and probably can't for all file types, but you can do it for at least some) is package the dependencies into the PowerShell script. That sounds a little bit weird, but it works out well, and that's what I want to show you how to do in this video. 

An Example PowerShell Script With an External Dependency

For this video, I've created several different PowerShell scripts. One of those scripts is depend.ps1. It is a basic PowerShell script that has an external dependency. I also have a file called logo.jpg. The logo.jpg file is the logo from my website. The depend.ps1 script depends on this logo.jpg file.

You can see what the script looks like. It is a basic script.  

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
 
$Form = New-Object system.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size(800,600)
$Form.StartPosition = "CenterScreen"
 
$JpgPath = "C:\Scripts\Portable\Logo.jpg"
$JpgImage = [System.Drawing.Image]::FromFile($JpgPath)
 
$PictureBox = New-Object System.Windows.Forms.PictureBox
$PictureBox.Size = $JpgImage.Size
$Form.Controls.Add($PictureBox)
$PictureBox.Image = $jpgImage
 
$Form.ShowDialog ()

I'm starting the script by loading a couple of dependencies, and these are some dependencies needed to create a GUI in PowerShell

Next, I have a few lines of code that create what's known as a form. You can think of a form as being like a window. It essentially refers to the GUI interface itself. 

From there, I have two lines. The first one defines a variable called $JpgPath, and I'm pointing the path to C:\Scripts\Portable\Logo.jpg. That's the .jpg file that I showed you a moment ago. The second line creates a variable called $JpgImage, and I'm setting that image equal to System.Drawing.Image. Then, I'm pulling it from the file defined in $JpgPath – in other words, the C:\Scripts\Portable\Logo.jpg file. 

Related:How To Automate PowerShell Scripts With Windows Task Scheduler

The next thing that I'm doing is defining a PictureBox. In a PowerShell GUI environment, a PictureBox displays an image. In this case, we're displaying the .jpg file. So, I'm defining the PictureBox, and that PictureBox is going to be a New-Object System.Windows.Forms.PictureBox.

From there, I'm setting the $PictureBox.Size equal to the size of the $JpgImage. 

Next, I'm adding the $PictureBox to the form I created. You can see that I'm referencing $Form, then Controls.Add. So, I'm adding something to the form: the $PictureBox.

Finally, I'm setting $PictureBox.Image to the $jpgImage. That will take that image file and display it within the picture box. 

Then, the last line of code displays the dialog box on the screen.

Let's see what this looks like. I will run the depend.ps1 script in PowerShell. It displays a simple window with my logo in it. 

Incorporating the External Dependency Into the PowerShell Script

I want to now return to the original question that I posed. Let's say I have this PowerShell script and want to turn it into an executable, but I don't want to deal with external dependencies – in this case, the logo file. How would I incorporate that logo file into the PowerShell script? 

Related:How To Use PowerShell for Automated Event Response

Convert the JPG File to a Base64 Encoded String

The first thing that we need to do is convert that logo file into a Base64 encoded text string. You'll notice that I have a script called conversion.ps1. 

My conversion.ps1 script has two commands. 

# Convert the JPG file to a Base64 encoded string
$Base64Image = [Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\Scripts\Portable\Logo.jpg"))
 
#Output the image
$Base64Image | Out-File "C: \scripts\portable\logo.txt"

The first one creates a variable called $Base64Image, and I'm setting it equal to [Convert]::ToBase64String. Then, we're reading all bytes of C:\Scripts\Portable\Logo.jpg. So, I'm reading the Logo.jpg file, taking all the file's bytes, and converting it into a Base64 image.

The next thing that I'm doing is writing that $Base64Image out to a file. That file is going to be called C: \scripts\portable\logo.txt. 

Essentially, I'm creating a text string based on the contents of my logo file, and then I'm outputting that to a .txt file. 

There's no visible output when I run the script, but you’ll notice logo.txt in File Explorer. If I open logo.txt, you can see what my image file looks like converted to Base64 format. It is a lot to take in. What we're going to be doing is incorporating this into our PowerShell script. 

Add the Base64 Encoded Image String

I have two other script files. I have one named portable.ps1. Portable.ps1 is a script in which I've already incorporated the .txt file.

However, because this logo.txt file is a lot to take in, I want to show you Empty Portable.ps1. It essentially contains the code we need without containing all the Base64 encoded imagery within the .txt file. 

# Base64 encoded image string
$Base64Image = @"
<Your Base64 Encoded String Here>
"@
 
# Decode the Base64 string to a byte array
$ImageBytes = [Convert]::FromBase64String($Base64Image)
 
# Create a MemoryStream from the byte array
$MemoryStream = New-Object System.IO.MemoryStream
$MemoryStream.Write($ImageBytes, 0, $ImageBytes.Length)
$MemoryStream.Seek(0, [System.IO.SeekOrigin]::Begin)
 
# Create an image object from the MemoryStream
$JpgImage = [System.Drawing.Image]::FromStream($MemoryStream)
 
 
# The Code Below Was Not Modified
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
 
$Form = New-Object system.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size (800,600)
§Form.StartPosition = "CenterScreen"
 
$PictureBox = New-Object System.Windows.Forms.PictureBox

The first thing we do in the Empty Portable.ps1 file is add the Base64 encoded image string. Now, that string doesn't exist in this file because I was trying to keep things simple, but you would usually take the contents of that .txt file and paste it in place of where it says, “Your Base64 Encoded String Here.” You'll notice that we're creating a variable called $Base64Image. This variable will be an array, as designated by the @ symbol. Then, we will paste in the contents of the .txt file. 

After we've created that $Base64Image variable, we must decode the Base64 encryption. I've set up a variable called $ImageBytes equal to [Convert]::FromBase64String. And what are we converting? We're converting the ($Base64Image). 

Now that we've converted the image into something usable, the next thing we do is create a MemoryStream. MemoryStream is a type of .NET object. You'll notice that I've set up a variable called $MemoryStream equal to New-Object System.IO.MemoryStream. So, once we've created that MemoryStream, we must write the contents of the $ImageBytes variable – that's the decoded Base64 image – to the MemoryStream, using this line: $MemoryStream.Write($ImageBytes, 0, $ImageBytes.Length). Then, the next thing that we do is use MemoryStream.Seek. You can see that I'm referencing the $MemoryStream variable, and then I've appended .Seek. Then we have: (0, [System.IO.SeekOrigin]::Begin). This line sets a pointer so that we're looking at the beginning of the MemoryStream. 

Now, if some of this MemoryStream stuff seems foreign to you, you don't have to worry about it. You can use these three lines of code as they are written here without digging into anything.

Create an Image Object From the MemoryStream

Next, I'm setting up a variable called $JpgImage. Now, if I could switch back to my original script for a moment, you'll recall that this script also had a variable called $JpgImage. In that case, I was setting $JpgImage equal to System.Drawing.Image. I'm reading that image from a file defined by the $JpgPath, and the $JpgPath pointed to C:\Scripts\Portable\Logo.jpg. 

So, I'm doing things a little differently in the other file. Here, I'm still using the $JpgImage variable and setting it equal to System.Drawing.Image. However, rather than reading the image from a file, I'm reading the image FromStream, and that stream is, of course, my $MemoryStream that I've created. 

The Code Below Was Not Modified (Not Entirely True)

Below that, a comment says the code below is unmodified. That's not entirely true. In my original script, we had these two lines of code that define the $JpgPath and the $JpgImage, and I don't need those lines of code anymore. So, you can see that those two lines of code do not exist in this section. Aside from that, I haven't made any changes. Everything here is just the same. The only difference is that we're reading our .jpg file from that MemoryStream instead of reading it from a file. 

Finalizing the Script and Running the Executable

So, what does this look like when we put it all together? As I mentioned, I already created a file called portable.ps1 that incorporates the script you just saw, plus the logo.txt file. 

In the portable.ps1 file, you can see I’ve set my $Base64Image variable and then pasted the contents of that .txt file. If I scroll down, you can see where that definition ends. Then, everything that comes after that is exactly like what I showed you in the other script. 

So, I’ll switch over to PowerShell and run the script. I'm going to type: 

./portable.ps1 

I'll run the script, and you can see that my logo appears within the window. This time, I'm not referencing any outside files. Everything that I need is within that PowerShell script. That way, if I use the PS2EXE utility to convert the script into an executable, I don't have to worry about external dependencies. Everything I need is in the script. At that point, the executable file is fully portable. I can move it from machine to machine without worrying about installing anything or ensuring external dependencies are in place. 

Hopefully, that's something that's going to be helpful to you. 

About the Author

Brien Posey

Brien Posey is a bestselling technology author, a speaker, and a 20X Microsoft MVP. In addition to his ongoing work in IT, Posey has spent the last several years training as a commercial astronaut candidate in preparation to fly on a mission to study polar mesospheric clouds from space.

https://brienposey.com/

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like