<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Import-VApp Archives - LucD notes</title>
	<atom:link href="https://www.lucd.info/category/import-vapp/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.lucd.info/category/import-vapp/</link>
	<description>My PowerShell ramblings</description>
	<lastBuildDate>Wed, 08 Apr 2020 11:08:22 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.7</generator>

<image>
	<url>https://www.lucd.info/wp-content/uploads/2018/12/cropped-120px-Tibetan_Dharmacakra-32x32.png</url>
	<title>Import-VApp Archives - LucD notes</title>
	<link>https://www.lucd.info/category/import-vapp/</link>
	<width>32</width>
	<height>32</height>
</image> 
<atom:link rel="hub" href="https://pubsubhubbub.appspot.com"/><atom:link rel="hub" href="https://pubsubhubbub.superfeedr.com"/><atom:link rel="hub" href="https://websubhub.com/hub"/>	<item>
		<title>Deploy Photon 2.0 &#8211; Part 1</title>
		<link>https://www.lucd.info/2018/08/14/deploy-photon-2-0-part-1/</link>
					<comments>https://www.lucd.info/2018/08/14/deploy-photon-2-0-part-1/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Tue, 14 Aug 2018 10:51:10 +0000</pubDate>
				<category><![CDATA[Guest Operations]]></category>
		<category><![CDATA[Guest OS]]></category>
		<category><![CDATA[Import-VApp]]></category>
		<category><![CDATA[Invoke-VMScriptPlus]]></category>
		<category><![CDATA[Photon]]></category>
		<category><![CDATA[PowerCLI]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[VMware Tools]]></category>
		<category><![CDATA[GuestOperations]]></category>
		<guid isPermaLink="false">http://www.lucd.info/?p=5851</guid>

					<description><![CDATA[Photon 2.0 is definitely a guest OS that is useful in a VMware [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p><a href="https://vmware.github.io/photon/" target="_blank" rel="noopener noreferrer">Photon 2.0</a> 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.&nbsp;<br>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 <a href="https://github.com/PowerShell/PowerShell" target="_blank" rel="noopener noreferrer">PowerShell Core</a> with <a href="https://code.vmware.com/web/dp/tool/vmware-powercli/10.1.1" target="_blank" rel="noopener noreferrer">VMware PowerCLI</a>, while running it in a <a href="https://hub.docker.com/r/vmware/powerclicore/" target="_blank" rel="noopener noreferrer">Docker container</a>, well shielded from our other platforms.<br><br></p>



<figure class="wp-block-image"><img fetchpriority="high" decoding="async" width="435" height="359" src="https://www.lucd.info/wp-content/uploads/2018/08/sddc-1.jpg" alt="" class="wp-image-5914" srcset="https://www.lucd.info/wp-content/uploads/2018/08/sddc-1.jpg 435w, https://www.lucd.info/wp-content/uploads/2018/08/sddc-1-300x248.jpg 300w" sizes="(max-width: 435px) 100vw, 435px" /></figure>



<p><p>There are numerous articles and blog posts that describe how set up a Photon 2.0 VM, but most of these use the <a href="https://www.vmware.com/support/developer/ovf/" target="_blank" rel="noreferrer noopener">ovftool</a> 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.<br>With the latest version of my <a href="https://www.lucd.info/2018/08/05/invoke-vmscriptplus-v2/" target="_blank" rel="noreferrer noopener">Invoke-VMScriptPlus</a> function, you can now automate this entire process, the <a href="https://code.vmware.com/sddc-getting-started" target="_blank" rel="noreferrer noopener">SDDC</a> way!&nbsp;</p>
<p>&nbsp;</p></p>


<p><span style="background-color: #ffff00;"><strong>Update August 21st 2018</strong></span></p>
<ul>
<li>Added &#8220;reboot&#8221; action</li>
<li>Added optional Environment variables for the customisation scripts</li>
<li>Added Folder field for template destination</li>
</ul>


<p><span id="more-5851"></span></p>



<p><h2>Intro</h2>
<p>As I briefly mentioned in the introduction paragraph, setting up a VM with the Photon OS 2.0, is a two-step process.</p></p>



<ul class="wp-block-list"><li>Deploy the appliance</li><li>Configure the OS</li></ul>



<p>The first step is rather straight-forward with PowerCLI&#8217;s <a href="https://vdc-repo.vmware.com/vmwb-repository/dcr-public/d402b7ed-b345-4fda-880d-a48e8885e910/b6ff10a7-3769-4346-8a83-d92d99d6caf3/doc/Import-VApp.html" target="_blank" rel="noopener noreferrer">Import-VApp</a> cmdlet.</p>



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



<p>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.</p>



<p>The following scenario, which I&#8217;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.</p>



<h2 class="wp-block-heading">Create a Photon VM Template</h2>



<p>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!</p>



<h3 class="wp-block-heading">The JSON Configuration file</h3>



<p>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.</p>



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


<p>&nbsp;</p>


<div class="wp-block-image"><figure class="alignleft is-resized"><img decoding="async" src="https://www.lucd.info/wp-content/uploads/2018/08/json-main.jpg" alt="" class="wp-image-5867" width="207" height="296"/></figure></div>



<p>&nbsp;</p>
<ul class="wp-block-list">
<li><strong>vSphere</strong>: describes the Location for the template</li>
<li><strong>Template</strong>: characteristics of the Template</li>
<li><strong>Network</strong>: the network configuration inside the guest OS</li>
<li><strong>Account</strong>: the current and new password for the root account</li>
<li><strong>Proxy</strong>: the proxy settings, if present</li>
<li><strong>Docker</strong>: the docker configuration</li>
<li><strong>Code</strong>: a collection of customisation scripts</li>
</ul>



<p>&nbsp;</p>



<h4 class="wp-block-heading"> </h4>
<h4> </h4>
<h4>vSphere</h4>
<p><img decoding="async" class="alignnone wp-image-5937 size-full" src="https://www.lucd.info/wp-content/uploads/2018/08/json-vsphere-1.jpg" alt="" width="722" height="95" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-vsphere-1.jpg 722w, https://www.lucd.info/wp-content/uploads/2018/08/json-vsphere-1-300x39.jpg 300w, https://www.lucd.info/wp-content/uploads/2018/08/json-vsphere-1-720x95.jpg 720w" sizes="(max-width: 722px) 100vw, 722px" /></p>





<ul class="wp-block-list"><li><strong>VMHost</strong>: 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.</li><li><strong>Storage</strong>: a datastorecluster or datastore where the VM will be installed.</li><li><strong>OvaPath</strong>: where the script can find the Photon 2.0 ova file. Note the escaped back-slashes in the path.</li></ul>



<h4 class="wp-block-heading">Template</h4>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5939" src="https://www.lucd.info/wp-content/uploads/2018/08/json-template-1.jpg" alt="" width="687" height="211" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-template-1.jpg 687w, https://www.lucd.info/wp-content/uploads/2018/08/json-template-1-300x92.jpg 300w" sizes="auto, (max-width: 687px) 100vw, 687px" /></p>





<ul class="wp-block-list"><li><strong>TemplateName</strong>, <strong>TemplateNote</strong>: self explanatory</li><li><strong>Folder</strong>: a VM type folder where the template shall be stored</li><li><strong>NumCpu</strong>, <strong>MemoryGB</strong>, <strong>HarddiskGB</strong>:&nbsp;self explanatory</li><li><strong>GuestID</strong>: has to be <strong>other3xLinux64Guest</strong> for Photon 2.0</li><li><strong>CreateTemplate</strong>: 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).</li><li><strong>CustomisationCode</strong>: the names of the customisation scripts that will be executed once the VM is installed. See later in the <strong>Code</strong> section.</li></ul>



<h4 class="wp-block-heading">Network</h4>



<p>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.</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="426" height="171" src="https://www.lucd.info/wp-content/uploads/2018/08/json-network.jpg" alt="" class="wp-image-5872" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-network.jpg 426w, https://www.lucd.info/wp-content/uploads/2018/08/json-network-300x120.jpg 300w" sizes="auto, (max-width: 426px) 100vw, 426px" /></figure>



<p>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.</p>



<h4 class="wp-block-heading">Account</h4>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="313" height="81" src="https://www.lucd.info/wp-content/uploads/2018/08/json-account.jpg" alt="" class="wp-image-5873" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-account.jpg 313w, https://www.lucd.info/wp-content/uploads/2018/08/json-account-300x78.jpg 300w" sizes="auto, (max-width: 313px) 100vw, 313px" /></figure>



<p>Again, quite straight-forward. The account, and the old and new password.</p>



<h4 class="wp-block-heading">Proxy</h4>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="377" height="115" src="https://www.lucd.info/wp-content/uploads/2018/08/json-proxy.png" alt="" class="wp-image-5904" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-proxy.png 377w, https://www.lucd.info/wp-content/uploads/2018/08/json-proxy-300x92.png 300w" sizes="auto, (max-width: 377px) 100vw, 377px" /></figure>



<p>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,</p>



<h4 class="wp-block-heading">Docker</h4>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="230" height="56" src="https://www.lucd.info/wp-content/uploads/2018/08/json-docker.jpg" alt="" class="wp-image-5876"/></figure>



<p>Only one setting, the default port to access docker.</p>



<h4 class="wp-block-heading">Code</h4>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5942" src="https://www.lucd.info/wp-content/uploads/2018/08/json-code-3.jpg" alt="" width="481" height="118" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-code-3.jpg 481w, https://www.lucd.info/wp-content/uploads/2018/08/json-code-3-300x74.jpg 300w" sizes="auto, (max-width: 481px) 100vw, 481px" /></p>





<p>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.</p>



<ul class="wp-block-list"><li><strong>Name</strong>: the name for this particular customisation script. This is the name that is used in the <strong>Template</strong> section, in <strong>CustomisationCode</strong> field.</li><li><strong>Script</strong>: 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</li><li><strong>Environment</strong>: an array with environment variables that will be passed to the environment where the customisation script is executed.</li></ul>



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



<p>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.</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1013" height="549" src="https://www.lucd.info/wp-content/uploads/2018/08/json-code-2.jpg" alt="" class="wp-image-5879" srcset="https://www.lucd.info/wp-content/uploads/2018/08/json-code-2.jpg 1013w, https://www.lucd.info/wp-content/uploads/2018/08/json-code-2-300x163.jpg 300w, https://www.lucd.info/wp-content/uploads/2018/08/json-code-2-768x416.jpg 768w, https://www.lucd.info/wp-content/uploads/2018/08/json-code-2-720x390.jpg 720w" sizes="auto, (max-width: 1013px) 100vw, 1013px" /></figure>



<p>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.</p>



<p>In <strong>line 126</strong> of excerpt above, we use the expression <strong>$($paramData.Docker.Port)</strong> to refer to the port number we defined in the Docker section.&nbsp;</p>



<p><p>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.</p>
<pre class="lang:ps decode:true">function Convert-Script2Json
{
  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)]
    [string]$ScriptName,
    [Parameter(Mandatory = $true)]
    [string[]]$Script
  )

  $obj = New-Object PSObject -Property @{
      Name = $ScriptName
      Script = $script.Split("<code>n") | %{$_.TrimEnd("</code>r")}
  }
  $obj | ConvertTo-Json
}</pre></p>



