Deploy Photon 2.0 – Part 1

Photon 2.0 is definitely a guest OS that is useful in a VMware vSphere environment. It is light-weight, easy to manage, security hardened and comes with the Docker daemon. 
That last feature makes it an ideal environment to explore new applications, isolated from your live platforms. As an example, quite a few of us got to know PowerShell Core with VMware PowerCLI, while running it in a Docker container, well shielded from our other platforms.

There are numerous articles and blog posts that describe how set up a Photon 2.0 VM, but most of these use the ovftool or the vSphere Web Client to install it. Followed by quite of a bit of editing config files, to have your Photon 2.0 VM running the way you prefer it.
With the latest version of my Invoke-VMScriptPlus function, you can now automate this entire process, the SDDC way! 


Update August 21st 2018

  • Added “reboot” action
  • Added optional Environment variables for the customisation scripts
  • Added Folder field for template destination


As I briefly mentioned in the introduction paragraph, setting up a VM with the Photon OS 2.0, is a two-step process.

  • Deploy the appliance
  • Configure the OS

The first step is rather straight-forward with PowerCLI’s Import-VApp cmdlet.

The second step, configuring the Photon OS, is presented as a manual step in most of the articles I could find. Till now!

With my Invoke-VMScripPlus function, this step can now also be automated. And that automation step is driven by a JSON file. This JSON file is the part that you place under version control.

The following scenario, which I’m currently using, creates a VM with a configured Photon 2.0 guest OS. Then converts that VM into a Template, which can then be used to roll out your VMs.

Create a Photon VM Template

This step is fully automated and driven by a JSON configuration file. In a matter of minutes you can create a new Photon 2.0 template, without even logging on to the machine!

The JSON Configuration file

The objective to have all configuration parameters in a flat text file is of course to be able to easily place it under version control and to easily compare between versions.

The JSON file contains a number of Level-1 entries, each specifying part of the final configuration of the Photon Template.



  • vSphere: describes the Location for the template
  • Template: characteristics of the Template
  • Network: the network configuration inside the guest OS
  • Account: the current and new password for the root account
  • Proxy: the proxy settings, if present
  • Docker: the docker configuration
  • Code: a collection of customisation scripts





  • VMHost: a cluster or an ESXi node where the VM will be installed. If you specify a cluster, the script takes a random ESXi node in that cluster.
  • Storage: a datastorecluster or datastore where the VM will be installed.
  • OvaPath: where the script can find the Photon 2.0 ova file. Note the escaped back-slashes in the path.


  • TemplateName, TemplateNote: self explanatory
  • Folder: a VM type folder where the template shall be stored
  • NumCpu, MemoryGB, HarddiskGB: self explanatory
  • GuestID: has to be other3xLinux64Guest for Photon 2.0
  • CreateTemplate: when set to false, the script will not power off the VM, nor will it convert the VM into a Template. This can be used will fine-tuning the customisation scripts (see later).
  • CustomisationCode: the names of the customisation scripts that will be executed once the VM is installed. See later in the Code section.


Since most of the customization of the Photon guest OS was done when we created the Template, this step requires very little extra customization steps.

I assume these settings all speak for themselves. Nothing out of the ordinary, just you regular network settings. Perhaps one point to note, when you have multiple values for a field, separate them with a space.


Again, quite straight-forward. The account, and the old and new password.


When your environment into which you are installing the VM is located behind a proxy, you can specify the proxy details in this section. This is required to get the Photon repositories working (tdnf), and also the docker repositories,


Only one setting, the default port to access docker.


Under the Code section, you can define a number of customisation scripts, that will be executed inside the Photon 2.0 guest OS. Each entry has a two fixed subfields.

  • Name: the name for this particular customisation script. This is the name that is used in the Template section, in CustomisationCode field.
  • Script: the actual customisation script. Due to restrictions in the JSON syntax, the script must be provided as an array of strings. Each line of the script is an element in this array
  • Environment: an array with environment variables that will be passed to the environment where the customisation script is executed.

In the example above, the customisation script is a one-liner. Hence one string in the Code array. In this example, the Code contains the line to pull the docker container that contains VMware PowerCLI.

There are of course multi-line customisation scripts. In that case the array contains multiple elements. The following example is an excerpt of the docker customisation script. As you notice, the code also contains comment lines and empty lines.

Since we do not want to hard-code values in our customisation scripts, we use a PowerShell style notation to refer to variables defined in other sections in the JSON file.

In line 126 of excerpt above, we use the expression $($paramData.Docker.Port) to refer to the port number we defined in the Docker section. 

To avoid that you have to type in your customisation scripts in this specific JSON format, the following short script can help you convert your regular bash script to JSON notation.

A sample run shows you how you could use the above code.

Will result in the following output, which you can copy/paste into your JSON file.

The New-PhotonTemplate function

Before you can run the New-PhotonTemplate function, you will need two functions that are used in the function. These are

