<?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>seedISO Archives - LucD notes</title>
	<atom:link href="https://www.lucd.info/tag/seediso/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.lucd.info/tag/seediso/</link>
	<description>My PowerShell ramblings</description>
	<lastBuildDate>Mon, 09 Dec 2019 09:10:19 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.9</generator>

<image>
	<url>https://www.lucd.info/wp-content/uploads/2018/12/cropped-120px-Tibetan_Dharmacakra-32x32.png</url>
	<title>seedISO Archives - LucD notes</title>
	<link>https://www.lucd.info/tag/seediso/</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>Cloud-init &#8211; Part 3 &#8211; Photon OS</title>
		<link>https://www.lucd.info/2019/12/08/cloud-init-part-3-photon-os/</link>
					<comments>https://www.lucd.info/2019/12/08/cloud-init-part-3-photon-os/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Sun, 08 Dec 2019 21:52:03 +0000</pubDate>
				<category><![CDATA[Cloud-init]]></category>
		<category><![CDATA[Deploy]]></category>
		<category><![CDATA[OVA]]></category>
		<category><![CDATA[Photon]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[powershellv6]]></category>
		<category><![CDATA[YAML]]></category>
		<category><![CDATA[cloud-init]]></category>
		<category><![CDATA[seedISO]]></category>
		<guid isPermaLink="false">http://www.lucd.info/?p=6695</guid>

					<description><![CDATA[In Part 1 and Part 2 of this series we used Ubuntu as [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>In <a rel="noreferrer noopener" aria-label="Part 1 (opens in a new tab)" href="https://www.lucd.info/2019/12/06/cloud-init-part-1-the-basics/" target="_blank">Part 1</a> and <a href="https://www.lucd.info/2019/12/07/cloud-init-part-2-advanced-ubuntu/">Part 2</a> of this series we used Ubuntu as the guest OS of our target instances. In Part 3 we will show how to use VMware&#8217;s <a rel="noreferrer noopener" aria-label="Photon OS (opens in a new tab)" href="https://vmware.github.io/photon/" target="_blank">Photon OS</a> as our guest OS.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="693" height="295" src="https://www.lucd.info/wp-content/uploads/2019/12/cloud-init-part-3.png" alt="" class="wp-image-6784" srcset="https://www.lucd.info/wp-content/uploads/2019/12/cloud-init-part-3.png 693w, https://www.lucd.info/wp-content/uploads/2019/12/cloud-init-part-3-300x128.png 300w" sizes="(max-width: 693px) 100vw, 693px" /></figure>



<p>The main reason to use Photon OS is that it is <strong>open-sourced</strong>, it has a <strong>small footprint</strong> and it is <strong>optimised for VMware vSphere</strong>.</p>



<span id="more-6695"></span>



<h2 class="wp-block-heading">Some Background</h2>



<p>Does this mean that <strong>Photon OS</strong> is the ideal guest OS for our &#8216;<em>cattle</em>&#8216; stations where we want to run our PowerShell scripts?</p>



<p>Not really, the major flaw with Photon OS, imho, is that the available packages are <strong>limited</strong> and rather <strong>infrequently maintained</strong>. </p>



<p>In fact, until recently the available <strong>PowerShell package</strong> was an outdated, <strong>not supported </strong>anymore version. But the worst part of the deal, it didn&#8217;t even allow to perform an <a rel="noreferrer noopener" aria-label="Install-Module (opens in a new tab)" href="https://docs.microsoft.com/en-us/powershell/module/powershellget/install-module?view=powershell-6" target="_blank">Install-Module</a>. Which, again imho, is a basic concept in PowerShell. And which didn&#8217;t allow me to install, for example, <a rel="noreferrer noopener" aria-label="VMware PowerCLI (opens in a new tab)" href="https://www.vmware.com/support/developer/PowerCLI/" target="_blank">VMware PowerCLI</a> on a Photon station.</p>



<p>But then recently this happened!</p>



<figure class="wp-block-image size-large"><img decoding="async" width="521" height="363" src="https://www.lucd.info/wp-content/uploads/2019/12/ps6-photon.png" alt="" class="wp-image-6788" srcset="https://www.lucd.info/wp-content/uploads/2019/12/ps6-photon.png 521w, https://www.lucd.info/wp-content/uploads/2019/12/ps6-photon-300x209.png 300w" sizes="(max-width: 521px) 100vw, 521px" /></figure>



<p>The available PowerShell v6 <a href="https://vmware.github.io/photon/assets/files/html/3.0/photon_admin/tdnf.html" target="_blank" rel="noopener noreferrer">TDNF</a> package was upgraded to a currently supported version, and more importantly, the <strong>Import-Module</strong> cmdlet&nbsp; worked!&nbsp;</p>
<p>Allowing us to install VMware PowerCLI on a Photon instance.</p>



<p>Is this the end of all the PowerShell woes on Photon OS?</p>



<p>Not really! The PowerShell version in this TDNF package will also run out of support in a not so far future and a PowerShell TDNF package upgrade is (still) not automated and somewhat of a black art. </p>



<p>But for now, let&#8217;s be happy with what we have and &#8216;cloud-init&#8217; our Photon &#8216;cattle&#8217; station!</p>



<h2 class="wp-block-heading">Issues and some Solutions</h2>



<h3 class="wp-block-heading">No UserData Property</h3>



<p>While the Ubuntu cloud image, as we saw in <a rel="noreferrer noopener" aria-label="Part 1 (opens in a new tab)" href="https://www.lucd.info/2019/12/06/cloud-init-part-1-the-basics/" target="_blank">Part 1</a> of the series, has the option to pass the <strong>user-data</strong> to cloud-init via an <strong>OVF property</strong>, this feature is unfortunately not available in the Photon OVA.</p>



<p>There are some solutions available.</p>



<ul class="wp-block-list"><li>Roll your own OVA image</li><li>Use the <a rel="noreferrer noopener" aria-label="NoCloud (opens in a new tab)" href="https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html" target="_blank">NoCloud</a> option, which means creating an ISO containing the user-data and attaching this ISO to the instance before cloud-init runs.</li></ul>



<p>Since I prefer to use the &#8216;standard&#8217; OVA, I opt for creating such a seed ISO.</p>



<h3 class="wp-block-heading">The Root Password</h3>



<p>This is somewhat of a <a rel="noreferrer noopener" aria-label="chicken-and-the-egg (opens in a new tab)" href="https://en.wikipedia.org/wiki/Chicken_or_the_egg" target="_blank">chicken-and-the-egg</a> dilemma.</p>



<p>In the Photon OVA image, the root account is set to force a password change on first use. But unfortunately, this also disallows setting the root password via the user-data. See <a href="https://github.com/vmware/photon/issues/931">Issue#931</a> for more details.</p>



<p>Again, there are two options available.</p>



<ul class="wp-block-list"><li>Roll your own OVA image</li><li>Use a two-stage process, wherein the first stage we use <a rel="noreferrer noopener" aria-label="William Lam (opens in a new tab)" href="https://twitter.com/lamw" target="_blank">William Lam</a>&#8216;s  <a rel="noreferrer noopener" aria-label="Set-VMKeystrokes (opens in a new tab)" href="https://www.virtuallyghetto.com/2017/09/automating-vm-keystrokes-using-the-vsphere-api-powercli.html" target="_blank">Set-VMKeystrokes</a> function. Then in the second stage, we attach the seed ISO and start the instance. And thus kicking off the cloud-init process.</li></ul>



<p>And again, since I prefer to use the &#8216;standard&#8217; OVA, I opt for the Set-VMKeystrokes solution.</p>



<h2 class="wp-block-heading">Install-CloudInitVM Revisited</h2>



<pre class="lang:ps decode:true  ">function Install-CloudInitVM
{
  &lt;#
.SYNOPSIS
  Deploy a VM from an OVA file and use cloud-init for the configuration
  .DESCRIPTION
  This function will deploy an OVA file.
  The function transfer the user-data to the cloud-init process on the VM with
  one of the OVF properties.
.NOTES
  Author:  Luc Dekens
  Version:
  1.0 05/12/19  Initial release
  1.1 07/12/19  Added Photon deployment
                Added seed ISO option
.PARAMETER OvaFile
  Specifies the path to the OVA file
.PARAMETER VmName
  The displayname of the VM
.PARAMETER ClusterName
  The cluster onto which the VM shall be deployed
.PARAMETER DsName
  The datastore on which the VM shall be deployed
.PARAMETER PgName
  The portgroupname to which the VM shall be connected
.PARAMETER CloudConfig
  The path to the YAML file containing the user-data
.PARAMETER CloudConfigISO
  The ISO seed file, containing the meta-data and user-data
.PARAMETER Credential
  The credentials for a user in the VM's guest OS
.PARAMETER Photon
  Switch to indicate a Photon is deployed. This will add an
  additional step to reset the root password before starting
  the actual cloud-init based deployment
.EXAMPLE
  $sCloudInitVM = @{
    OvaFile = '.\bionic-server-cloudimg-amd64.ova'
    VmName = $vmName
    ClusterName = $clusterName
    DsName = $dsName
    PgName = $pgName
    CloudConfig = '.\user-data.yaml'
    Credential = $cred
  }
  Install-CloudInitVM @sCloudInitVM
.EXAMPLE
  $sCloudInitVM = @{
    OvaFile = '.\photon-hw13_uefi-3.0-9355405.ova'
    VmName = $vmName
    ClusterName = $clusterName
    DsName = $dsName
    PgName = $pgName
    CloudConfigISO = '.\seed.iso'
    Credential = $cred
    Photon = $true
  }
  Install-CloudInitVM @sCloudInitVM
#&gt;

  [cmdletbinding()]
  param(
    [string]$OvaFile,
    [string]$VmName,
    [string]$ClusterName,
    [string]$DsName,
    [string]$PgName,
    [Parameter(ParameterSetName = 'YAML')]
    [string]$CloudConfig,
    [Parameter(ParameterSetName = 'SeedISO')]
    [string]$CloudConfigISO,
    [PSCredential[]]$Credential,
    [Switch]$Photon
  )

  $waitJob = (Get-Command -Name .\Wait-Job.ps1 ).ScriptBlock
  if ($PSCmdlet.ParameterSetName -eq 'YAML')
  {
    $userData = Get-Content -Path $CloudConfig -Raw
  }

  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Starting deployment of $vmName"
  if ($Photon)
  {
    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - This is a Photon deployment"
  }

  $start = Get-Date

  $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue -Verbose:$false
  if ($vm)
  {
    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Cleaning up"
    if ($vm.PowerState -eq 'PoweredOn')
    {
      Stop-VM -VM $vm -Confirm:$false -Verbose:$false | Out-Null
    }
    Remove-VM -VM $vm -DeletePermanently -Confirm:$false -Verbose:$false
  }

  $ovfProp = Get-OvfConfiguration -Ovf $ovaFile -Verbose:$false
  if (-not $Photon)
  {
    $ovfProp.Common.user_data.Value = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($userData))
    $ovfProp.NetworkMapping.VM_Network.Value = $pgName
  }
  else
  {
    $ovfProp.NetworkMapping.None.Value = $pgName
  }
  $sApp = @{
    Source = $ovaFile
    Name = $vmName
    Datastore = Get-Datastore -Name $dsName -Verbose:$false
    DiskStorageFormat = 'Thin'
    VMHost = Get-Cluster -Name $clusterName -Verbose:$false | Get-VMHost  -Verbose:$false | Get-Random
    OvfConfiguration = $ovfProp
    Verbose = $false
  }
  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Importing OVA"
  $vm = Import-VApp @sApp

  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Starting the VM"
  Start-VM -VM $vm -Confirm:$false -RunAsync -Verbose:$false | Out-Null

  if ($Photon)
  {
    while (-not $vm.ExtensionData.Guest.GuestOperationsReady)
    {
      Start-Sleep 2
      $vm.ExtensionData.UpdateViewData('Guest')
    }

    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Resetting password"

    $PswdIsReset = $false
    while (-not $PswdIsReset)
    {
      Set-VMKeystrokes -VMName $VM.Name -StringInput 'root' -ReturnCarriage $true | Out-Null
      Start-Sleep 1
      Set-VMKeystrokes -VMName $VM.Name -StringInput 'changeme' -ReturnCarriage $true | Out-Null
      Start-Sleep 1
      Set-VMKeystrokes -VMName $VM.Name -StringInput 'changeme' -ReturnCarriage $true | Out-Null
      Start-Sleep 1
      Set-VMKeystrokes -VMName $VM.Name -StringInput 'Welcome2019!' -ReturnCarriage $true | Out-Null
      Start-Sleep 1
      Set-VMKeystrokes -VMName $VM.Name -StringInput 'Welcome2019!' -ReturnCarriage $true | Out-Null
      Start-Sleep 1
      Set-VMKeystrokes -VMName $VM.Name -StringInput "exit" -ReturnCarriage $true | Out-Null

      Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Testing new password"

      $sInvoke = @{
        VM = $vm
        ScriptType = 'bash'
        ScriptText = 'whoami'
        GuestUser = $Credential.GetNetworkCredential().UserName
        GuestPassword = $Credential.GetNetworkCredential().Password
        Verbose = $false
      }
      try
      {
        Invoke-VMScript @sInvoke -ErrorAction Stop | Out-Null
        $PswdIsReset = $true
      }
      catch [VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.InvalidGuestLogin]
      {
        Start-Sleep -Seconds 1
      }
      catch
      {
        Write-Error "Exception $($error[0].Exception.GetType().FullName)"
        return
      }
    }

    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Shutdown VM"

    Shutdown-VMGuest -VM $vm -Confirm:$false -Verbose:$false | Out-Null
    while ($vm.ExtensionData.Runtime.PowerState -ne 'poweredOff')
    {
      Start-Sleep 2
      $vm.ExtensionData.UpdateViewData('Runtime.PowerState')
    }

    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Attaching seed ISO"

    $isoName = Split-Path -Path $CloudConfigISO -Leaf
    $isoFile = "DS:\$($vm.ExtensionData.Summary.Config.VmPathName.Split(' ')[1].Split('/')[0])\$($isoName)"
    Get-PSDrive -Name DS -ErrorAction SilentlyContinue -Verbose:$false | Remove-PSDrive -Confirm:$false -Verbose:$false
    New-PSDrive -Location (Get-Datastore -VM $vm) -Name DS -PSProvider VimDatastore -Root '\' -Verbose:$false | Out-Null
    Get-Item -Path $CloudConfigISO |
    Copy-DatastoreItem -Destination $isoFile -Force -Confirm:$false -Verbose:$false
    $isoDSPath = $vm.ExtensionData.Summary.Config.VmPathName.Replace("$vmName.vmx", $isoName)
    Get-CDDrive -VM $vm -Verbose:$false | Set-CDDrive -IsoPath $isoDSPath -StartConnected:$true -Confirm:$false -Verbose:$false | Out-Null

    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Starting the VM"
    Start-VM -VM $vm -Confirm:$false -Verbose:$false | Out-Null
  }

  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Waiting for cloud-init to finish"

  $User = $Credential.GetNetworkCredential().UserName
  $Password = $Credential.GetNetworkCredential().Password

  $sJob = @{
    Name = 'WaitForCloudInit'
    ScriptBlock = $waitJob
    ArgumentList = $vm.Name, $User, $Password, $global:DefaultVIServer.Name, $global:DefaultVIServer.SessionId
  }
  Start-Job @sJob | Receive-Job -Wait

  if ($Photon)
  {
    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Restart OS"
    $vmGuest = Stop-VMGuest -VM $vm -Confirm:$false -Verbose:$false
    while ($vmGuest.VM.ExtensionData.Runtime.PowerState -ne 'poweredOff')
    {
      Start-Sleep 2
      $vmGuest.VM.ExtensionData.UpdateViewData('Runtime.PowerState')
    }
    $vm = Start-VM -VM $vmName -Confirm:$false -Verbose:$false
    while ($vm.PowerState -ne 'PoweredOn')
    {
      Start-Sleep 2
      $vm = Get-VM -Name $VmName -Verbose:$false
    }
    while (-not $vm.ExtensionData.Guest.GuestOperationsReady)
    {
      Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Waiting for GuestOperations ready"
      Start-Sleep 2
      $vm.ExtensionData.UpdateViewData('Guest')
    }
    Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Eject CDROM"
    $sInvoke = @{
      VM = $vm
      ScriptType = 'bash'
      ScriptText = 'eject cdrom'
      GuestUser = $Credential.GetNetworkCredential().UserName
      GuestPassword = $Credential.GetNetworkCredential().Password
      Verbose = $false
    }
    Invoke-VMScript @sInvoke | Out-Null
    Get-CDDrive -VM $vm -Verbose:$false | Set-CDDrive -NoMedia -Confirm:$false -Verbose:$false | Out-Null
  }

  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Deployment complete"
  Write-Verbose "`nDeployment took $([math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds,0)) seconds"
}</pre>
<p>&nbsp;</p>



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



<p>The v1.0 version of this function has already been annotated in <a href="https://www.lucd.info/2019/12/06/cloud-init-part-1-the-basics/" target="_blank" rel="noreferrer noopener" aria-label="Part 1 (opens in a new tab)">Part 1</a> of this series. Those annotations will not be repeated here.</p>



<p><strong>Line 68-71</strong>: To distinguish between the way the user-data is passed, the function now has two mutually exclusive parameters, CloudConfig and CloudConfigIso.</p>



<p><strong>Line 73</strong>: Since Photon requires some special actions, the function now has a switch, obviously named Photon, that specifies if the cloud-init deployment is intended for a Photon instance or not.</p>



<p><strong>Line 77-80</strong>: When the user-data is provided as a YAML file, the function reads the file.</p>



<p><strong>Line 83-86</strong>: A small reminder in the verbose output that the function will handle this as a Photon deployment.</p>



<p><strong>Line 102-110</strong>: The Ubuntu and the Photon OVA files have different OVF properties. This If-Then-Else handles the differences.</p>



<p><strong>Line 126-199</strong>: This part of the function handles most of the peculiarities of a Photon deployment.</p>



<p><strong>Line 136-175</strong>: This part handles the reset of the root password. As mentioned earlier, it uses William&#8217;s <a rel="noreferrer noopener" aria-label="Set-VMKeystrokes (opens in a new tab)" href="https://www.virtuallyghetto.com/2017/09/automating-vm-keystrokes-using-the-vsphere-api-powercli.html" target="_blank">Set-VMKeystrokes</a> function. I experienced intermittent race conditions in sending the keystrokes. Hence the loop with the retries. As a test, to check if the root password was reset, the command &#8216;<em>whoami</em>&#8216; is executed trough <a rel="noreferrer noopener" aria-label="Invoke-VMScript (opens in a new tab)" href="https://vdc-repo.vmware.com/vmwb-repository/dcr-public/6fb85470-f6ca-4341-858d-12ffd94d975e/4bee17f3-579b-474e-b51c-898e38cc0abb/doc/Invoke-VMScript.html" target="_blank">Invoke-VMScript</a> on the instance.</p>



<p><strong>Line 179-184</strong>: Before attaching the seed ISO, the Photon OS is shutdown gracefully. In a While-loop the script test until the instance is actually powered off.</p>



<p><strong>Line 188-195</strong>: The seed ISO is copied to the VM&#8217;s folder, and then attached.</p>



<p><strong>Line 198</strong>: The VM is started with the seed ISO attached. As a result, cloud-init will now use the YAML files it finds on the CD drive as datasource.</p>



<p><strong>Line 216-221</strong>: Since the cloud-init implementation on Photon is <a rel="noreferrer noopener" aria-label="not configured (opens in a new tab)" href="https://github.com/vmware/photon/issues/950" target="_blank">not configured</a> to use the power-state module, the test we used on the Ubuntu instance, can not be used for Photon. As a bypass, the function stops/starts the guest OS on the instance.</p>



<p><strong>Line 235-244</strong>: The function uses the stop/start sequence to disconnect the seed ISO. To avoid having a question on the VM, the function first runs the &#8216;<em>eject cdrom</em>&#8216; command inside the instance.</p>



<h2 class="wp-block-heading">The Seed ISO</h2>



<p>As mentioned earlier, I prefer to use a seed ISO file to solve the issue of not having an OVF property for the user-data.</p>



<p>To create this seed ISO file, we need a Linux station that has the &#8216;<a rel="noreferrer noopener" aria-label="genisoimage (opens in a new tab)" href="https://linux.die.net/man/1/genisoimage" target="_blank">genisoimage</a>&#8216; command available. In the following sample script, I use an  Ubuntu station just for that purpose.</p>



<pre class="lang:ps decode:true  ">$workVM = 'UbuntuPS'
$isoName = 'genisoimage-ps.iso'
$isoVolId = 'cidata'
$isoFiles = @{
    'meta-data' = 'meta-data.yaml'
    'user-data' = 'user-data.yaml'
}

$code1 = @'
sudo apt-get install genisoimage -y &gt; null
genisoimage -output $isoName -volid $isoVolId -joliet -rock $($isoFiles.Keys -join ' ')
'@

$workCred = Get-VICredentialStoreItem -Host $workVM

$isoFiles.GetEnumerator() | ForEach-Object -Process {
    Copy-Item -Path $_.Value -Destination $_.Name
}

$sInvoke = @{
    VM = $workVM
    ScriptType = 'Bash'
    ScriptText = $ExecutionContext.InvokeCommand.ExpandString($code1)
    GuestUser = $workCred.User
    GuestPassword = ConvertTo-SecureString -String $workCred.Password -AsPlainText -Force
    GuestOSType = 'Linux'
    Verbose = $true
    NoIPinCert = $true
    InFile = $isoFiles.Keys
    OutFile = $isoName
}
Invoke-VMScriptPlus @sInvoke

Remove-Item -Path @($isoFiles.Keys) -Confirm:$false
Move-Item -Path $isoName -Destination "..\$isoName" -Force -Confirm:$false</pre>



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



<p><strong>Line 4-7, 16-18</strong>: The script converts (copies) the provided YAML files to standard named YAML files. This allows the script to be used with different versions of the data and only needs a change in the <strong>$isoFiles</strong> hash table.</p>



<p><strong>Line 32</strong>: This script uses the latest version of my <a rel="noreferrer noopener" aria-label="Invoke-VMScriptPlus (opens in a new tab)" href="https://www.lucd.info/2019/11/17/invoke-vmscriptplus-v3/" target="_blank">Invoke-VMScriptPlus</a> function. The reason is twofold: the bash script is multi-line and the script used the InFile/OutFile parameters to copy the YAML files to the Linux box and the ISO file back to the calling station.</p>



<h2 class="wp-block-heading">User-Data</h2>



<p>The user-data that is used for a Photon based instance is, for now, rather simple and straightforward. </p>



<p>Note the installation of the <strong>tdnf</strong> <strong>PowerShell</strong> package (as mentioned earlier in this post) in the <strong>runcmd</strong> section.</p>



<pre class="lang:yaml decode:true ">#cloud-config
fqdn: PhotonPS.local.lab
timezone: Europe/Brussels
hostname: PhotonPS
write_files:
    - path: /etc/systemd/network/10-static.network
      permissions: 0644
      content: |
        [Match]
        Name=eth0

        [Network]
        Address=192.168.10.81/24
        Gateway=192.168.10.1
        DNS=192.168.10.2 192.168.10.3
        DHCP=no
        Domains=local.lab
        NTP=192.168.10.2 192.168.10.3
        LinkLocalAddressing=no
        LLDP=true
    - path: /etc/systemd/network/99-dhcp-en.network
      permissions: 0644
      content: |
        [Match]
        Name=e*

        [Network]
        DHCP=no
    - path: /etc/gnutls/default-priorities
      content: |
        SYSTEM=NONE:!VERS-SSL3.0:!VERS-TLS1.0:+VERS-TLS1.1:+VERS-TLS1.2:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL
runcmd:
- tdnf distro-sync
- tdnf update -y
- tdnf install bindutils -y
- tdnf install powershell -y
- tdnf install gnuTLS -y
- touch /etc/gnutls/default-priorities
package_upgrade: true
package_reboot_if_required: true
</pre>



<h2 class="wp-block-heading">Meta-data</h2>



<p>If you plan on deploying multiple instance, just make sure that the <strong>instance-id</strong> has a unique value.</p>



<pre class="lang:yaml decode:true ">instance-id: iid-local01
local-hostname: photonps
</pre>



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



<p>For a Photon instance the function is called like this.</p>



<pre class="lang:ps decode:true  ">$vmName = 'PhotonPS'

# Get credential for logging on to the guest OS
$viCred = Get-VICredentialStoreItem -Host $vmName
$secPassword = ConvertTo-SecureString -String $viCred.Password -AsPlainText -Force
$cred = [Management.Automation.PSCredential]::new($viCred.User, $secPassword)

$sCloudInitVM = @{
  OvaFile = 'D:\Repository\VMware\Photon\photon-hw13_uefi-3.0-9355405.ova'
  VmName = $vmName
  ClusterName = 'cluster'
  DsName = 'vsanDatastore'
  PgName = 'vdPg1'
  CloudConfigISO = '..\genisoimage-ps.iso'
  Photon = $true
  Credential = $cred
  Verbose = $true
}
Install-CloudInitVM @sCloudInitVM</pre>



<p>The result of this, since we used the Verbose switch, looks like this.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="687" height="533" src="https://www.lucd.info/wp-content/uploads/2019/12/photon-deploy-1.png" alt="" class="wp-image-6832" srcset="https://www.lucd.info/wp-content/uploads/2019/12/photon-deploy-1.png 687w, https://www.lucd.info/wp-content/uploads/2019/12/photon-deploy-1-300x233.png 300w" sizes="(max-width: 687px) 100vw, 687px" /></figure>



<p>Again, don&#8217;t give too much importance to the total execution time. This was a run in my lab environment, which has limited resources.</p>



<p>On a side-note, notice how the PowerCLI cmdlets used in the function all show this, imho, annoying &#8216;<em><strong>Finished execution</strong></em>&#8216; message.<br>I would love to be able to suppress these verbose messages from the PowerCLI cmdlets. That is why I launched <a rel="noreferrer noopener" aria-label="PowerCLI Idea #225 (opens in a new tab)" href="https://powercli.ideas.aha.io/ideas/PCLI-I-225?utm_source=idea_mailer&amp;utm_medium=email&amp;utm_campaign=submitted_idea" target="_blank">PowerCLI Idea #225</a> &#8220;<em>Get rid of the over-eager verbosity on PowerCLI cmdlets</em>&#8220;.</p>



<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2019/12/08/cloud-init-part-3-photon-os/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