<p><p>A sample run shows you how you could use the above code.</p>
<pre class="lang:ps decode:true">@'
# Update packages
# Separate to avoid script ending with VMware Tools update

tdnf update -y
'@

Convert-Script2Json -ScriptName packages -Script $code</pre></p>



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



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="604" height="162" src="https://www.lucd.info/wp-content/uploads/2018/08/script2json.jpg" alt="" class="wp-image-5881" srcset="https://www.lucd.info/wp-content/uploads/2018/08/script2json.jpg 604w, https://www.lucd.info/wp-content/uploads/2018/08/script2json-300x80.jpg 300w" sizes="auto, (max-width: 604px) 100vw, 604px" /></figure>



<h3 class="wp-block-heading">The New-PhotonTemplate function</h3>



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



<ul class="wp-block-list"><li><strong>Invoke-VMScriptPlus</strong> from my <a href="https://www.lucd.info/2018/08/05/invoke-vmscriptplus-v2/" target="_blank" rel="noopener noreferrer">Invoke-VMScriptPlus v2</a> post.</li><li><strong>Set-VMKeystrokes</strong>, from William Lam&#8217;s <a href="https://www.virtuallyghetto.com/2017/09/automating-vm-keystrokes-using-the-vsphere-api-powercli.html" target="_blank" rel="noopener noreferrer">Automating VM keystrokes using the vSphere API &amp; PowerCLI&nbsp;</a></li></ul>



