ImageBuilder Deep Dive, Part 3: The Power of the Shell

Let's start this third and last part of my ImageBuilder series with a short retrospective summary: In part one we introduced an easily adaptable script for building a customized ESXi 5.0 installation ISO. In the second part we looked closer at the basic cmdlets and learnt some new advanced ones.

In this final post we will walk through an improved and more universal version of the first script. It is more about Powershell in general than about ImageBuilder, so it will definitely help if you are already somewhat familiar with Powershell. But even if this is not the case you can still benefit by just using the improved script as it is. It took me some time to write it, because - according to Powershell - I was an absolute beginner when starting. I learnt a lot while developing it, but it is probably still not perfect ...

You can just download a copy of the script and use it. For the curious here is the listing:
# ESXi-Customizer-PS.ps1 - a script to build a customized ESXi installation ISO using ImageBuilder
# Version: 1.0
# Author: Andreas Peetz ([email protected])

    [string]$obDir = $(Split-Path $MyInvocation.MyCommand.Path),
    [string]$isoDir = $(Split-Path $MyInvocation.MyCommand.Path),
    [switch]$hp = $false,
    [switch]$help = $false

$AccLevel = @{"VMwareCertified" = 1; "VMwareAccepted" = 2; "PartnerSupported" = 3; "CommunitySupported" = 4}

