In the PowerShell world, we have a similar package repository, the PowerShell Gallery, but no way to simplify the deployment of scripts. Instead, we need to call Install-Module or Save-Module in order to deploy the proper module to machines running our scripts.
Introducing Module Packaging with Merge-Script
Before today, Merge-Script already offered the ability to bundle multiple scripts together by expanding dot sourced scripts into the root source file. This works well for PS1 files but does not support expanding modules. Modules require scoping and often bring in other resources when they are loaded.
As of today, Merge-Script can now bundle modules imported into scripts that use Import-Module. Aside from just expanding the module’s source, it can bundle required assemblies, nested modules, binary modules and dot sourced files used within the root module.
An Example Using PowerSploit
PowerSploit is a popular pen testing module available on the Gallery. Let’s take an example of a script that utilizes PowerSploit’s Invoke-PortScan cmdlet. Here’s a very simple script we will bundle.
Using Merge-Script, we can automatically include the contents of PowerSploit within our script. Merge-Script now offers a ConfigFile parameter that allows for more fine-grained control over the bundling, packaging and obfuscation process. The first step in bundling PowerSploit with our script, is creating a PsPackConfig file. These config files are used to adjust the Merge-Script behavior. The full documentation for these configuration files can be found here.
The configuration file found below enables bundling, including modules, nested modules and required assemblies as well as packaging the script as an executable. The Root specifies the script we want to bundle and the OutputPath specifies the directory to output to.
Finally, we need to execute Merge-Script to produce our bundled and packed executable.
The result of this process is an example.exe that contains both the example.ps1 script and all the contents of PowerSploit. Here is an example of running the executable on a machine that does not have PowerSploit installed.
As shown in the video above, the PowerSploit Invoke-PortScan cmdlet is available in the machine without having the PowerSploit module installed.
All About Module Packaging
Packaging modules is tricky business and depending on what a module does while loading, errors may occur and a module may fail to load or pack correctly. This will improve over time. Both Script and Binary modules are supported. Binary modules are packed as base64 strings and may trigger PowerShell script block logging.
Nested Modules and Scripts
Many modules, such as PowerSploit and Pester, break up their functionality into a series of scripts or nested modules. Merge-Script supports expanding both of these methods, as well as Import-Module and NestedModules, into the output script.
Module scope is maintained by utilizing New-Module to produce the in memory modules. This allows for nested modules to maintain their scope as well.
Unsupported Module Features
Many features of a module are not currently, and may never be, supported by Merge-Script. Type extensions, external help, non-PowerShell assets (aside from referenced assemblies), DSC resources and CD1XML modules.
What’s coming next?
Improvements to module bundling as on the horizon. I encourage you to give it a shot and report any issues on GitHub. Some modules, such as PSCX and Carbon, present errors when loaded through this method and may not run correctly. Please consider this functionality a beta and make sure to thoroughly test your output script before running in production.
Additionally, some optimizations can be made to reduce the size of the output module.
The new version of Merge-Script is already on the PowerShell Gallery.