<p>You have multiple options to make these two functions available to the <strong>New-PhotonTemplate</strong> function.</p>



<ul class="wp-block-list"><li>Add them to the .ps1 file where you copied the New-PhotonTemplate function</li><li>Store the two functions in separate .ps1 files, and dot-source these .ps1 files before calling the New-PhotonTemplate function</li><li>Create a module, and add the two functions to that module</li></ul>



<h4 class="wp-block-heading">The Code</h4>


<pre class="urvanov-syntax-highlighter-plain-tag">function New-PhotonTemplate {
&lt;#
.SYNOPSIS
 Create a Template with the Photon 2.0 guest OS
.DESCRIPTION
 This function will create a new Template with the Photon 2.0
 guest OS. The parameters and the configuration scripts are passed via
 a JSON file.
.NOTES
 Author:  Luc Dekens
 Version:
 1.0 11/08/18  Initial release
 1.1 16/08/18  Create function
               Added support for Reboot action
               Added support for environment variable
 1.2 17/08/18  Added Folder (in JSON file) support
              
.PARAMETER JSONPath
 The location of the JSON file with the configuration parameters
.PARAMETER LogFile
 Optional file for capturing logging information.
 This includes the output of the customisation scripts
.EXAMPLE
 New-PhotonTemplate -JSONPath .\photon.json
.EXAMPLE
 New-PhotonTemplate -JSONPath .\photon.json -LogFile .\photon.log
#&gt;

  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)]
    [string]$JSONPath,
    [string]$LogFile
  )

  function Write-ToLog {
    [CmdletBinding()]
    param(
      [string]$Category,
      [string]$Text
    )

    $timeStamp = (Get-Date).ToString('yyyyMMddThhmmss')
    $script = Split-Path -Path $myInvocation.ScriptName -Leaf
    $user = $env:UserName

    $line = "$timeStamp - $script - $user - [$Category] $Text"
    if ($LogFile) {
      $line | Out-File -FilePath $LogFile -Append
    }
    else {
      $line
    }
  }

  function Invoke-VMReboot {
    [CmdletBinding()]
    param(
      [string]$VMName
    )

    $vmGuest = Get-VMGuest -VM $VMName

    Stop-VMGuest -VM $vm -Confirm:$false | Out-Null
    while ($vmGuest.VM.PowerState -ne 'PoweredOff') {
      Start-Sleep 1
      $vmGuest = Get-VMGuest -VM $vmGuest.VM
    }

    $vmGuest = Start-VM -VM $vmGuest.VM -Confirm:$false | Get-VMGuest
    while ($vmGuest.VM.PowerState -ne 'PoweredOn' -or -not $vmGuest.ExtensionData.GuestOperationsReady) {
      Start-Sleep 1
      $vmGuest = Get-VMGuest -VM $vmGuest.VM
    }
  }

  # Get Parameters

  $paramDataJSON = Get-Content -Path $JSONPath | Out-String
  $paramData = ConvertFrom-Json -InputObject $paramDataJSON

  # Determine location for template

  $obj = Get-Inventory -Name $paramData.vSphere.VMHost
  if ($obj -is [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]) {
    $obj = Get-VMHost -Location $obj | Get-Random
  }
  $esx = $obj

  $dsc = Get-DatastoreCluster -Name $paramData.vSphere.Storage -ErrorAction SilentlyContinue
  if ($dsc) {
    $ds = Get-Datastore -RelatedObject $dsc | Get-Random
  }
  else {
    $ds = Get-Datastore -Name $paramData.vSphere.Storage
  }

  # Clean up eventual artifacts

  if ($vm = Get-VM -Name $paramData.Template.TemplateName -ErrorAction SilentlyContinue) {
    if ($vm.PowerState -eq [VMware.VimAutomation.ViCore.Types.V1.Inventory.PowerState]::PoweredOn) {
      Stop-VM -VM $vm -Confirm:$false | Out-Null
    }
    Remove-VM -VM $vm -Confirm:$false -DeletePermanently
    Write-ToLog -Category Info -Text "Removed VM $($vm.Name)"
  }
  if ($template = Get-Template -Name $paramData.Template.TemplateName -ErrorAction SilentlyContinue) {
    Remove-Template -Template $template -DeletePermanently -Confirm:$false
    Write-ToLog -Category Info -Text "Removed Template $($template.Name)"
  }

  # Import Appliance

  $ovfParm = Get-OvfConfiguration -Ovf $paramData.vSphere.OvaPath
  $ovfParm.NetworkMapping.None.Value = $paramData.Network.PortGroup
  $sVApp = @{
    Name              = $paramData.Template.TemplateName
    Source            = $paramData.vSphere.OvaPath
    OvfConfiguration  = $ovfParm
    VMHost            = $esx
    Datastore         = $ds
    DiskStorageFormat = [VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.VirtualDiskStorageFormat]::Thin
    Confirm           = $false
  }
  if($paramData.Template.Folder){
    $sVApp.Add('InventoryLocation',(Get-FolderByPath -Path $paramData.Template.Folder))
  }
  Write-ToLog -Category Info -Text "Importing vApp..."
  $vm = Import-VApp @sVApp
  Write-ToLog -Category Info -Text "Imported vApp $($sVApp.Name)"

  # Configure VM

  $vm = Get-VM -Name $paramData.Template.TemplateName

  $sSet = @{
    VM       = $vm
    NumCpu   = $paramData.Template.NumCpu
    MemoryGB = $paramData.Template.MemoryGB
    Notes    = $paramData.Template.TemplateNote.Replace('#timestamp#', (Get-Date -Format 'dd/MM/yyyy HH:mm'))
    Confirm  = $false
  }
  Set-VM @sSet &gt; $null
  Get-HardDisk -VM $vm | Set-HardDisk -CapacityGB $paramData.Template.HarddiskGB -Confirm:$false  &gt; $null
  Get-FloppyDrive -VM $vm | Remove-FloppyDrive -Confirm:$false &gt; $null
  Write-ToLog -Category Info -Text "Configured VM $($vm.Name)"

  Start-VM -VM $vm &gt; $null
  Write-ToLog -Category Info -Text "Power on VM $($vm.Name)"

  while ($vm.ExtensionData.Guest.ToolsRunningStatus -ne [VMware.Vim.VirtualMachineToolsRunningStatus]::guestToolsRunning -or
    $vm.ExtensionData.Runtime.PowerState -ne [VMware.Vim.VirtualMachinePowerState]::poweredOn) {
    Start-Sleep 2
    $vm.ExtensionData.UpdateViewData()
  }
  Write-ToLog -Category Info -Text "VM $($vm.Name) is powered on"

  # Change Photon OS default root password
  # Thanks to William Lam for his Set-VMKeystrokes function
  # See https://www.virtuallyghetto.com/2017/09/automating-vm-keystrokes-using-the-vsphere-api-powercli.html

  Write-ToLog -Category Info -Text "Change password for user $($paramData.Account.User)"

  Set-VMKeystrokes -VMName $VM -StringInput $paramData.Account.User -ReturnCarriage $true | Out-Null
  Set-VMKeystrokes -VMName $VM -StringInput $paramData.Account.OldPassword -ReturnCarriage $true | Out-Null
  Set-VMKeystrokes -VMName $VM -StringInput $paramData.Account.OldPassword -ReturnCarriage $true | Out-Null
  Set-VMKeystrokes -VMName $VM -StringInput $paramData.Account.NewPassword -ReturnCarriage $true | Out-Null
  Set-VMKeystrokes -VMName $VM -StringInput $paramData.Account.NewPassword -ReturnCarriage $true | Out-Null

  Set-VMKeystrokes -VMName $VM -StringInput "exit" -ReturnCarriage $true | Out-Null

  $vm = Get-VM -Name $paramData.Template.TemplateName

  # Run all customisation scripts

  foreach ($code in $paramData.Template.CustomisationCode) {
    if ($code -eq 'reboot') {
      Write-ToLog -Category Info -Text "Rebooting VM $($vm.Name)..."
      Invoke-VMReboot -VMName $vm.Name
      Write-ToLog -Category Info -Text "Rebooted VM $($vm.Name)"
    }
    else {
      $codeObj = $paramData.Code.Where{$_.Name -eq $code}
      $sInvoke = @{
        VM               = $vm
        ScriptType       = 'Bash'
        ScriptText       = $ExecutionContext.InvokeCommand.ExpandString(($codeObj.Script -join "`r`n"))
        GuestUser        = $paramData.Account.User
        GuestPassword    = ConvertTo-SecureString -String $paramData.Account.NewPassword -AsPlainText -Force
        GuestOSType      = 'Linux'
      }
      if($codeObj.Environment){
        $sInvoke.Add('ScriptEnvironment',$codeObj.Environment.foreach{$ExecutionContext.InvokeCommand.ExpandString($_)})
      }
      Write-ToLog -Category Info -Text "Running $code"
      $result = Invoke-VMScriptPlus @sInvoke
      if ($LogFile) {
        $result | Out-File -FilePath $LogFile -Append
      }
      else {
        $result
      }
    }
  }

  # Create Template

  if ($paramData.Template.CreateTemplate) {
    $vm = Get-VM -Name $paramData.Template.TemplateName

    if ($vm.PowerState -eq [VMware.VimAutomation.ViCore.Types.V1.Inventory.PowerState]::PoweredOn) {
      Write-ToLog -Category Info -Text "Stopping VM $($vm.Name) ..."
      Shutdown-VMGuest -VM $vm -Confirm:$false &gt; $null
      while ($vm.PowerState -ne [VMware.VimAutomation.ViCore.Types.V1.Inventory.PowerState]::PoweredOff) {
        Start-Sleep 3
        $vm = Get-VM -Name $paramData.Template.TemplateName
      }
      Write-ToLog -Category Info -Text "VM $($vm.Name) stopped"
    }

    Write-ToLog -Category Info -Text "Convert VM $($vm.Name) to Template"
    Set-vm -VM $vm -ToTemplate -Confirm:$false &gt; $null
  }
}</pre>
<p> </p>