You have multiple options to make these two functions available to the New-PhotonTemplate function.

  • Add them to the .ps1 file where you copied the New-PhotonTemplate function
  • Store the two functions in separate .ps1 files, and dot-source these .ps1 files before calling the New-PhotonTemplate function
  • Create a module, and add the two functions to that module

The Code



Line 36-54: An inline function to have all messages in the same format.

Line 56-75: A reboot the guest OS function. Called when the “reboot” instruction is encountered. See later.

Line 79-80: These lines read the JSON file and convert the data to a PowerShell object

Line 84-88: The vSphere.VMHost field can contain a cluster or an ESXi node. These lines handle those options. In case of a cluster, the function selects a random ESXi node from the cluster.

Line 90-96: The vSphere.Storage field can contain a DatastoreCluster or a Datastore. These lines handle those options. In case of a datastorecluster, the function selects a random datastore from the datastorecluster.

Line 100-110: These lines check if there is already a Template or a VirtualMachine with a name as specified in Template.TemplateName. If there is, the Template or VirtualMachine are removed.

Line 114-130: A straight-forward method to import the Photon OVA as defined in vSphere.OvaPath.

Line 134-146:  Further HW configuration of the VM

Line 148-156: The new VM is powered on, and the function waits till the VMware Tools are responding

Line 162-170: The function changes the root password with the new password defined in Account.NewPassword. For this action the code uses the Set-VMKeystrokes function. William describes this ingenious method in his Automating VM keystrokes using the vSphere API & PowerCLI post, well worth a read!

Line 176-204: The code now runs all the customisation script that are defined in Template.CustomisationCode. The actual customisation scripts are defined in the Code section of the JSON file.

Line 177-182: With reserved name “reboot” you can request a reboot of the guest OS. This is executed in the sequence order with the customisation scripts as defined in the Template.CustomisationCode field.

Line 187,193: With the ExpandString method the script expands all $paramData occurrances in the customisation scripts with the actual values. This allows us to keep the customisation script general and not coded for a specific instance.

Line 192-194: If the customisation script contains environment variables in the Code.Environment field, these are environment variables are passed along with the customisatoin script to the Invoke-VMScriptPlus function.

Line 208-223: Based on the Template.CreateTemplate value (true or false) the VM is converted into a Template. One reason not to convert the VM to a Template, could be that your debugging one or more of the customisation scripts.

Sample Run

Now we have everything in place to create a Photon 2.0 template, based on the  configuration settings and customisation scripts in our JSON file. Since it can be useful to review the output, especially of the customisation scripts, we use the LogFile parameter to specify a file where the function can write the output.

When we call the New-PhotonTemplate with the LogFile parameter, there will be no regular output on the PowerShell console.

The logfile contains useful information, especially if we add statements to our customisation scripts to retrieve information on the components. This excerpt shows some information that was obtained in the docker customisation script.

When all goes well, the New-PhotonTemplate will have created a Template with the Photon 2.0 guest OS.

Known Issues


When you use one of the customisation scripts to upgrade the Photon components (command tdnf upgrade), and the open-vm-tools are in there, the returned information of the script might not be complete. You will miss the ScriptOutput and the ExitCode.

As a workaround, upgrade the open-vm-tools separately before upgrading any of the other components.


Attached to this post are some files that will help you get started. There is an empty, skeleton JSON file and two Photon customisation scripts, one for the network and one for docker.


In Part 2 of this series I will be showing how the Template we just created, can be used to roll out any number of VMs.


Version: 1.1
1.3 KiB
Photon docker customisation script
Photon docker customisation script
Version: 1.0
861.0 B
Photon network customisation script
Photon network customisation script
Version: 1.0
1.1 KiB


    I had an issue with the password change happening way too quickly, causing it to fail. I added in one-second sleeps to mitigate it.


      Strange, I never experienced that.
      You might want to try to update the While-loop before sending the keystrokes with this one.

      while (($vm.ExtensionData.Guest.ToolsRunningStatus -ne [VMware.Vim.VirtualMachineToolsRunningStatus]::guestToolsRunning -or
      $vm.ExtensionData.Runtime.PowerState -ne [VMware.Vim.VirtualMachinePowerState]::poweredOn) -and
      -not $vm.ExtensionData.Guest.GuestOperationsReady) {
      Start-Sleep 2

      Let me know if that helps.


        That function is another post on my blog, see Folder By Path.
        But I’ll update the post, thanks for noticing that.

    Reginaldo Tunisi

    Luc.. Nice script! Can i make a suggestion for the password session? You could use your invoke-vmscriptplus with
    echo $newpassword | passwd root –stdin (passing $oldpassword as credentials).. This way you change the root password without console interaction…


      Hi Reginaldo,
      When you connect to the Photon VM, it prompts immediately to change the password. There is no access to the shell at that point I’m afraid.

Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.