function AddVIB2Profile($vib) {
    if ($AccLevel[$vib.AcceptanceLevel.ToString()] -gt $AccLevel[$MyProfile.AcceptanceLevel.ToString()]) {
        write-host -nonewline (" [New AcceptanceLevel: " + $vib.AcceptanceLevel + "]")
        $MyProfile.AcceptanceLevel = $vib.AcceptanceLevel
    Add-EsxSoftwarePackage -SoftwarePackage $vib -ImageProfile $MyProfile | Out-Null 
    if ($?) { " [OK]" } else { " [FAILED]" }

# Write info and help if requested
write-host "`nScript to build a customized ESXi installation ISO using ImageBuilder"
if ($help) {
    write-host "Optional parameters:"
    write-host "   -help         : display this help"
    write-host "   -obDir <dir>  : directory of Offline bundles to add (default = script directory)"
    write-host "   -isoDir <dir> : directory to store the customized ISO (default = script directory)"
    write-host "   -hp           : add packages from the HP VIBs Depot (default = no)`n"
} else {
    write-host "(Call with -help for instructions)"

# Load the ImageBuilder Snapin (if not already loaded)
if (!(Get-PSSnapin -name VMware.ImageBuilder -ErrorAction:SilentlyContinue)) {
    if (!(Add-PSSnapin -PassThru VMware.ImageBuilder)) {
        # Error out if loading fails
        write-host "FATAL ERROR: Cannot load the ImageBuilder Snapin. Is the PowerCLI installed?"

# Add the VMware ESXi base depot
write-host -nonewline "`nConnecting the VMware ESXi base depot ..."
if ($baseDepot = Add-EsxSoftwareDepot {
    write-host " [OK]"
} else {
    write-host "`n   FATAL ERROR: Cannot add VMware base Online depot. Please check your internet connectivity and/or proxy settings!"

# Get the newest ImageProfile from the base depot
$LatestIP = (Get-EsxImageProfile "ESXi-5.0.0-20*-standard" | Sort-Object -Descending Name)[0]
write-host ("Using latest ImageProfile " + $LatestIP.Name)
write-host ("(dated " + $LatestIP.CreationTime + ", AcceptanceLevel: " + $LatestIP.AcceptanceLevel + ",")
write-host ($LatestIP.Description + ")")

# Create your own Imageprofile
$MyProfile = New-EsxImageProfile -CloneProfile $LatestIP -Name ($LatestIP.Name + "-customized") -Description ($LatestIP.Description + " + customizations")

if ($hp) {
    # Add the HP VIBs depot
    write-host -nonewline "`nConnecting the HP Online Depot ..."
    if ($hpDepot = Add-EsxSoftwareDepot {
        write-host " [OK]"
    } else {
        write-host "`n   FATAL ERROR: Cannot add HP Online depot. Please check your internet connectivity and/or proxy settings!"
    $hpDepot.Channels[0] | Get-EsxSoftwarePackage | foreach {
        write-host -nonewline ("   Add VIB " + $_.Name + $_.Version )
        AddVIB2Profile $_

# Loop over Offline bundles
write-host "`nAdding Offline bundles from" $obDir ...
foreach ($oBundle in Get-Item $obDir\*.zip) {
    write-host -nonewline "   Adding" $oBundle ...
    if ($ob = Add-EsxSoftwareDepot $oBundle) {
        write-host " [OK]"
        $ob.Channels[0] | Get-EsxSoftwarePackage | foreach {
            write-host -nonewline "      Add VIB" $_.Name $_.Version
            AddVIB2Profile $_
    } else {
        write-host " [FAILED]`n      Probably not a valid Offline bundle, ignoring."

# Export the Imageprofile into an installation ISO file
$isoFile = $isoDir + "\" + $MyProfile.Name + ".iso"
write-host -nonewline ("`nExporting to ISO file " + $isoFile + ". Please be patient ...")
Export-EsxImageProfile -ImageProfile $MyProfile -ExportToIso -FilePath $isoFile -Force
if ($?) { " [OK]" } else { " [FAILED]" }

write-host "`nAll done."
Let's look at the script line by line:

Line 7 - 12: The script accepts command line parameters that are defined inside a param() statement. One of the possible parameters is the switch -help. If specified the script will print a help text (explaining the rest of the parameters) and exit (see line 25ff.). Another optional switch is -hp which will add the HP Online VIBs depot (s. line 65ff.). Using -obDir you can specify a directory where the script will look for Offline bundle zip files to be added. And with -isoDir you can specify the directory to store the created ISO file. The default for both directories is the path where the script itself is stored (which is determined by $(Split-Path $MyInvocation.MyCommand.Path)).

Line 14: One of the advanced Powershell features is using hash tables like the one that is created here. It contains the string representations of the four possible acceptance levels that a package or an image profile can have. They are assigned a ranking value (1 to 4) that is used to determine what the most restrictive acceptance level is ("VMwareCertified" = 1) as opposed to the least restrictive ("CommunitySupported" = 4).

Line 16 - 23: In these lines we define a Powershell function named AddVIB2Profile that adds a VIB package ($vib) that is passed as an argument to the custom image profile. As a first step it compares the acceptance level of the software package with that of the image profile (line 17) to determine if the latter needs to be lowered in order to accept the package (line 18 - 19). This is where the above mentioned hash table is used. Then it adds the package using the Add-EsxSoftwarePackage cmdlet. As I want the output of the script to be pretty and not garbled by useless information I pipe the output of the cmdlet into out-null. That means that it is just discarded. In line 22 I use the special Powershell variable $? to test whether the previous command was successful or not. This is a basic and the easiest way to add error handling to a Powershell script, and I will use that a lot here.

Line 25 - 36: This is actually the start of the main script. It prints a title line (line 26), then it will either display a help text and exit the script if -help was specified (line 27 - 33), or it will display only a short line explaining that -help can be used for instructions (line 35) and continue. BTW the special characters `n represent a new line when used in Powershell strings.

Line 39 to 45: Here we will check whether the required snapin VMware.ImageBuilder was already loaded (e.g. because you used the PowerCLI desktop shortcut to start your Powershell session). If not then we try to load it with Add-PSSnapin (line 40). If this fails (e.g. because PowerCLI is not installed on the computer) then the script errors out, because it depends on the ImageBuilder snapin being available and loaded.

Line 47 to 54: Now we add the VMware ESXi Online depot to the ImageBuilder session (also with some error handling). Nothing spectacular, we've seen this before.

Line 57 to 60: In line 57 we grab all standard image profiles from the VMware depot, create a sorted array of them and just pick the first one (with index [0]). This is the newest one, because we sorted the array in descending order. In the following lines we output some information about this image profile that is stored in its attributes.

Line 63: Here we create our custom image profile by cloning the latest one from the VMware depot. Its name and description are derived from the original VMware profile's properties.

Line 65 to 78: If the command line switch -hp was specified then we connect the HP Online VIBs depot to the ImageBuilder session (line 68 to 73) and add all included software packages using the AddVIB2Profile function that we defined at the beginning of the script.

Line 81 to 93: The foreach loop starting at line 82 is another essential part of the script code: Here we loop over all zip files that are located in the obDir directory and try to add them as Offline bundles to the ImageBuilder session (line 84). If it succeeds because a zip file is a valid Offline bundle (and not some other sort of file archive) then all software packages that are included in the bundle will be added to the custom image profile, again using our self-defined AddVIB2Profile function (line 88).

Line 96 to 99: Finally we export the custom image profile into an installation ISO. Its file name is derived from the image profile's name.

How do you run this Powershell script (and others)?

Open a plain Powershell window from the Start menu, or use the PowerCLI desktop shortcut to start an already customized Powershell session.

By default Powershell will only execute scripts that were electronically signed for security reasons. This script (and most other useful scripts that you will find in other places) is not signed, so you will need to relax the security check by using the Set-ExecutionPolicy cmdlet like shown in the following screenshot. You can do this for the current user only by specifying -Scope CurrentUser. This will also work if you do not have administrative permissions on the computer. If you have administrative permissions then you can leave the -Scope parameter and make the setting system global. Setting the execution policy needs to be done only once - it will be saved to the registry and reused for future Powershell sessions.

You can just call the script by its name (adding parameters as required). Since the script is from an "untrusted" source you still need to confirm its execution every time you start it:

ESXi-Customizer-PS help screen
Finally this is an example output from a complete script run:

ESXi-Customizer-PS example run
The script that I provide here is fully functional and flexible enough to meet most requirements. However, it is certainly not perfect. E.g. it could have better error handling. If you have any comments or ideas on how to improve it (or if you run into errors when trying it) then please add a comment to this post!

Update (2012-06-07): In the meantime I updated this script to fix errors. Please watch out for the latest version using this download link!


  1. Excellent script! Two suggestions to make it even better and fit a broader range of needs:

    1) I really need to customize the profile name. So allow users to specify an optional switch to set the profile name, otherwise default to what you use now.

    2) Another optional switch that would present a numbered list of the VMware profiles and let the user choose which one to base their customized ISO on. Without the switch it uses your current logic and gets the latest one.

    1. Hi Derek,
      yes, I already thought of your suggestion 2,) and 1) is viable two.
      Also thanks for your comment on the hpvsa driver. Obviously HP screwed the signature of this package. Hopefully they will fix that soon.

    2. Excellent, when can we expect a new version? :-) Keep up the great work..much easier than the manual method.

  2. Also note that the June 4 2012 version of the "hpvsa" driver is NOT signed and if included with your script it will fail writing the ISO image with:

    Export-EsxImageProfile : Could not find a trusted signer.

    hpvsa driver:

    I didn't futz around with the acceptance level, but the 'partner accepted' level fails. Community Supported should work, but I just left the driver out since I didn't need it on our hardware.

    1. You can avoid this error by adding the -NoSignatureCheck switch to the Export-EsxImageProfile command. The latest version of my script adds a parameter for this (-nsc). See

  3. Hey.. thanks for this code snippet. I've been looking for similar stuff. Will have to modify it according to my needs. But thanks for this.. I've had been taking ages to generate this.

    Open port checker

  4. Hey.. thanks for this code snippet. I've been looking for similar stuff. Will have to modify it according to my needs. But thanks for this.. I've had been taking ages to generate this.


***** All comments will be moderated! *****
- Please post only comments or questions that are related to this post's contents!
- Advertising and link spamming will not be tolerated!