<h4 class="wp-block-heading">Annotations</h4>



<p><p><strong>Line 36-54</strong>: An inline function to have all messages in the same format.</p>
<p><strong>Line 56-75</strong>: A reboot the guest OS function. Called when the &#8220;reboot&#8221; instruction is encountered. See later.</p></p>



<p><strong>Line 79-80</strong>: These lines read the JSON file and convert the data to a PowerShell object</p>
<p><strong>Line 84-88</strong>: The <strong>vSphere.VMHost</strong> 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.</p>
<p><strong>Line 90-96</strong>: The <strong>vSphere.Storage</strong> 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.</p>
<p><strong>Line 100-110</strong>: These lines check if there is already a Template or a VirtualMachine with a name as specified in <strong>Template.TemplateName</strong>. If there is, the Template or VirtualMachine are removed.</p>
<p><strong>Line 114-130</strong>: A straight-forward method to import the Photon OVA as defined in <strong>vSphere.OvaPath</strong>.</p>
<p><strong>Line 134-146</strong>:  Further HW configuration of the VM</p>
<p><strong>Line 148-156</strong>: The new VM is powered on, and the function waits till the VMware Tools are responding</p>
<p><strong>Line 162-170</strong>: The function changes the root password with the new password defined in <strong>Account.NewPassword</strong>. For this action the code uses the Set-VMKeystrokes function. <a href="https://twitter.com/lamw" target="_blank" rel="noopener noreferrer">William</a> describes this ingenious method in his <a href="https://www.virtuallyghetto.com/2017/09/automating-vm-keystrokes-using-the-vsphere-api-powercli.html" target="_blank" rel="noopener noreferrer">Automating VM keystrokes using the vSphere API &amp; PowerCLI</a> post, well worth a read!</p>
<p><strong>Line 176-204</strong>: The code now runs all the customisation script that are defined in <strong>Template.CustomisationCode</strong>. The actual customisation scripts are defined in the <strong>Code</strong> section of the JSON file.</p>
<p><strong>Line 177-182</strong>: With reserved name &#8220;reboot&#8221; 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.</p>
<p><strong>Line 187,193</strong>: With the <strong>ExpandString</strong> 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.</p>
<p><strong>Line 192-194</strong>: 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.</p>
<p><strong>Line 208-223</strong>: Based on the <strong>Template.CreateTemplate</strong> 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.</p>
<p>



</p>
<h3 class="wp-block-heading">Sample Run</h3>
<p>



</p>
<p>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.</p>
<p>



</p>
<pre class="wp-block-code"><code>New-PhotonTemplate -JSONPath .\LabParams.json -LogFile C:\Temp\photon-template.log</code></pre>
<p>



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



</p>
<p>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 <strong>docker</strong> customisation script.</p>
<p>



</p>
<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1716" height="518" class="wp-image-5894" src="https://www.lucd.info/wp-content/uploads/2018/08/cust-out-1.jpg" alt="" srcset="https://www.lucd.info/wp-content/uploads/2018/08/cust-out-1.jpg 1716w, https://www.lucd.info/wp-content/uploads/2018/08/cust-out-1-300x91.jpg 300w, https://www.lucd.info/wp-content/uploads/2018/08/cust-out-1-768x232.jpg 768w, https://www.lucd.info/wp-content/uploads/2018/08/cust-out-1-1024x309.jpg 1024w, https://www.lucd.info/wp-content/uploads/2018/08/cust-out-1-720x217.jpg 720w" sizes="auto, (max-width: 1716px) 100vw, 1716px" /></figure>
<p>



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



</p>
<h3 class="wp-block-heading">Known Issues</h3>
<p>



</p>
<h4 class="wp-block-heading">open-vm-tools</h4>
<p>



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



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



</p>
<p>



</p>
<h3 class="wp-block-heading">Files</h3>
<p>



</p>
<p>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.</p>
<p>



</p>
<h2 class="wp-block-heading">Next</h2>
<p>



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



</p>
<p>Enjoy!</p>
<p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2018/08/14/deploy-photon-2-0-part-1/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>Upgrade an Appliance with OvfTools</title>
		<link>https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/</link>
					<comments>https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Tue, 10 Jan 2017 18:43:44 +0000</pubDate>
				<category><![CDATA[Appliance]]></category>
		<category><![CDATA[Import-VApp]]></category>
		<category><![CDATA[Ovf]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Property]]></category>
		<category><![CDATA[OpBot]]></category>
		<category><![CDATA[Opvizor]]></category>
		<category><![CDATA[OVF]]></category>
		<guid isPermaLink="false">http://www.lucd.info/?p=5518</guid>

					<description><![CDATA[Appliances are hot! Each appliance, delivered as an OVA or OVF, can have [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Appliances are hot!</p>
<p>Each appliance, delivered as an <a href="https://en.wikipedia.org/wiki/Open_Virtualization_Format" target="_blank" rel="noopener noreferrer">OVA or OVF</a>, can have one or more <strong>properties</strong> attached to it. These properties are mostly used to configure the appliance. In <a href="https://communities.vmware.com/community/vmtn/automationtools/powercli" target="_blank" rel="noopener noreferrer">PowerCLI</a> we have the <a href="https://www.vmware.com/support/developer/PowerCLI/PowerCLI65R1/html/Get-OvfConfiguration.html" target="_blank" rel="noopener noreferrer">Get-OvfConfiguration</a> cmdlet. It returns the <strong>user configurable properties</strong> from an OVF or OVA file in a hash table.</p>
<p>But what about upgrading an appliance? Most, if not all, of the Ovf Properties are already entered for the older version of the appliance. Can&#8217;t we use that information to upgrade the appliance? And avoid having to retype all that information?</p>
<p style="padding-left: 30px;"><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools/" rel="attachment wp-att-5519"><img loading="lazy" decoding="async" class="alignnone wp-image-5519 " src="https://www.lucd.info/wp-content/uploads/2017/01/OvfTools.png" width="400" height="342" srcset="https://www.lucd.info/wp-content/uploads/2017/01/OvfTools.png 505w, https://www.lucd.info/wp-content/uploads/2017/01/OvfTools-300x257.png 300w" sizes="auto, (max-width: 400px) 100vw, 400px" /></a></p>
<p>And to take away the suspense, of course, that can be done with a bit of PowerShell!</p>
<p><span id="more-5518"></span></p>
<h2>The OvfTools</h2>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">function Get-OvfVm
{
  [cmdletbinding()]
  param(
    [Parameter(Mandatory=$true)]
    [PSObject]$VM
  )

  Process{
    if($VM -is [STring]){
      $VM = Get-VM -Name $VM
    }
    $result = New-Object PSObject -Property @{
      VmName = $vm.Name
      OvfProperties = @()
    }

    $vm.ExtensionData.Config.VAppConfig.Property | 
    where{$_.UserConfigurable} | %{
      $obj = [ordered]@{
        Name = $_.Id
        Value = $_.Value
        Type = $_.Type
        Label = $_.Label
        Description = $_.Description
      }
      $result.ovfProperties += New-Object PSOBject -Property $obj
    }
    $result
  }
}

function Update-OvfVm
{
  [cmdletbinding()]
  param(
    [Parameter(Mandatory=$true)]
    [PSObject]$VM,
    [Parameter(Mandatory=$true)]
    [String]$Path,
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$OvfProperties,
    [PSCredential[]]$Credential,
    [Parameter(Mandatory=$true)]
    [PSOBject]$VMHost,
    [Parameter(Mandatory=$true)]
    [PSObject]$Datastore,
    [String]$Suffix = 'new'
  )

  Process{
    if($VM -is [String]){
      $VM = Get-VM -Name $VM
    }
    if($VMHost -is [String]){
      $VMHost = Get-VMHost -Name $VMHost
    }
    if($Datastore -is [String]){
      $Datastore = Get-Datastore -Name $Datastore
    }

    $OVfHash = @{}
    foreach($prop in $OvfProperties.OvfProperties){
      if($OVfHash.ContainsKey($prop.Name)){
        Write-Error -Message "Name $($prop.Name) appears twice!"
      }
      else{
        if($prop.Type -eq 'password'){
          $OVfHash.Add($prop.Name,($Credential | where{$prop.Name -eq $_.UserName} | select -ExpandProperty Password))
        }
        else{
          $OVfHash.Add($prop.Name,$prop.Value)
        }
      }
    }
    $sVApp = @{
      Source = $Path
      Name = "$($VM.Name)_$($Suffix)"
      OvfConfiguration = $OVfHash
      VMHost = $VMHost
      Datastore = $Datastore
    }
    Import-VApp @SVApp
  }
}</pre><p></p>
<h3>Annotations</h3>
<p><strong>Line 18</strong>: The vApp properties can be found under the <strong>VAppConfig</strong> property in a <a href="https://pubs.vmware.com/vsphere-60/topic/com.vmware.wssdk.apiref.doc/vim.vApp.VAppConfigInfo.html" target="_blank" rel="noopener noreferrer">VAppConfigInfo</a> object.</p>
<p><strong>Line 19</strong>: In the current version of the function we only look at the <strong>UserConfigurable</strong> properties.</p>
<p><strong>Line 43</strong>: The Update-OvfVm function can accept multiple PSCredential objects. And in this way can handle multiple Ovf Properties of type <strong>password</strong>.</p>
<p><strong>Line 52-60</strong>: Poor man&#8217;s version of <a href="https://www.vmware.com/support/developer/PowerCLI/PowerCLI65R1/html/about_obn.html" target="_blank" rel="noopener noreferrer">OBN</a>.</p>
<p><strong>Line 68-70</strong>: The lines that handle Ovf Properties of type password.</p>
<p><strong>Line 83</strong>: Use the <a href="https://www.vmware.com/support/developer/PowerCLI/PowerCLI65R1/html/Import-VApp.html" target="_blank" rel="noopener noreferrer">Import-VApp</a> cmdlet with the self-constructed OvfConfiguration hash table to import the new appliance.</p>
<h2>Sample Run with OvfTools</h2>
<p>A recent Appliance that is seeing quite a bit of updates, is the OpBot appliance from <a href="https://www.opvizor.com/" target="_blank" rel="noopener noreferrer">Opvizor</a>. See my <a href="https://www.lucd.info/2016/12/01/chatops-bot/" target="_blank" rel="noopener noreferrer">ChatOps Bot</a> post for more information and further links.</p>
<p>The OpBot appliance has quite a number of properties that need to be provided on installation.</p>
<p><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools1/" rel="attachment wp-att-5522"><img loading="lazy" decoding="async" class="alignnone wp-image-5522 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/01/ovftools1-300x87.png" width="300" height="87" srcset="https://www.lucd.info/wp-content/uploads/2017/01/ovftools1-300x87.png 300w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools1-720x209.png 720w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools1.png 730w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>Once installed you can check these Ovf Properties via the vApp Options page. In the case of the OpBot appliance, this also contains the Slack integration token.</p>
<p><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools2/" rel="attachment wp-att-5521"><img loading="lazy" decoding="async" class="alignnone wp-image-5521 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/01/ovftools2-300x263.png" width="300" height="263" srcset="https://www.lucd.info/wp-content/uploads/2017/01/ovftools2-300x263.png 300w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools2.png 550w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>With the help of the OvfTools functions, I&#8217;m going to upgrade my OpBot v2.0 installation to the newer OpBot v2.0.1 version. And that without having to retype all the Ovf Properties.</p>
<p>The OvfTools functions assume that the version from which you want to copy the Ovf Properties is installed. The appliance doesn&#8217;t need to be running. First let&#8217;s have a look at what the Get-OvfVm function returns.</p><pre class="urvanov-syntax-highlighter-plain-tag">Get-OvfVm -VM esx1Bot | Select -ExpandProperty OvfProperties</pre><p>And all the properties we saw earlier via the vApp Options page, are returned.</p>
<p><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools3/" rel="attachment wp-att-5524"><img loading="lazy" decoding="async" class="alignnone wp-image-5524 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/01/ovftools3-300x194.png" width="300" height="194" srcset="https://www.lucd.info/wp-content/uploads/2017/01/ovftools3-300x194.png 300w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools3.png 506w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<h3>Password property</h3>
<p>There is one kind of property that we can&#8217;t retrieve this way, these are the properties of type Password. To circumvent that issue, I have written the <strong>Update-OvfVm</strong> function in such a way that the user can provide these passwords via a separate parameter. Consider this a security measure.</p>
<p>In short, the function caller needs to created a <a href="https://msdn.microsoft.com/en-us/library/system.management.automation.pscredential(v=vs.85).aspx" target="_blank" rel="noopener noreferrer">PSCredential</a> object, where the <strong>User</strong> is the <strong>Name</strong> of the Ovf Password property.</p>
<p>First determine the password (1) properties, and find the propertyname (2).</p>
<p><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools4/" rel="attachment wp-att-5527"><img loading="lazy" decoding="async" class="alignnone wp-image-5527 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/01/ovftools4-300x126.png" width="300" height="126" srcset="https://www.lucd.info/wp-content/uploads/2017/01/ovftools4-300x126.png 300w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools4.png 414w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>In the script create a PSCredential object (3) where the username is equal to the propertyname (2). Pass the PSCredential object(s) on the call to the Update-OvfVm function (4).</p>
<p><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools5/" rel="attachment wp-att-5526"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5526" src="https://www.lucd.info/wp-content/uploads/2017/01/ovftools5.png" alt="" width="820" height="132" srcset="https://www.lucd.info/wp-content/uploads/2017/01/ovftools5.png 820w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools5-300x48.png 300w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools5-768x124.png 768w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools5-720x116.png 720w" sizes="auto, (max-width: 820px) 100vw, 820px" /></a></p>
<p>The complete script to update the OpBot VM from v2.0 to v2.0.1 looks like this.</p><pre class="urvanov-syntax-highlighter-plain-tag">$ovfPath = 'C:\Temp\opbot_2.0.1\OpBot_2.0.1.ovf'
$vmName = 'esx1bot'
$suffix = '2.0.1'
$esxName = 'esx1*'
$dsName = 'DS1'

$credProperty = 'vmware_password'
$pswd = 'Whatever'

$secPswd = ConvertTo-SecureString -String $pswd -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ($credProperty,$secPswd)

Get-OvfVm -VM $vmName | 
Update-OvfVm -VM $vmName -Path $ovfPath -Credential $cred -VMHost $esxName -Datastore $dsName -Suffix $suffix</pre><p>After the script is executed, the new version of the OpBot is deployed. The Ovf Properties are all copied. The switch from the old to the new appliance with some PowerCLI cmdlets should be trivial.</p>
<p><a href="https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/ovftools6/" rel="attachment wp-att-5531"><img loading="lazy" decoding="async" class="alignnone wp-image-5531 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/01/ovftools6-300x127.png" width="300" height="127" srcset="https://www.lucd.info/wp-content/uploads/2017/01/ovftools6-300x127.png 300w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools6-768x326.png 768w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools6-720x305.png 720w, https://www.lucd.info/wp-content/uploads/2017/01/ovftools6.png 788w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>&nbsp;</p>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2017/01/10/upgrade-appliance-ovftools/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>
