<?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>REST API Archives - LucD notes</title>
	<atom:link href="https://www.lucd.info/tag/rest-api/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.lucd.info/tag/rest-api/</link>
	<description>My PowerShell ramblings</description>
	<lastBuildDate>Thu, 24 Dec 2020 06:58:45 +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>REST API Archives - LucD notes</title>
	<link>https://www.lucd.info/tag/rest-api/</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>A Hitchhikers Guide to SRS 1.0.0</title>
		<link>https://www.lucd.info/2020/12/23/a-hitchhikers-guide-to-srs-1-0-0/</link>
					<comments>https://www.lucd.info/2020/12/23/a-hitchhikers-guide-to-srs-1-0-0/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Wed, 23 Dec 2020 14:08:53 +0000</pubDate>
				<category><![CDATA[Cloud-init]]></category>
		<category><![CDATA[Photon]]></category>
		<category><![CDATA[PowerCLI]]></category>
		<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[SRS]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vSphere]]></category>
		<category><![CDATA[YAML]]></category>
		<category><![CDATA[Appliance]]></category>
		<guid isPermaLink="false">https://www.lucd.info/?p=7381</guid>

					<description><![CDATA[Sometimes announcements tend to disappear in the cracks of time. When the Script [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Sometimes announcements tend to disappear in the cracks of time. When the <a href="https://github.com/vmware/script-runtime-service-for-vsphere/releases/tag/v1.0.0" target="_blank" rel="noopener">Script Runtime Service for vSphere (SRS) 1.0.0</a> was announced, I had the feeling just that happened.</p>
<p>When version <strong>1.0.0</strong> of this open-sourced (!) product was released, I had expected much more buzz on social media from <a href="https://code.vmware.com/web/tool/12.1.0/vmware-powercli" target="_blank" rel="noopener">VMware PowerCLI</a> users.&nbsp;</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1000" height="426" src="https://www.lucd.info/wp-content/uploads/2020/12/SRS_1.png" alt="" class="wp-image-7400" srcset="https://www.lucd.info/wp-content/uploads/2020/12/SRS_1.png 1000w, https://www.lucd.info/wp-content/uploads/2020/12/SRS_1-300x128.png 300w, https://www.lucd.info/wp-content/uploads/2020/12/SRS_1-768x327.png 768w, https://www.lucd.info/wp-content/uploads/2020/12/SRS_1-720x307.png 720w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure>



<p>This appliance does in fact bring an answer to a wish that many PowerShell/PowerCLI users have had for years: a &#8220;<em><strong>Scripting Host</strong></em>&#8220;!</p>



<p>This <strong>Hitchhikers Guide to SRS 1.0.0</strong> post will show how I build my own customised SRS appliance, and how I use it to run PowerShell/PowerCLI scripts.</p>



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



<h2 class="wp-block-heading">Introduction</h2>



<p>When you visit the <a href="https://github.com/vmware/script-runtime-service-for-vsphere" target="_blank" rel="noopener">Script Runtime Service for vSphere (SRS) repository</a>, you&#8217;ll notice that this <strong>open-sourced</strong> project comes with extensive <strong>documentation</strong>.</p>



<p>Always a great characteristic for an open-sourced project!</p>



<p>The installation as a VM&nbsp; (from the <a href="https://github.com/vmware/script-runtime-service-for-vsphere/releases/tag/v1.0.0" target="_blank" rel="noopener">downloadable OVF files</a>) or in a Kubernetes cluster is well documented.</p>



<p>The available documentation in the SRS repo contains instructions on how to build, and customise, your own SRS appliance under the <a href="https://github.com/vmware/script-runtime-service-for-vsphere/blob/master/BUILD_AND_RUN.md" target="_blank" rel="noopener">Build and Run</a> page.</p>
<p>This blog post documents how I did exactly that.</p>
<p>I wanted to have a fully automated, self-documenting, and repeatable method, think CI, to roll out my own SRS station.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="892" src="https://www.lucd.info/wp-content/uploads/2020/12/SRS-Build-1024x892.png" alt="" class="wp-image-7405" srcset="https://www.lucd.info/wp-content/uploads/2020/12/SRS-Build-1024x892.png 1024w, https://www.lucd.info/wp-content/uploads/2020/12/SRS-Build-300x261.png 300w, https://www.lucd.info/wp-content/uploads/2020/12/SRS-Build-768x669.png 768w, https://www.lucd.info/wp-content/uploads/2020/12/SRS-Build-720x627.png 720w, https://www.lucd.info/wp-content/uploads/2020/12/SRS-Build.png 1394w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<ol class="wp-block-list"><li>Roll out the Builder station and install the prerequisites.</li><li>Start the Build of the SRS Appliance on the Builder station</li><li>Retrieve the OVF/OVA files, created from the SRS Appliance, that came out of the build</li><li>Use the OVF/OVA to deploy your SRS station</li></ol>



<h2 class="wp-block-heading">Creating the SRS Builder</h2>



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



<p>To create an SRS OVA/OVF yourself you need a &#8220;builder&#8221; station. And on that station there need to be a number of packages installed.&nbsp;</p>
<p>I included the installation of these packages in the &#8220;build&#8221; I describe in the next section.</p>
<p>The <a href="https://github.com/vmware/script-runtime-service-for-vsphere/blob/master/appliance/buildappliance.sh" target="_blank" rel="noopener">buildappliance.sh</a> script, which you will need to run on the builder station, will use the packages to setup and configure an SRS appliance (VMware Photon based), and as the last step will generate an OVA file from the appliance.</p>
<p>That OVA file can then be used to deploy your SRS VM.</p>



<p>This is also the place where you can install extra applications and extra PowerShell modules that will be included in your DIY SRS OVA.</p>



<h3 class="wp-block-heading">Create the Builder station</h3>



<h4>Preparation</h4>



<p>I used an <a href="https://cloud-images.ubuntu.com/bionic/current/" target="_blank" rel="noopener">Ubuntu 18.04 LTS</a> station as the Builder station. As the download of the ISO is also automated, I use the <strong>Daily Build</strong>.</p>



<pre class="lang:ps decode:true  ">
#region Get the Ubuntu Bionic Cloud Image OVA Daily Build

$uri = 'https://cloud-images.ubuntu.com/bionic/current/'
$ovaName = 'bionic-server-cloudimg-amd64.ova'
$repository = 'D:\Repository\Linux\Ubuntu\'

$currentOVA = Get-ChildItem -Path "$repository\$ovaName" -ErrorAction SilentlyContinue
if (-not $currentOVA -or $currentOVA.LastWriteTime.Date -lt $now.Date) {
  $sWeb = @{
    Uri = "$uri$ovaName"
    OutFile = "$repository$ovaName"
    Verbose = $false
  }
  Invoke-WebRequest @sWeb
  $currentOVA = Get-ChildItem -Path "$repository\$ovaName"
}
#endregion
</code></pre>



<p>I also check if the <strong>DNS A</strong>&nbsp;and <strong>PTR</strong> records for the Builder station are present in DNS. The Builder station I used has an FQDN of <strong>srsbuilder.local.lab</strong> with an IP address of <strong>192.168.10.88</strong>.</p>



<pre class="lang:ps decode:true  ">
$hostName = 'srsbuilder'
$domain = 'local.lab'
$ipAddress = '192.168.10.88'

$if = Get-Netadapter
$sDns = @{
   AddressFamily = 'IPv4'
   InterfaceIndex = $if.ifIndex
}
$dns = Get-DnsClientServerAddress @sDns
$dnsServer = $dns.ServerAddresses | Get-Random
$ip = $ipAddress.Split('.')
$reverseZone = "$($ip[2]).$($ip[1]).$($ip[0]).in-addr.arpa"

try{
  Write-Verbose (Get-LogText -Text "Check DNS A record for $hostname.$domain with $ipAddress")
  $sQDns = @{
    Name = "$hostName.$domain"
    Server = $dnsServer
    ErrorAction = 'Stop'
    Verbose = $false
  }
  Resolve-DnsName @sQDns  | Out-Null
  Write-Verbose (Get-LogText -Text "DNS A record exists for $hostname.$domain with $ipAddress")
}
catch{
  Write-Verbose (Get-LogText -Text "Create A record for $hostname.$domain with $ipAddress")
  $sADns = @{
    Name = $hostName
    ZoneName = $domain
    IPv4Address = $ipAddress
    A = $true
    CreatePtr = $true
    ComputerName = $dnsServer
  }
  Add-DnsServerResourceRecord @sADns | Out-Null
}
try {
  Write-Verbose (Get-LogText -Text "Check DNS PTR record for $hostname.$domain with $ipAddress")
  $sQdns = @{
    Name = $ipAddress
    Type = 'PTR'
    Server = $dnsServer
    Verbose = $false
    ErrorAction = 'Stop'
  }
  Resolve-DnsName @sQdns | Out-Null
  Write-Verbose (Get-LogText -Text "DNS PTR record exists for $hostname.$domain with $ipAddress")
}
catch {
  Write-Verbose (Get-LogText -Text "Create PTR record for $hostname.$domain with $ipAddress")
  $sADns = @{
    Name = "$($ip[3])"
    PtrDomainName = "$hostname.$domain"
    ZoneName = $reverseZone
    ComputerName = $dnsServer
  }
  Add-DnsServerResourceRecordPtr @sADns | Out-Null
}
</code></pre>



<h4>Deploy the Builder</h4>



<p>For the rollout of this Builder station, I used the Cloud-Init method I described in <a href="https://www.lucd.info/2019/12/07/cloud-init-part-2-advanced-ubuntu/" target="_blank" rel="noopener">Cloud-Init – Part 2 – Advanced Ubuntu</a>. I had to make some small changes to the <strong>Install-CloudInitVM</strong> function from that post, so I was able to specify the memory size and the disk size. The default sizes were insufficient to run the SRS Appliance build.</p>



<p>The updated function now has <strong>MemoryGB</strong> and <strong>DiskGB</strong> parameters.</p>



<p>I also had to add a snippet to handle the issue with the $PSScriptRoot parameter when running the code from the Visual Studio Code editor.</p>



<pre class="lang:ps decode:true  ">
function Install-CloudInitVM {
  <#
.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
.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 Credential
  The credentials for a user in the VM's guest OS
.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
#>

  [cmdletbinding()]
  param(
    [string]$OvaFile,
    [string]$VmName,
    [string]$ClusterName,
    [string]$DsName,
    [string]$PgName,
    [string]$CloudConfig,
    [PSCredential[]]$Credential,
    [int]$MemoryGB,
    [int]$DiskGB
  )

#region Bypass for known issue in VSC ()
# See https://github.com/PowerShell/vscode-powershell/issues/633

  if ($psISE) {
    $dir = Split-Path -Path $psISE.CurrentFile.FullPath
  }
  else {
    if ($profile -match "VScode") {
      $dir = Split-Path $psEditor.GetEditorContext().CurrentFile.Path
    }
    else {
      $dir = $PSScriptRoot
    }
  }
#endregion

  $waitJob = (Get-Command -Name "$dir\Wait-Job.ps1").ScriptBlock
  $userData = Get-Content -Path "$dir\$CloudConfig" -Raw

  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Starting deployment of $vmName"

  $start = Get-Date

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

  $ovfProp = Get-OvfConfiguration -Ovf $ovaFile
  $ovfProp.Common.user_data.Value = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($userData))
  $ovfProp.NetworkMapping.VM_Network.Value = $pgName

  $sApp = @{
    Source = $ovaFile
    Name = $vmName
    Datastore = Get-Datastore -Name $dsName
    DiskStorageFormat = 'Thin'
    VMHost = Get-Cluster -Name $clusterName | Get-VMHost | Get-Random
    OvfConfiguration = $ovfProp
  }
  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Importing OVA"
  $vm = Import-VApp @sApp

  if($MemoryGB){
    $vm = Set-VM -VM $vm -MemoryGB $MemoryGB -Confirm:$false
  }
  if($DiskGB){
    $hd = Get-HardDisk -VM $vm | Set-HardDisk -CapacityGB $DiskGB -Confirm:$false
  }

  Write-Verbose "$(Get-Date -Format 'HH:mm:ss.fff') - Starting the VM"
  Start-VM -VM $vm -Confirm:$false -RunAsync | 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

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

  Write-Verbose "<code>nDeployment took $([math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds,0)) seconds"
}
</code></pre>



<p>The call to the <strong>Install-CloudInitVM</strong> functions is rather straightforward.</p>



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

# Get credential for logging on to the guest
# Replace with your secrets manager application when
# running this on PSv6 or higher

$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\Linux\Ubuntu\bionic-server-cloudimg-amd64.ova'
  VmName = $vmName
  ClusterName = 'cluster'
  DsName = 'vsanDatastore'
  PgName = 'vdPg1'
  CloudConfig = 'user-data-srsbuilder.yaml'
  Credential = $cred
  MemoryGB = 4
  DiskGB = 25
  Verbose = $true
}
Install-CloudInitVM @sCloudInitVM
</code></pre>



<p>Notice how I used the <strong>VICredentialStore</strong> to store and retrieve the credentials for the Builder station. When you run this from a PowerShell version greater than 5.1, you will have to replace that part with calls to the secrets manager of your choice.</p>



<p>The real strength of using this <strong>Cloud-Init</strong> method is that one can use a <strong>YAML</strong> file to specify how the target station, the Builder station, in this case, needs to be configured. And also specify which packages shall be installed on the target station as part of the deployment.</p>



<p>The following <strong>extract</strong> of my YAML file shows the packages that are required. It also includes pulling down the SRS appliance.</p>



<pre class="lang:yaml decode:true  ">
# SRS 1.0
# A) dotnet sdk
# 1) add the Microsoft package signing key
- wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
# 2) add the package repository
- dpkg -i packages-microsoft-prod.deb
# 3) donet sdk
- apt-get update
- apt-get install -y apt-transport-https
- apt-get update
- apt-get install -y dotnet-sdk-3.1
# B) docker
- curl -fsSL https://get.docker.com -o get-docker.sh
- sh get-docker.sh
# C) PowerShell
- apt-get install -y wget apt-transport-https software-properties-common
- wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
- dpkg -i packages-microsoft-prod.deb
- apt-get update
- add-apt-repository universe
- apt-get install -y powershell
# D) VMware PowerCLI
- pwsh -C '& {Install-Module -Name VMware.PowerCLI -Scope AllUsers -Force -Confirm:$false -AllowClobber}'
# E) OvfTool
- /root/ovftool-ftp.sh
- chmod 744 /root/VMware-ovftool-4.4.1-16812187-lin.x86_64.bundle
- /root/VMware-ovftool-4.4.1-16812187-lin.x86_64.bundle --eulas-agreed --required --console
# F) packer
- curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add -
- apt-get update
- apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
- apt-get update
- apt-get install packer
# X) Additional modules
- pwsh -C '& {Install-Module -Name ImportExcel -Scope AllUsers -Force -Confirm:$false -AllowClobber}'
- pwsh -C '& {Install-Module -Name Posh-Ssh -Scope AllUsers -Force -Confirm:$false -AllowClobber}'
# Y) Clone SRS repo
- mkdir /root/SRS
- git clone https://github.com/vmware/script-runtime-service-for-vsphere /root/SRS
# End SRS 1.0
</code></pre>



<h4>Verify the Builder</h4>



<p>Once the Builder station is deployed, it is useful to check that everything the creation of the SRS Appliance needs, is present.</p>



<p>A <strong>word of warning</strong>, the following code includes all <strong>versions as hard-coded</strong>. This is not ideal, and especially for products that have a daily build, this will require updating the code each time. Since this was, at the time of writing this post, not my top priority, I postponed looking for a more portable solution to a later date.</p>



<pre class="lang:ps decode:true  ">
#region Helper functions
function Get-LogText {
  param([string]$Text)

  $dt = (Get-Date).ToString('yyyyMMdd HH:mm:ss.fff')
  $app = Split-Path -Path $MyInvocation.ScriptName -Leaf
  "$dt - $app - $Text"
}
#endregion

#region Preamble

$vmName = 'SrsBuilder'

$now = Get-Date

$viCredObj = Get-VICredentialStoreItem -Host $vmName
$sObj = @{
  TypeName = 'PSCredential'
  ArgumentList = $viCredObj.User,(ConvertTo-SecureString -String $viCredObj.Password -AsPlainText -Force)
}
$cred = New-Object @sObj

$vm = Get-VM -Name $vmName
#endregion

#region dotnet SDK
$check_dotnet = @'
dotnet --version
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_dotnet
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if($result.ScriptOutput.Trim("</code>n") -ne '3.1.404'){
  Write-Host (Get-LoGText -Text "Dotnet SDK installation failure") -ForegroundColor red
}
else{
  Write-Host (Get-LogText -Text "Dotnet SDK $($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region Docker
$check_docker = @'
docker --version
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_docker
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne 'Docker version 20.10.1, build 831ebea') {
  Write-Host (Get-LoGText -Text "Docker installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "$($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region PowerShell v7
$check_PSv7 = @'
pwsh -C '& {$PSVersionTable.PSVersion.ToString()}'
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_PSv7
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne '7.1.0') {
  Write-Host (Get-LoGText -Text "PowerShell installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "PowerShell $($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region VMware PowerCLI
$check_PowerCLI = @'
pwsh -C '& {(Get-Module -Name VMware.PowerCLI -ListAvailable).Version.ToString()}'
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_PowerCLI
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne '12.1.0.17009493') {
  Write-Host (Get-LoGText -Text "VMware PowerCLI installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "VMware PowerCLI $($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region VMware ovftool
$check_ovftool = @'
ovftool --version
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_ovftool
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne 'VMware ovftool 4.4.1 (build-16812187)') {
  Write-Host (Get-LoGText -Text "VMware OVFTool installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "$($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region Packer
$check_packer = @'
packer --version
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_packer
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne '1.6.6') {
  Write-Host (Get-LoGText -Text "Packer installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "Packer $($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region Additional

#region ImportExcel
$check_ImportExcel = @'
pwsh -C '& {(Get-Module -Name ImportExcel -ListAvailable).Version.ToString()}'
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_ImportExcel
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne '7.1.1') {
  Write-Host (Get-LoGText -Text "ImportExcel installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "ImportExcel $($result.ScriptOutput.Trim("<code>n")) installation OK") -ForegroundColor green
}
#endregion

#region Posh-Ssh
$check_PoshSSH = @'
pwsh -C '& {(Get-Module -Name Posh-SSH -ListAvailable).Version.ToString()}'
'@
$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptText = $check_PoshSSH
  ScriptType = 'bash'
}
$result = Invoke-VMScript @sInvoke
if ($result.ScriptOutput.Trim("</code>n") -ne '2.3.0') {
  Write-Host (Get-LoGText -Text "Posh-SSH installation failure") -ForegroundColor red
} else {
  Write-Host (Get-LogText -Text "Posh-SSH $($result.ScriptOutput.Trim("`n")) installation OK") -ForegroundColor green
}
#endregion

#endregion
</code></pre>



<h3>Run the Build</h3>



<p>When the Builder station is deployed, and we verified that all prerequisites are in place, we can start the deployment of the SRS Applicance. From which, if all goes well, the OVA file will be generated.</p>



<p>During my experimenting with building the SRS Appliance, I stumbled on two what I suspect are issues.</p>



<p>As a bypass I build the SRS Appliance for now with <strong>Photon V3 Rev 2</strong>.</p>



<p>I did not succeed in building the SRS Appliance with <strong>Photon V3 Rev 3</strong> image. I opened an Issue for that, and I&#8217;m curious to know if the issue is a Photon issue or something in my environment.</p>
<p>Another issue, which seems to be a real, known issue, is that you apparently can not use a <strong>Distributed Switch Portgroup</strong> to build the SRS Appliance. The underlying issue seems to stem from the <strong>Packer</strong> application, more specifically the <strong>VMware-Iso</strong> builder. This builder uses <strong>VNC</strong> to &#8216;talk&#8217; with the ESXi node on which the SRS Appliance is created.</p>



<p>There are two issues here:</p>
<ul>
<li>VNC is not included anymore in ESXi since 6.7</li>
<li>With the ESXi API you can not interact with a VDS</li>
</ul>



<p>The VNC issue is resolved by changing some Packer settings. But for the VDS issue, there is no solution besides switching to the vSphere-Iso builder in the Packer step, so I had to build the SRS Appliance on a <strong>VSS Portgroup</strong>.</p>



<p>I packaged the preparation steps and the start of the SRS Appliance build in a function, named <strong>Invoke-SRSBuild</strong>.</p>



<pre class="lang:ps decode:true  ">
function Invoke-SRSBuild {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [String]$BuilderName,
    [Parameter(Mandatory)]
    [PSCredential]$Credential,
    [Switch]$PrepOnly,
    [Switch]$BuildOnly,
    [Parameter(Mandatory)]
    [ValidateSet('V3Rev2', 'V3Rev3')]
    [String]$PhotonVersion,
    [Parameter(Mandatory)]
    [String]$Portgroup,
    [Switch]$PackerLog
  )

  #region Helper functions
  function Get-LogText {
    param([string]$Text)

    $dt = (Get-Date).ToString('yyyyMMdd HH:mm:ss.fff')
    $app = Split-Path -Path $MyInvocation.ScriptName -Leaf
    "$dt - $app - $Text"
  }

  function Remove-SRSFolder {
    Param(
      [Parameter(Mandatory = $true)]
      [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.StorageResource]$Datastore
    )

    $dsBrowser = Get-View -Id $Datastore.ExtensionData.Browser
    $spec = New-Object -TypeName VMware.Vim.HostDatastoreBrowserSearchSpec
    $spec.MatchPattern = 'SRS_Appliance'
    $spec.Query = New-Object -TypeName Vmware.Vim.FolderFileQuery
    $spec.Details = New-Object -TypeName VMware.Vim.FileQueryFlags
    $result = $dsBrowser.SearchDatastore("[$($Datastore.Name)]", $spec)

    if ($result.File.Count -gt 0 -and $result.File[0]) {
      $dc = Get-VMHost -Datastore $Datastore | Get-Datacenter
      $fileMgr = Get-View FileManager
      $fileMgr.DeleteDatastoreFile("[$($Datastore.Name)] $($result.File[0].Path)", $dc.ExtensionData.MoRef)
    }
  }
  #endregion

  #region Bypass for known issue in VSC ()
  # See https://github.com/PowerShell/vscode-powershell/issues/633

  if ($psISE) {
    $dir = Split-Path -Path $psISE.CurrentFile.FullPath
  } else {
    if ($profile -match "VScode") {
      $dir = Split-Path $psEditor.GetEditorContext().CurrentFile.Path
    } else {
      $dir = $PSScriptRoot
    }
  }
  #endregion

  #region Preamble

  $vm = Get-VM -Name $BuilderName
  #endregion

  if (-not $BuildOnly.IsPresent) {

    #region SSH service
    $ssh = Get-VMHostService -VMHost $vm.VMHost | Where-Object { $_.Label -eq 'SSH' }
    if ($ssh.Running) {
      Write-Verbose (Get-LogText -Text "SSH is running on $($vm.VMHost.Name)")
    } else {
      Write-Verbose (Get-LogText -Text "Starting SSH on $($vm.VMHost.Name)")
      $ssh = Start-VMHostService -HostService $ssh -Confirm:$false
      if (-not $ssh.Running) {
        Write-Verbose (Get-LogText -Text "Could not start SSH on $($vm.VMHost.Name)")
        throw "SSH service not started on $($vm.VMHost.Name)"
      }
    }
    #endregion

    #region GuestIPHack
    $esxcli = Get-EsxCli -VMHost $vm.VMHost -V2
    $sOpt = @{
      option = '/Net/GuestIPHack'
    }
    $opt = $esxcli.system.settings.advanced.list.Invoke($sOpt)
    if ($opt.IntValue -eq 1) {
      Write-Verbose (Get-LogText -Text "GuestIPHack setting is correct on $($vm.VMHost.Name)")
    } else {
      Write-Verbose (Get-LogText -Text "GuestIPHack setting is not correct on $($vm.VMHost.Name)")
      $sOpt.Add('intvalue', [long]1)
      if ($esxcli.system.settings.advanced.set.Invoke($sOpt)) {
        Write-Verbose (Get-LogText -Text "GuestIPHack setting corrected on $($vm.VMHost.Name)")
      } else {
        Write-Verbose (Get-LogText -Text "Could not change GuestIPHack setting on $($vm.VMHost.Name)")
        throw "Could net set GuestIPHack"
      }
    }
    #endregion

    #region Make sure folder doesn't exist
    $ds = Get-Datastore -RelatedObject $vm
    Remove-SRSFolder($ds)
    #endregion

    #region Update photon-build.json
    # Suspected issue when using a vdPortgroup

    $jsonFile = New-TemporaryFile
    $photonBuilder = @{
      builder_host = $vm.VMHost.Name
      builder_host_username = $viCredObj.User
      builder_host_password = $viCredObj.Password
      builder_host_datastore = (Get-Datastore -RelatedObject $vm).Name
      builder_host_portgroup = $Portgroup
    }
    $photonBuilder | ConvertTo-Json | Set-Content -Path $jsonFile
    $sCopy = @{
      VM = $vm
      GuestCredential = $Credential
      LocalToGuest = $true
      Source = $jsonFile
      Destination = '/root/SRS/appliance/photon-builder.json'
      Force = $true
      Confirm = $false
    }
    Copy-VMGuestFile @sCopy
    Remove-Item -Path $jsonFile -Confirm:$false
    #endregion

    #region Update photon-version.json
    $photonTab = Get-Content -Path "$dir\Photon-version.json" | ConvertFrom-Json
    $photonSelected = $photonTab.Photon | Where-Object { $_.Label -eq $PhotonVersion }
    $jsonFile = New-TemporaryFile
    $sCopy = @{
      VM = $vm
      GuestCredential = $Credential
      GuestToLocal = $true
      Source = '/root/SRS/appliance/photon-version.json'
      Destination = $jsonFile
      Force = $true
      Confirm = $false
    }
    Copy-VMGuestFile @sCopy
    $photonBuilder = (Get-Content -Path $jsonFile | ConvertFrom-Json)[0]
    if ($photonBuilder.iso_url -ne $photonSelected.Uri -or $photonBuilder.iso_checksum -ne $photonSelected.CheckSum) {
      $photonBuilder.iso_url = $photonSelected.Uri
      $photonBuilder.iso_checksum = $photonSelected.CheckSum
      @($photonBuilder) | ConvertTo-Json | Set-Content -Path $jsonFile
      $sCopy = @{
        VM = $vm
        GuestCredential = $Credential
        LocalToGuest = $true
        Source = $jsonFile
        Destination = '/root/SRS/appliance/photon-version.json'
        Force = $true
        Confirm = $false
      }
      Copy-VMGuestFile @sCopy
      Remove-Item -Path $jsonFile -Confirm:$false

    }
    #endregion

    #region Handle VNC for ESXi 6.7 and later
    if ([Version]$vm.VMHost.Version -ge [Version]'6.7.0') {
      $updateJSON = 'sed' +
      ' -i.bak -e ''/vnc_disable_password/ i \      "vnc_over_websocket": true,''' +
      ' -e ''/vnc_disable_password/ i \      "insecure_connection": true,''' +
      ' -e ''/vnc_disable_password/d'' ~/SRS/appliance/photon.json'

      $sInvoke = @{
        VM = $vm
        GuestCredential = $Credential
        ScriptText = $updateJSON
        ScriptType = 'bash'
      }
      $result = Invoke-VMScript @sInvoke
    }
    #endregion

  }

  if (-not $PrepOnly.IsPresent) {

    #region Get PowerCLI folder path
    $findDir = 'pwsh -C ''& {Split-Path -Path (Split-Path -Path (Get-Module -Name VMware.PowerCLI -ListAvailable).ModuleBase)}'''
    $sInvoke = @{
      VM = $vm
      GuestCredential = $Credential
      ScriptText = $findDir
      ScriptType = 'bash'
    }
    $pcliPath = (Invoke-VMScript @sInvoke).ScriptOutput
    #endregion

    #region Start build
    $sInvoke = @{
      VM = $vm
      GuestCredential = $Credential
      ScriptText = "/root/SRS/build.sh $pcliPath"
      ScriptType = 'bash'
    }
    if ($PackerLog.IsPresent) {
      $packerLogName = 'packer.log'
      $sInvoke.ScriptText = "export PACKER_LOG=1;export PACKER_LOG_PATH='$($packerLogName)';$($sInvoke.ScriptText)"
    }
    $buildResult = Invoke-VMScript @sInvoke
    $buildResult.ScriptOutput > "$dir\Buildlog.txt"
    #endregion

    #region Rettrieve Packer log
    if($PackerLog.IsPresent){
      $sCopy = @{
        VM = $vm
        GuestCredential = $Credential
        GuestToLocal = $true
        Source = "/root/SRS/appliance/$($packerLogName)"
        Destination = "$dir\$($packerLogName)"
        Force = $true
        Confirm = $false
      }
      Copy-VMGuestFile @sCopy
    }
    #endregion

    #region Clean up
    # Packer leaves an orphaned entry in the VCSA behind

    $endpointVM = Get-VM -Name 'SRS_Appliance' -ErrorAction SilentlyContinue
    if($endpointVM){
      while($endpointVM.PowerState -ne 'PoweredOff'){
        sleep2
        $endpointVM = Get-VM -Name 'SRS_Appliance' -ErrorAction SilentlyContinue
      }
      Remove-VM -VM $endpointVM   -DeletePermanently -ErrorAction SilentlyContinue -Confirm:$false
    }
    #endregion
  }
}
</code></pre>



<p>The call to the <strong>Invoke-SRSBuilder</strong> function is again straight-forward.</p>



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

#region Credential
# Get the credentials for the SRS Builder station
# Alternative method required when using PSv6 or later

$viCredObj = Get-VICredentialStoreItem -Host $vmName
$sObj = @{
  TypeName = 'PSCredential'
  ArgumentList = $viCredObj.User, (ConvertTo-SecureString -String $viCredObj.Password -AsPlainText -Force)
}
$cred = New-Object @sObj
#endregion

$sBuild = @{
  BuilderName = $vmName
  Credential = $cred
  PhotonVersion = 'V3Rev2'
  Portgroup = 'PG1'
  PackerLog = $true
}
Invoke-SRSBuild @sBuild
</code></pre>



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



<p>When the call to the <strong>build.sh</strong> script completes successfully, there will be <strong>OVA/OVF</strong> files created on the Builder station.</p>
<p>Since the Builder station is, in my setup, a temporary station, I need to download those files to a more permanent location. The following snippet uses the <strong>Get-ScpFile</strong> cmdlet from the <a href="https://www.powershellgallery.com/packages/Posh-SSH" target="_blank" rel="noopener">Posh-Ssh</a> module to do that.</p>



<pre class="lang:ps decode:true  ">
#requires -Modules Posh-Ssh

#region Preamble

$vmName = 'SrsBuilder'

$now = Get-Date

$viCredObj = Get-VICredentialStoreItem -Host $vmName
$sObj = @{
  TypeName = 'PSCredential'
  ArgumentList = $viCredObj.User, (ConvertTo-SecureString -String $viCredObj.Password -AsPlainText -Force)
}
$cred = New-Object @sObj

$vm = Get-VM -Name $vmName

$fqdn = (Resolve-DnsName -Name $vm.ExtensionData.Guest.IpAddress).NameHost
#endregion

#region Download OVA
$sSCP = @{
  ComputerName = $fqdn
  Credential = $cred
  RemoteFile = '/root/SRS/appliance/output-vmware-iso/SRS_Appliance_1.0.0.ova'
  LocalFile = 'D:\OVA\SRS\V1.0.0\Rev2-VSS\SRS_Appliance_1.0.0.ova'
  AcceptKey = $true
}
Get-SCPFile @sSCP
#endregion
</code></pre>



<h2 class="wp-block-heading">Deploy SRS</h2>



<p>Once we have the OVA available, it is a piece of cake to deploy our SRS VM with the <a href="https://vdc-repo.vmware.com/vmwb-repository/dcr-public/f17594eb-bbe1-44a7-b7ac-b2da546936c2/21da7740-996d-481e-83e4-05d8fa7db18d/doc/Get-OvfConfiguration.html" target="_blank" rel="noreferrer noopener">Get-OvfConfiguration</a> and <a href="https://vdc-repo.vmware.com/vmwb-repository/dcr-public/f17594eb-bbe1-44a7-b7ac-b2da546936c2/21da7740-996d-481e-83e4-05d8fa7db18d/doc/Import-VApp.html" target="_blank" rel="noreferrer noopener">Import-VApp</a> cmdlets.</p>



<p>Note that deploying the SRS OVA to a Distributed Switch Portgroup is perfectly possible. The VDS issue mentioned earlier only comes into play when Packer with the VMware-Iso builder is involved.&nbsp;</p>



<pre class="lang:ps decode:true  ">
$vmName = 'srs1'
$clusterName = 'cluster'
$numCPU = 2
$memoryGB = 4
$dsName = 'vsanDatastore'
$harddiskGB = 2
$ovaPath = 'D:\OVA\SRS\V1.0.0\Rev2-VSS\SRS_Appliance_1.0.0.ova'
$networkName = 'vdPg1'

$cluster = Get-Cluster -Name $clusterName
$esx = Get-VMHost -Location $cluster | Get-Random
$ds = Get-Datastore -Name $dsName
$ovfProp = Get-OvfConfiguration -Ovf $ovaPath
$ovfProp.Common.guestinfo.hostname.Value = $vmName
$ovfProp.Common.guestinfo.ipaddress.Value = '192.168.10.43'
$ovfProp.Common.guestinfo.netmask.Value = '24'
$ovfProp.Common.guestinfo.gateway.Value = '192.168.10.1'
$ovfProp.Common.guestinfo.root_password.Value = 'Welcome2020!'
$ovfProp.Common.guestinfo.dns.Value = '192.168.10.2'
$ovfProp.Common.guestinfo.domain.Value = 'local.lab'
$ovfProp.Common.srs.vcaddress.Value = 'vcsa7.local.lab'
$ovfProp.Common.srs.vcpassword.Value = 'Welcome2020!'
$ovfProp.Common.srs.vcuser.Value = 'administrator@vsphere.local'
$ovfProp.Common.srs.vcthumbprint.Value = '5bf3246fde90fd3b7ab84144f50105114c2826e1'
$ovfProp.NetworkMapping.PG1.Value = $networkName

$vm = Import-VApp -Name $vmName -Source $ovaPath -VMHost $esx -OvfConfiguration $ovfProp -Datastore $ds
Start-VM -VM $vm -Confirm:$false

</code></pre>



<p>Your first test to see if the deployment went ok, is to try and access the swagger page on your SRS VM. The URI is <strong>https://&lt;your-SRS-FQDN&gt;/swagger</strong>. When all goes well, you will see a page like this.</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="797" src="https://www.lucd.info/wp-content/uploads/2020/12/swagger-1024x797.png" alt="" class="wp-image-7437" srcset="https://www.lucd.info/wp-content/uploads/2020/12/swagger-1024x797.png 1024w, https://www.lucd.info/wp-content/uploads/2020/12/swagger-300x234.png 300w, https://www.lucd.info/wp-content/uploads/2020/12/swagger-768x598.png 768w, https://www.lucd.info/wp-content/uploads/2020/12/swagger-720x560.png 720w, https://www.lucd.info/wp-content/uploads/2020/12/swagger.png 1471w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Using SRS</h2>



<p>Now we can start using the SRS server to run our scripts. The way to do that is quite simple. The complete process is described in the <a href="https://github.com/vmware/script-runtime-service-for-vsphere/wiki/Run-Scripts" target="_blank" rel="noopener">Run Scripts</a> section in the SRS repository.</p>



<h3 class="wp-block-heading">A Simple Sample</h3>



<p>My test installation of SRS is done on a VM that is named <strong>SRS1</strong>.</p>
<p>A basic REST API call to verify that everything is working, is to call the <strong>api/about</strong> method.</p>



<pre class="lang:ps decode:true  ">
function Invoke-SrsMethod {
  [cmdletbinding()]
  param(
    [Parameter(Mandatory)]
    [String]$FQDN,
    [Parameter(Mandatory)]
    [String]$API,
    [Parameter(Mandatory)]
    [String]$Method
  )

  $sWeb = @{
    Uri = "https://$($FQDN)/$($API)"
    Method = $Method
  }

  try {
    $result = Invoke-WebRequest @sWeb
  }
  catch [System.Net.WebException] {
    switch ($error[0].Exception.Status) {
      ([System.Net.WebExceptionStatus]::TrustFailure) {
        if ($PSVersionTable.PSVersion.Major -lt 6) {
          if (-not ([System.Management.Automation.PSTypeName]"TrustAllCertsPolicy").Type) {
            Add-Type -TypeDefinition @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem)
    {
        return true;
    }
}
"@
          }
          if ([System.Net.ServicePointManager]::CertificatePolicy.ToString() -ne "TrustAllCertsPolicy") {
            [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
          }
        } else {
          $sWeb.Add('SkipCertificateCheck', $true)
        }
        $result = Invoke-WebRequest @sWeb
      }
      Default {
        Write-Error "Unhandled WebException $($error[0].Exception.Status)"
      }
    }
  }
  catch {
    Write-Error "Unhandled exception code $($error[0].Exception.gettype().Name)"
  }

  switch ($result.StatusCode) {
    200 {
      $result
    }
    Default {
      Write-Error "Unhandled StatusCode $($result.StatusCode)"
    }
  }
}

$vmName = 'srs1'

$vm = Get-VM -Name $vmName
$fqdn = (Resolve-DnsName -Name $vm.ExtensionData.Guest.IpAddress).NameHost

$about = Invoke-SrsMethod -FQDN $fqdn -API 'api/about' -Method 'Get'
$about.Content | ConvertFrom-Json
</code></pre>



<p>You notice that I created a function Invoke-SRSMethod instead of just calling the Invoke-WebRequest cmdlet directly. The reason for creating that function is that I needed to be able to bypass invalid certificates.</p>
<p>With PSv6 that has become easy, since the <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-7.1" target="_blank" rel="noopener">Invoke-WebRequest</a> cmdlet now has the <strong>SkipCertificateCheck</strong> switch.&nbsp;</p>
<p>But I wanted to have a function that would also work in a PSV5.1 environment, hence the function and the logic in there.</p>
<p>When all goes well, that snippet should return the following.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="672" height="91" src="https://www.lucd.info/wp-content/uploads/2020/12/srs-call.png" alt="" class="wp-image-7442" srcset="https://www.lucd.info/wp-content/uploads/2020/12/srs-call.png 672w, https://www.lucd.info/wp-content/uploads/2020/12/srs-call-300x41.png 300w" sizes="auto, (max-width: 672px) 100vw, 672px" /></figure>



<h3>A PowerCLI example</h3>



<p>The full sequence for running code on the SRS station is perfectly explained in the <a href="https://github.com/vmware/script-runtime-service-for-vsphere/wiki/Run-Scripts" target="_blank" rel="noopener">Run Scripts</a> section on the <a href="https://github.com/vmware/script-runtime-service-for-vsphere" target="_blank" rel="noopener">SRS repository</a>.</p>



<p>A full, scripted example, excluding the earlier <strong>Invoke-SRSMethod</strong> function, could look like this.</p>



<pre class="lang:ps decode:true  ">
#region Preamble
$vmName = 'srs1'

$vm = Get-VM -Name $vmName
$fqdn = (Resolve-DnsName -Name $vm.ExtensionData.Guest.IpAddress).NameHost

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

$headers = @{
  "accept" = "application/json"
  "content-type" = "application/json"
}
#endregion

#region Login
$sLogon = @{
  FQDN = $fqdn
  API = '/api/auth/login'
  Method = 'Post'
  Credential = $cred
  Headers = $headers
}
$logon = Invoke-SrsMethod @sLogon
$headers.Add('X-SRS-API-KEY', $logon.Headers['X-SRS-API-KEY'])
#endregion

#region Create Runspace
$sRSCreate = @{
  FQDN = $fqdn
  API = '/api/runspaces'
  Method = 'Post'
  Credential = $cred
  Headers = $headers
  Body = @{
    name = 'MyRS'
    run_vc_connection_script = $true
  }
}
$createRS = Invoke-SrsMethod @sRSCreate
$rs = $createRS.Content | ConvertFrom-Json
#endregion

#region Wait till RS is ready
while ($rs.state -eq 'Creating') {
  $sRSGet = @{
    FQDN = $fqdn
    API = "/api/runspaces/$($rs.Id)"
    Method = 'Get'
    Headers = $headers
  }
  $getRS = Invoke-SrsMethod @sRSGet
  $rs = $getRS.Content | ConvertFrom-Json
}
#endregion

#region Run Script
$code = {
  Get-VM
}
$sSCRCreate = @{
  FQDN = $fqdn
  API = '/api/script-executions'
  Method = 'Post'
  Headers = $headers
  Body = @{
    runspace_id = $rs.id
    name = 'MyScript'
    script = $code.ToString()
    script_parameters = @()
  }
}
$createSCR = Invoke-SrsMethod @sSCRCreate
$scr = $createSCR.Content | ConvertFrom-Json
#endregion

#region Wait for Script to end
while($scr.state -eq 'running'){
$sSCRCreate = @{
  FQDN = $fqdn
  API = "/api/script-executions/$($scr.id)"
  Method = 'Get'
  Headers = $headers
}
$getSCR = Invoke-SrsMethod @sSCRCreate
$scr = $getSCR.Content | ConvertFrom-Json
}
#endregion

#region Retrieve Script output
$sSCROut = @{
  FQDN = $fqdn
  API = "/api/script-executions/$($scr.id)/output"
  Method = 'Get'
  Headers = $headers
}
$outSCR = Invoke-SrsMethod @sSCROut
$outSCR.Content | ConvertFrom-Json
#endregion

#region Retrieve Script streams
'information', 'error', 'warning', 'debug', 'verbose' |
ForEach-Object -Process {
  $sSCRStr = @{
    FQDN = $fqdn
    API = "/api/script-executions/$($scr.id)/streams/$($_)"
    Method = 'Get'
    Headers = $headers
  }
  $getStream = Invoke-SrsMethod @sScrStr
  if($getStream.Content -ne '[]'){
    Write-Host "--- $_ ---" -ForegroundColor Green
    $getStream.Content | ConvertFrom-Json | Out-Default
    Write-Host "------------" -ForegroundColor Green
  }
}
#endregion

#region Remove Runspace
$sRSDel = @{
  FQDN = $fqdn
  API = "/api/runspaces/$($rs.id)"
  Method = 'Delete'
  Headers = $headers
}
$rsDel = Invoke-SrsMethod @sRSDel
$rsDel.Content | ConvertFrom-Json
#endregion

#region Logout
$sLogout = @{
  FQDN = $fqdn
  API = "/api/auth/logout"
  Method = 'Post'
  Headers = $headers
}
$srsLogout = Invoke-SrsMethod @sLogout
#endregion
</code></pre>



<h3 class="wp-block-heading">A Function</h3>



<p>That turned out to be a whole lot of code to just run a simple Get-VM.</p>



<p>But since we will be using most of the time the same logic, we can easily turn that into a function. That will make submitting code to run on the SRS a lot simpler.</p>



<p>And while we are at it, let&#8217;s include an option to run the code from an existing .ps1 file.</p>



<pre class="lang:ps decode:true  ">
Function Invoke-SRSScript {
  [cmdletbinding()]
  param(
    [Parameter(Mandatory = $true)]
    [String]$SRSHost,
    [Parameter(ParameterSetName = 'ScriptBlock', Mandatory = $true)]
    [scriptblock]$Code,
    [Parameter(ParameterSetName = 'ScriptFile', Mandatory = $true)]
    [String]$Path,
    [Parameter(Mandatory = $true)]
    [PSCredential]$VCCredential
  )

  #region Helper functions
  function Invoke-SrsMethod {
    [cmdletbinding()]
    param(
      [Parameter(Mandatory)]
      [String]$FQDN,
      [Parameter(Mandatory)]
      [String]$API,
      [Parameter(Mandatory)]
      [String]$Method,
      [PSCredential]$Credential,
      [PSObject]$Headers,
      [PSObject]$Body
    )

    $sWeb = @{
      Uri = "https://$($FQDN)$($API)"
      Method = $Method
    }
    if ($Credential) {
      $sWeb.Add('Credential', $Credential)
    }
    if ($Headers) {
      $sWeb.Add('Headers', $Headers)
    }
    if ($Body) {
      $sWeb.Add('Body', ($Body | ConvertTo-Json))
    }

    try {
      $result = Invoke-WebRequest @sWeb
    } catch [System.Net.WebException] {
      switch ($error[0].Exception.Status) {
        ([System.Net.WebExceptionStatus]::TrustFailure) {
          if ($PSVersionTable.PSVersion.Major -lt 7) {
            if (-not ([System.Management.Automation.PSTypeName]"TrustAllCertsPolicy").Type) {
              Add-Type -TypeDefinition @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem)
    {
        return true;
    }
}
"@
            }
            if ([System.Net.ServicePointManager]::CertificatePolicy.ToString() -ne "TrustAllCertsPolicy") {
              [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
            }
          } else {
            $sWeb.Add('SkipCertificateCheck', $true)
          }
          $result = Invoke-WebRequest @sWeb
        }
        Default {
          Write-Error "Unhandled WebException $($error[0].Exception.Status)"
        }
      }
    } catch {
      Write-Error "Unhandled exception code $($error[0].Exception.gettype().Name)"
      $error[0].Exception | Format-Custom
    }

    switch ($result.StatusCode) {
      200 {
        $result
      }
      202 {
        $result
      }
      401 {
        Write-Error "Unauthorized $($result.StatusCode)"
      }
      500 {
        Write-Error "Server error $($result.StatusCode)"
      }
      Default {
        Write-Error "Unhandled StatusCode $($result.StatusCode)"
      }
    }
  }
  #endregion

  #region Preamble

  $vm = Get-VM -Name $SRSHost
  $fqdn = (Resolve-DnsName -Name $vm.ExtensionData.Guest.IpAddress).NameHost

  switch ($PSCmdlet.ParameterSetName){
    'ScriptBlock' {
      $strCode = $Code.ToString()
    }
    'ScriptFile' {
      $strCode = Get-Content -Path $Path | Out-String
    }
  }

  $headers = @{
    "accept" = "application/json"
    "content-type" = "application/json"
  }
  #endregion

  #region Login
  $sLogon = @{
    FQDN = $fqdn
    API = '/api/auth/login'
    Method = 'Post'
    Credential = $VCCredential
    Headers = $headers
  }
  $logon = Invoke-SrsMethod @sLogon
  $headers.Add('X-SRS-API-KEY', $logon.Headers['X-SRS-API-KEY'])
  #endregion

  #region Create Runspace
  $sRSCreate = @{
    FQDN = $fqdn
    API = '/api/runspaces'
    Method = 'Post'
    Credential = $cred
    Headers = $headers
    Body = @{
      name = 'MyRS'
      run_vc_connection_script = $true
    }
  }
  $createRS = Invoke-SrsMethod @sRSCreate
  $rs = $createRS.Content | ConvertFrom-Json
  #endregion

  #region Wait till RS is ready
  while ($rs.state -eq 'Creating') {
    $sRSGet = @{
      FQDN = $fqdn
      API = "/api/runspaces/$($rs.Id)"
      Method = 'Get'
      Headers = $headers
    }
    $getRS = Invoke-SrsMethod @sRSGet
    $rs = $getRS.Content | ConvertFrom-Json
  }
  #endregion

  #region Run Script
  $sSCRCreate = @{
    FQDN = $fqdn
    API = '/api/script-executions'
    Method = 'Post'
    Headers = $headers
    Body = @{
      runspace_id = $rs.id
      name = 'MyScript'
      script = $strCode
      script_parameters = @()
    }
  }
  $createSCR = Invoke-SrsMethod @sSCRCreate
  $scr = $createSCR.Content | ConvertFrom-Json
  #endregion

  #region Wait for Script to end
  while ($scr.state -eq 'running') {
    $sSCRCreate = @{
      FQDN = $fqdn
      API = "/api/script-executions/$($scr.id)"
      Method = 'Get'
      Headers = $headers
    }
    $getSCR = Invoke-SrsMethod @sSCRCreate
    $scr = $getSCR.Content | ConvertFrom-Json
  }
  #endregion

  #region Retrieve Script output
  $sSCROut = @{
    FQDN = $fqdn
    API = "/api/script-executions/$($scr.id)/output"
    Method = 'Get'
    Headers = $headers
  }
  $outSCR = Invoke-SrsMethod @sSCROut
  $outSCR.Content | ConvertFrom-Json
  #endregion

  #region Retrieve Script streams
  $streams = 'information','error','warning','debug','verbose'

  $streams | ForEach-Object -Process {
    $sSCRStr = @{
      FQDN = $fqdn
      API = "/api/script-executions/$($scr.id)/streams/$($_)"
      Method = 'Get'
      Headers = $headers
    }
    $getStream = Invoke-SrsMethod @sScrStr
    if ($getStream.Content -ne '[]') {
      $out = ($getStream.Content | ConvertFrom-Json).message | Out-String
      switch($_){
        'information' {
          Write-Information -MessageData $out
        }
        'error' {
          Write-Error -Message $out
        }
        'warning' {
          Write-Warning -Message $out
        }
        'debug' {
          Write-Debug -Message $out
        }
        'verbose' {
          Write-Verbose -Message $out
        }
      }
    }
  }
  #endregion

  #region Remove Runspace
  $sRSDel = @{
    FQDN = $fqdn
    API = "/api/runspaces/$($rs.id)"
    Method = 'Delete'
    Headers = $headers
  }
  $rsDel = Invoke-SrsMethod @sRSDel
  $rsDel.Content | ConvertFrom-Json
  #endregion

  #region Logout
  $sLogout = @{
    FQDN = $fqdn
    API = "/api/auth/logout"
    Method = 'Post'
    Headers = $headers
  }
  $srsLogout = Invoke-SrsMethod @sLogout
  #endregion

}
</code></pre>



<p>This <strong>Invoke-SRSScript</strong> function can be used in two variations (parametersets). With the <strong>Code</strong> parameter, you pass a ScriptBlock.</p>



<pre class="lang:ps decode:true  ">
$sScript = @{
  SRSHost = 'srs1'
  Code = { Get-VM }
  VCCredential = $cred
}
Invoke-SRSScript @sScript
</code></pre>



<p>With the <strong>Path</strong> parameter, you point to a .ps1 file.</p>



<pre class="lang:ps decode:true  ">$sScript = @{
  SRSHost = 'srs1'
  Path = 'D:\Git\SRS-Explore\Use\Sample.ps1'
  VCCredential = $cred
}
</code></pre>



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



<p>The SRS comes with a number of preset values, see the <a href="https://github.com/vmware/script-runtime-service-for-vsphere/wiki/Initial-Configuration" target="_blank" rel="noopener">Initial Configuration</a> documentation. In some situations, you might want to change one or more of these settings.</p>
<p>The obvious solution is to rebuild your SRS OVA with the desired settings, and re-deploy your SRS VM.&nbsp;</p>
<p>But you can also change these settings on the fly on an existing and running SRS. The following snippet is just an example where I change the <strong>script-runtime-service</strong> setting from the default 10 minutes to 20 minutes.</p>



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

# Get the SRS credentials

$viCred = Get-VICredentialStoreItem -Host $vmName
$secPassword = ConvertTo-SecureString -String $viCred.Password -AsPlainText -Force
$cred = [Management.Automation.PSCredential]::new($viCred.User, $secPassword)

$vm = Get-VM -Name $vmName

#region Change SRS setting

$code = @'
kubectl get cm service-settings -n script-runtime-service -o yaml | sed -e 's/\("MaxRunspaceIdleTimeMinutes": \).*/\120,/' | kubectl apply -f -
'@

$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptType = 'bash'
  ScriptText = $code
}
Invoke-VMScript @sInvoke
#endregion

#region Check new SRS settings

$code = @'
kubectl get cm service-settings -n script-runtime-service -o yaml
'@

$sInvoke = @{
  VM = $vm
  GuestCredential = $cred
  ScriptType = 'bash'
  ScriptText = $code
}
Invoke-VMScript @sInvoke

#endregion
</code></pre>



<p>Note that such a change will not be applied immediately, it might take some time (we are talking minutes).</p>



<h2 class="wp-block-heading">Epilogue</h2>



<p>This concludes, for now, my somewhat lengthy <strong>Hitchhikers Guide to SR</strong>S<strong> 1.0.0</strong>, which resulted from my playing/testing/exploring the Script Runtime Service for vSphere (SRS) 1.0.0.</p>



<p>Is this something to should have gotten more attention when it was released?</p>
<p>Definitely!</p>



<p>I will surely be doing some more experimenting with the new and shining tool.</p>
<p>Enjoy!</p>


]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2020/12/23/a-hitchhikers-guide-to-srs-1-0-0/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>PowerCLI, REST API and a sample module for Tag management</title>
		<link>https://www.lucd.info/2018/02/22/powercli-rest-api-sample-module-tag-management/</link>
					<comments>https://www.lucd.info/2018/02/22/powercli-rest-api-sample-module-tag-management/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Thu, 22 Feb 2018 08:23:03 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[rCisTag]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[SOAP]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[TechGenix]]></category>
		<guid isPermaLink="false">http://www.lucd.info/?p=5782</guid>

					<description><![CDATA[As most of you might know by now, VMware is moving away from [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>As most of you might know by now, VMware is moving away from SOAP and going to <a href="https://blogs.vmware.com/code/2017/02/02/getting-started-vsphere-automation-sdk-rest/" target="_blank" rel="noopener">REST API</a>.</p>
<p>Is this something you should know about? Yes, you should!</p>
<p>In a two-part article on <a href="https://techgenix.com/" target="_blank" rel="noopener">TechGenix</a>, I wrote down my thoughts and observations on REST API. The article goes into what this move towards the REST API might mean for you as a scripter/administrator.</p>
<p><img loading="lazy" decoding="async" class="wp-image-5784 size-full alignnone" src="https://www.lucd.info/wp-content/uploads/2018/02/rCisTag.png" alt="" width="351" height="434" srcset="https://www.lucd.info/wp-content/uploads/2018/02/rCisTag.png 351w, https://www.lucd.info/wp-content/uploads/2018/02/rCisTag-243x300.png 243w" sizes="auto, (max-width: 351px) 100vw, 351px" /></p>
<p>&nbsp;</p>
<p>Since an article on coding, without a coding example doesn&#8217;t make much sense, I added a module, named <a href="https://github.com/vmware/PowerCLI-Example-Scripts/tree/master/Modules/rCisTag" target="_blank" rel="noopener">rCisTag</a> on the <a href="https://github.com/vmware/PowerCLI-Example-Scripts" target="_blank" rel="noopener">PowerCLI Examples repository</a>.</p>
<h2>Articles</h2>
<p>Enjoy reading <strong>Part 1</strong>, <a href="https://techgenix.com/vmware-rest-api/" target="_blank" rel="noopener">Understanding the VMware REST API interface</a>!</p>
<p>and <strong>Part 2</strong>, <a href="https://techgenix.com/soap-vs-rest-vmware-environments/" target="_blank" rel="noopener">SOAP vs REST for performing tasks in VMware environments</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2018/02/22/powercli-rest-api-sample-module-tag-management/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Search VMTN with REST API</title>
		<link>https://www.lucd.info/2017/12/31/search-vmtn-rest-api/</link>
					<comments>https://www.lucd.info/2017/12/31/search-vmtn-rest-api/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Sun, 31 Dec 2017 18:53:19 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[search]]></category>
		<category><![CDATA[VMTN]]></category>
		<guid isPermaLink="false">http://www.lucd.info/?p=5723</guid>

					<description><![CDATA[REST API are (nearly) everywhere! VMware&#8217;s VMTN website is no exception. I already [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Representational_state_transfer" target="_blank" rel="noopener noreferrer">REST API</a> are (nearly) everywhere! VMware&#8217;s <a href="https://communities.vmware.com/welcome" target="_blank" rel="noopener noreferrer">VMTN</a> website is no exception. I already did a post on <a href="https://www.lucd.info/2012/02/29/automate-your-vmtn-search/" target="_blank" rel="noopener noreferrer">Automate Your VMTN Search</a>, but that was entirely based on constructing URI and interpreting the returned webpages. For the occasion of the <a href="http://www.topitvideos.com/vmworld-2017-ser1875bu-the-power-hour-vmware-vsphere-powercli-10th-birthday-edition-vmware-vmworld/" target="_blank" rel="noopener noreferrer">PowerCLI&#8217;s 10th Birthday session</a> at VMworld, I wanted to produce some <strong>InfoGraphs</strong> on the <a href="https://communities.vmware.com/community/vmtn/automationtools/powercli" target="_blank" rel="noopener noreferrer">PowerCLI Community</a>. For those <strong>InfoGraphs</strong> I needed to harvest data from said VMTN Community, and I looked for a better way to do this. That is where the <a href="https://community.jivesoftware.com/docs/DOC-112244" target="_blank" rel="noopener noreferrer">REST API</a> offered by the <a href="https://www.jivesoftware.com/" target="_blank" rel="noopener noreferrer">Jive software,</a> om which the VMTN website is hosted, came in handy.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5731" src="https://www.lucd.info/wp-content/uploads/2017/12/vmtn-search-jive.png" alt="" width="476" height="314" srcset="https://www.lucd.info/wp-content/uploads/2017/12/vmtn-search-jive.png 476w, https://www.lucd.info/wp-content/uploads/2017/12/vmtn-search-jive-300x198.png 300w" sizes="auto, (max-width: 476px) 100vw, 476px" /></p>
<p>The functions I ended up with, are also a good example of how easy it is to consume REST API through PowerShell. And they also show how the basic techniques to work with REST API can be reused. Check out my <a href="https://github.com/lucdekens/VMTNRest" target="_blank" rel="noopener noreferrer">VMTNRest</a> repo.</p>
<p><span id="more-5723"></span></p>
<h2>VMTN and Jive</h2>
<p>The VMTN website is currently build on the <a href="https://blogs.vmware.com/vmtn/2017/03/vmtn-jive-8.html" target="_blank" rel="noopener noreferrer">Jive 8</a>, and besides the difference in some names, the major components of a Jive 8 hosted platform can be found back. These components are quite simple, but one needs to understand what is where, to be able to work with the REST API.</p>
<p>On a side note: working with REST API is quite straight-forward, the difficult part consists of understanding the setup and the logic of the platform behind the REST API. And the Jive platform is no exception.</p>
<p>There are four major <strong>endpoint services</strong> in a Jive environment. Since, for now, we only want to <strong>query</strong> the VMTN Communities, we will primarily be calling the REST API against the <strong>Content</strong> and <strong>People </strong>service endpoints. This will allow us to work with all the threads and documents published in the VMTN communities. And it will also allow us to gather information about users and groups in the VMTN communities.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5736 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/12/endpoints-298x300.png" alt="" width="298" height="300" srcset="https://www.lucd.info/wp-content/uploads/2017/12/endpoints-298x300.png 298w, https://www.lucd.info/wp-content/uploads/2017/12/endpoints-150x150.png 150w, https://www.lucd.info/wp-content/uploads/2017/12/endpoints-170x170.png 170w, https://www.lucd.info/wp-content/uploads/2017/12/endpoints.png 627w" sizes="auto, (max-width: 298px) 100vw, 298px" /></p>
<p>For a complete overview of the Jive REST API see the <a class="jive-link-external-small" href="https://developers.jivesoftware.com/api/v3/cloud/rest/index.html" target="_blank" rel="nofollow noopener noreferrer">Jive v3 REST API Reference Documentation</a>.</p>
<h2>Jive REST API calls</h2>
<p>Basically the Jive REST API calls are similar to most of the other REST API calls. You compose the URI, eventually with a number of optional keywords and specify the method to be used. The data is returned in JSON format.</p>
<p>But the like I mentioned before, there are a couple of Jive specific elements one needs to be aware off.</p>
<p>Let&#8217;s look at an example. The following is a typical thread as can be found in any of the VMTN communities. The numbers in these pictures refer to the Annotations further on in the post.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5749 size-large" src="https://www.lucd.info/wp-content/uploads/2017/12/thread2-1024x294.png" alt="" width="770" height="221" srcset="https://www.lucd.info/wp-content/uploads/2017/12/thread2-1024x294.png 1024w, https://www.lucd.info/wp-content/uploads/2017/12/thread2-300x86.png 300w, https://www.lucd.info/wp-content/uploads/2017/12/thread2-768x221.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/thread2-720x207.png 720w, https://www.lucd.info/wp-content/uploads/2017/12/thread2.png 1065w" sizes="auto, (max-width: 770px) 100vw, 770px" /></p>
<p>The content of the thread is again nothing out of the extra ordinary.</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5750 size-large" src="https://www.lucd.info/wp-content/uploads/2017/12/thread3-1014x1024.png" alt="" width="770" height="778" srcset="https://www.lucd.info/wp-content/uploads/2017/12/thread3-1014x1024.png 1014w, https://www.lucd.info/wp-content/uploads/2017/12/thread3-297x300.png 297w, https://www.lucd.info/wp-content/uploads/2017/12/thread3-768x776.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/thread3-720x727.png 720w" sizes="auto, (max-width: 770px) 100vw, 770px" /></p>
<p>&nbsp;</p>
<p>If we &#8220;Get&#8221; that same thread through the REST API, there are a number of things to take note of.</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5756" src="https://www.lucd.info/wp-content/uploads/2017/12/thread1-1.png" alt="" width="2055" height="5073" srcset="https://www.lucd.info/wp-content/uploads/2017/12/thread1-1.png 2055w, https://www.lucd.info/wp-content/uploads/2017/12/thread1-1-122x300.png 122w, https://www.lucd.info/wp-content/uploads/2017/12/thread1-1-768x1896.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/thread1-1-415x1024.png 415w, https://www.lucd.info/wp-content/uploads/2017/12/thread1-1-720x1777.png 720w" sizes="auto, (max-width: 2055px) 100vw, 2055px" /></p>
<p>By the way, this last picture is the output of the REST API call made through the <a href="https://restlet.com/modules/client/" target="_blank" rel="noopener noreferrer">Chrome RestLet Client</a>, but could have been done through any other REST client, like for example <a href="https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en" target="_blank" rel="noopener noreferrer">PostMan</a>.</p>
<h3>Annotation</h3>
<ol>
<li>Like any other REST API call, each call is composed of three components
<ol style="list-style-type: upper-alpha;">
<li>The VMTN Jive community <strong>location</strong>, i.e. https://communities.vmware.com</li>
<li>The Jive REST API <strong>prefix</strong>, i.e. /api/core/v3</li>
<li>The REST <strong>endpoint</strong>, i.e. contents</li>
<li>Optional <strong>fields</strong>. In this example we ask the endpoint to return just one result at a time, and to only return entries of type &#8216;discussion&#8217;</li>
</ol>
</li>
<li>The Jive REST API accepts three <a href="https://developers.jivesoftware.com/api/v3/cloud/rest/index.html#authentication" target="_blank" rel="noopener noreferrer">types of authentication</a>. In our example we used <strong>Basic Authentication</strong>. The authentication string is passed as a Header in the REST API call. We also specify that all data shall be in the JSON format. <strong><span style="background-color: yellow;">Update</span></strong>: it looks as if <strong>Basic Authentication</strong> to call the REST API is not supported in the current Jive version for &#8220;federated&#8221; user accounts. I&#8217;m still looking into that.</li>
<li>The &#8220;throw&#8221; line is is a security measure. It avoids high-jacking a Jive session through a so-called XSS attack in some browsers. Since we are not accessing the REST endpoint through a browser, we can just ignore the &#8220;throw&#8221; line.</li>
<li>The links field contains the URI for the previous and next page. If an endpoint returns more than a page-full of objects, the &#8220;next&#8221; field can be used to go to the next set of objects. Similarly, the URI in the &#8220;previous&#8221; field can be used to go back in the data.</li>
<li>Under the &#8220;list&#8221; field, the actual data concerning a Jive resource is returned.</li>
<li>Each Jive object returned by the REST endpoints, contains a &#8220;resources&#8221; field. In the example we are looking at a &#8220;discussion&#8221; object. Under the resources field we find several links to related information. For example, the &#8220;attachments&#8221; field will show what actions (&#8220;POST&#8221; and &#8220;GET&#8221;) are allowed, and also the URI to access these attachments.</li>
<li>The returned discussion object has several fields that provide extra information. The object contains fields to indicate how many users are following the discussion, how many like a specific reply, when the discussion was created and last updated.</li>
<li>The &#8220;author&#8221; field provides information about the user that created the discussion. Again, the &#8220;resources&#8221; field provides links to access related information about the author.</li>
<li>The &#8220;content&#8221; of the discussion. Note that this is provided as HTML</li>
<li>Two important fields related to discussions are the &#8220;question&#8221; and &#8220;resolved&#8221; fields. The first one indicates if the discussion is actually marked as a question, the second one indicates if there has been an accepted answer to the discussion or not.</li>
<li>The &#8220;startIndex&#8221; field shows the index of the next reply in the discussion. This also allows us to determine how many answer there have already been to the discussion, just note that the index is zero-based</li>
<li>The &#8220;parent&#8221; for a discussion object is the place, or community in VMTN terminology, where the discussion was started.</li>
</ol>
<h2>The Script</h2>
<p>The script is in fact a module, named <a href="https://github.com/lucdekens/VMTNRest" target="_blank" rel="noopener noreferrer"><strong>VMTNRest</strong></a>. The module contains a number of functions to access VMTN communities, authors and discussions.</p>
<p>You always start off with the <strong>Initialize-VMTNRest</strong> cmdlet. This cmdlet sets some module-wide values, and originally it was the intention to allow the caller to pass credentials. But apparently the current Jive version doen&#8217;t allow REST API calls from &#8220;<strong>federated</strong>&#8221; users with <strong>Basic Authentication</strong>. See <a href="https://community.jivesoftware.com/thread/276827" target="_blank" rel="noopener noreferrer">here</a> and <a href="https://stackoverflow.com/questions/29377067/jive-rest-api-returning-code401-messagebad-credentials-using-basic-aut" target="_blank" rel="noopener noreferrer">here</a> for more information on that issue. I&#8217;m still looking for a possible bypass, but for now, the cmdlets in the VMTNRest module only allow access to the &#8220;<strong>public</strong>&#8221; VMTN resources. If you have access to <strong>private communities</strong>, I&#8217;m afraid that for now you will not be able to access these resources.</p>
<p>Another parameter that could be of use is the <strong>MaxCount</strong> parameter. It defines how many objects the REST API returns on one call. The default is 100, also the maximum the Jive software allows. You can set this to a number between 1 and 100.</p>
<p>The <strong>MaxSamples</strong> parameter, which is available on the other cmdlets in the VMTNRest module, defines the maximum total number of objects that are returned in one call to the cmdlet. This parameter is set by default to 100, but can be changed. Be warned though, there are a lot of objects available in some categories. You don&#8217;t want to wait for example till the REST API has returned all +3400000 author objects <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>The other three public cmdlets retrieve objects from the VMTN community. They are <strong>Get-VMTNAuthor</strong>, <strong>Get-VMTNCommunity</strong> and <strong>Get-VMTNContent</strong>. Some of these objects are quite rich  in content, as we will see in the next section.</p>
<h2>Use Cases</h2>
<p>As we said earlier, you always have to start with the Initialize-VMTNRest cmdlet. In the following example we will take all the defaults, and not use a proxy.</p>
<p>One way to get to know the VMTN platform, is to have a look which communities and groups are available. The following example uses one keywword, and yes, I might be a bit biased in my choice of examples <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

Get-VMTNCommunity -Community PowerCLI |
Select-Object -Property name,description,type</pre><p>This returns the following.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5761 size-large" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-1-1024x127.png" alt="" width="770" height="95" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-1-1024x127.png 1024w, https://www.lucd.info/wp-content/uploads/2017/12/sample-1-300x37.png 300w, https://www.lucd.info/wp-content/uploads/2017/12/sample-1-768x95.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/sample-1-720x89.png 720w, https://www.lucd.info/wp-content/uploads/2017/12/sample-1.png 1076w" sizes="auto, (max-width: 770px) 100vw, 770px" /></p>
<p>Note that &#8220;<strong>place</strong>&#8221; is the internal Jive name for what we know as a VMTN community. Also note that the keywork &#8220;PowerCLI&#8221; doesn&#8217;t seem to appear in the name of a returned community. The reason is that internally the REST API looks at more than just the name of a community.</p>
<p>If you want to look for an exact match in the name, you can use the <strong>ExactMatch</strong> parameter.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

Get-VMTNCommunity -Community 'VMware PowerCLI' -ExactMatch  |
Select-Object -Property name,description,type</pre><p>Now we only get the specific community we were looking for.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5762 size-full" src="https://www.lucd.info/wp-content/uploads/2017/12/sample2.png" alt="" width="826" height="85" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample2.png 826w, https://www.lucd.info/wp-content/uploads/2017/12/sample2-300x31.png 300w, https://www.lucd.info/wp-content/uploads/2017/12/sample2-768x79.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/sample2-720x74.png 720w" sizes="auto, (max-width: 826px) 100vw, 826px" /></p>
<p>Besides communities, there are also blogs and groups available in VMTN.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

Get-VMTNCommunity -Community PowerCLI -IncludeBlog -IncludeGroup |
Select-Object -Property name,type,description</pre><p>And we get entries that have &#8220;PowerCLI&#8221; in their name or description.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5763" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-3.png" alt="" width="1278" height="200" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-3.png 1278w, https://www.lucd.info/wp-content/uploads/2017/12/sample-3-300x47.png 300w, https://www.lucd.info/wp-content/uploads/2017/12/sample-3-768x120.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/sample-3-1024x160.png 1024w, https://www.lucd.info/wp-content/uploads/2017/12/sample-3-720x113.png 720w" sizes="auto, (max-width: 1278px) 100vw, 1278px" /></p>
<p>All the documents and entries in communities and blogs have an author. With the Get-VMTNAuthor we can explore those.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

Get-VMTNAuthor -Author lucd | 
Select displayName,id,
    @{N='Published';E={([DateTime]$_.published).ToLocalTime()}},
    @{N='Updated';E={([DateTime]$_.updated).ToLocalTime()}}</pre><p>This returns</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5764" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-4.png" alt="" width="506" height="321" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-4.png 506w, https://www.lucd.info/wp-content/uploads/2017/12/sample-4-300x190.png 300w" sizes="auto, (max-width: 506px) 100vw, 506px" /></p>
<p>The REST API looked for all authors whose name starts with &#8220;lucd&#8221;. But we also can go for an exact match.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

Get-VMTNAuthor -Author lucd -ExactMatch | 
Select displayName,id,
    @{N='Published';E={([DateTime]$_.published).ToLocalTime()}},
    @{N='Updated';E={([DateTime]$_.updated).ToLocalTime()}}</pre><p>And we get the single object back.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5766" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-5.png" alt="" width="482" height="82" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-5.png 482w, https://www.lucd.info/wp-content/uploads/2017/12/sample-5-300x51.png 300w" sizes="auto, (max-width: 482px) 100vw, 482px" /></p>
<p>We can also do some historic research with the DateTime parameters. Do we still have authors around that were created in the early days of VMTN. And we assume that the official start date was July 10th in 2003.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

$origin = Get-Date "10 Jul 2003"
Get-VMTNAuthor -CreatedAfter $origin -CreatedBefore $origin.AddDays(1) | 
Sort-Object -Property {[int]$_.id} |
Select-Object -First 5 -Property displayName,id,
        @{N='Points';E={$_.jive.level.points}},
        @{N='Published';E={([DateTime]$_.published).ToLocalTime()}} |
Format-Table -AutoSize</pre><p>Yup, still some of those first-day authors around.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5767" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-6.png" alt="" width="342" height="125" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-6.png 342w, https://www.lucd.info/wp-content/uploads/2017/12/sample-6-300x110.png 300w" sizes="auto, (max-width: 342px) 100vw, 342px" /></p>
<p>Just wonder what happened with ids 1 to 7 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>Finally we get to the core of the VMTN communities, the discussions. Again, quite simple to query.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

Get-VMTNContent -Keyword 'New-OSCustomizationSpec' |
select subject,status,resolved,replycount,viewcount,
    @{N='Author';E={$_.author.displayName}},
    @{N='Location';E={$_.parentPlace.name}},
    @{N='Published';E={([DateTime]$_.published).ToLocalTime()}},
    @{N='LastActivity';E={([DateTime]$_.lastActivityDate).ToLocalTime()}} |
Format-Table -AutoSize</pre><p>And this returns 100 objects, remember, the default <strong>MaxSamples</strong>.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5769" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-7.png" alt="" width="1993" height="609" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-7.png 1993w, https://www.lucd.info/wp-content/uploads/2017/12/sample-7-300x92.png 300w, https://www.lucd.info/wp-content/uploads/2017/12/sample-7-768x235.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/sample-7-1024x313.png 1024w, https://www.lucd.info/wp-content/uploads/2017/12/sample-7-720x220.png 720w" sizes="auto, (max-width: 1993px) 100vw, 1993px" /></p>
<p>These &#8220;content&#8221; objects are very rich, but I leave that to you and Get-Member to discover.</p>
<p>As we stated earlier, with the cmdlet we can also search the blog posts and documents. Let&#8217;s check if someone blogged about VSAN in the last month.</p><pre class="urvanov-syntax-highlighter-plain-tag">Initialize-VMTNRest

$now = Get-Date

Get-VMTNContent -Keyword 'VSAN' -ContentType post -SearchSubjectOnly -Start $now.AddMonths(-1) | 
select subject,status,resolved,replycount,viewcount,
    @{N='Author';E={$_.author.displayName}},
    @{N='Location';E={$_.parentPlace.name}},
    @{N='Published';E={([DateTime]$_.published).ToLocalTime()}},
    @{N='LastActivity';E={([DateTime]$_.lastActivityDate).ToLocalTime()}} |
Format-Table -AutoSize</pre><p>And there we go.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5770" src="https://www.lucd.info/wp-content/uploads/2017/12/sample-8.png" alt="" width="1076" height="90" srcset="https://www.lucd.info/wp-content/uploads/2017/12/sample-8.png 1076w, https://www.lucd.info/wp-content/uploads/2017/12/sample-8-300x25.png 300w, https://www.lucd.info/wp-content/uploads/2017/12/sample-8-768x64.png 768w, https://www.lucd.info/wp-content/uploads/2017/12/sample-8-1024x86.png 1024w, https://www.lucd.info/wp-content/uploads/2017/12/sample-8-720x60.png 720w" sizes="auto, (max-width: 1076px) 100vw, 1076px" /></p>
<p>The functionality these cmdlets bring you is of course also available via a Web Browser and the Search functionality on the VMTN website. But with these cmdlets you can now automate some of your searches. You could for example schedule a script to check weekly if something was published about VSAN. Mail the report to yourself, and you can immediately see if you need to fire up your browser.</p>
<p>The cmdlets are just another way to access the information available in VMware&#8217;s VMTN Community, and seen the richness of the returned objects, you can go very far in this. Some examples are the InfoGraphs I ended up with in the end for my VMworld session.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5771 size-large" src="https://www.lucd.info/wp-content/uploads/2017/12/slide11-1024x683.jpg" alt="" width="770" height="514" srcset="https://www.lucd.info/wp-content/uploads/2017/12/slide11-1024x683.jpg 1024w, https://www.lucd.info/wp-content/uploads/2017/12/slide11-300x200.jpg 300w, https://www.lucd.info/wp-content/uploads/2017/12/slide11-768x512.jpg 768w, https://www.lucd.info/wp-content/uploads/2017/12/slide11-720x480.jpg 720w" sizes="auto, (max-width: 770px) 100vw, 770px" /></p>
<p>and</p>
<p><img loading="lazy" decoding="async" class="alignnone size-large wp-image-5772" src="https://www.lucd.info/wp-content/uploads/2017/12/slide12-1024x683.jpg" alt="" width="770" height="514" srcset="https://www.lucd.info/wp-content/uploads/2017/12/slide12-1024x683.jpg 1024w, https://www.lucd.info/wp-content/uploads/2017/12/slide12-300x200.jpg 300w, https://www.lucd.info/wp-content/uploads/2017/12/slide12-768x512.jpg 768w, https://www.lucd.info/wp-content/uploads/2017/12/slide12-720x480.jpg 720w" sizes="auto, (max-width: 770px) 100vw, 770px" /></p>
<p>If you have suggestions to improve or expand the VMTNRest module, feel free to contact me.</p>
<p>Enjoy!</p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2017/12/31/search-vmtn-rest-api/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>vSphere Automation SDKs, PowerShell and you &#8211; Part 1</title>
		<link>https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/</link>
					<comments>https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/#comments</comments>
		
		<dc:creator><![CDATA[LucD]]></dc:creator>
		<pubDate>Fri, 10 Mar 2017 14:33:45 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[vSphere]]></category>
		<category><![CDATA[Open Source]]></category>
		<guid isPermaLink="false">http://www.lucd.info/?p=5581</guid>

					<description><![CDATA[Yesterday a blog post, named Integration with VMware vSphere using the new Open [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Yesterday a blog post, named <a href="https://blogs.vmware.com/opensource/2017/03/09/integration-vmware-vsphere-using-new-open-sourced-software-development-kits/" target="_blank">Integration with VMware vSphere using the new Open Sourced Software Development Kits</a>, was published. In my opinion an important milestone on VMware&#8217;s Open Source path ! The blog post announced the availability of the first two <a href="https://vmware.github.io/vsphere-automation-sdk/" target="_blank">Open Sourced SDKs</a> made available to the public on GitHub. One for <strong>REST</strong> and the other for <strong>Python</strong>.</p>
<p><a href="https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/restapi/" rel="attachment wp-att-5591"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5591" src="https://www.lucd.info/wp-content/uploads/2017/03/restapi.png" alt="" width="338" height="145" srcset="https://www.lucd.info/wp-content/uploads/2017/03/restapi.png 338w, https://www.lucd.info/wp-content/uploads/2017/03/restapi-300x129.png 300w" sizes="auto, (max-width: 338px) 100vw, 338px" /></a></p>
<p>When we hear REST API, we know it is relatively easy to consume these from a PowerShell script. So power up your labs and follow along on my first steps in my vSphere Audtomation SDK and PowerShell adventure.</p>
<p><span id="more-5581"></span></p>
<h2>Intro</h2>
<p>The REST API were officially announced during VMworld EMEA 2016 in Barcelona.</p>
<p>Recently <a href="https://twitter.com/kmruddy" target="_blank">Kyle Ruddy</a> published two blog posts on the VMware {code} blog that showed how to get started with both SDK.</p>
<ul>
<li><a href="https://blogs.vmware.com/code/2017/02/02/getting-started-vsphere-automation-sdk-rest/" target="_blank">Getting Started with the vSphere Automation SDK for REST</a></li>
<li><a href="https://blogs.vmware.com/code/2017/02/15/getting-started-vsphere-automation-sdk-rest-p2/" target="_blank">Getting Started with the vSphere Automation SDK for REST – Part 2</a></li>
</ul>
<p>Both should be in your reading list, since they are excellent introductions to the REST API usage, although not with our favourite tool <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p><span style="background-color: #ffff00;">Note</span> that what I show in this series is <span style="background-color: #ffff00;">not the only way</span> to consume the REST API. The <a href="https://www.vmware.com/support/developer/PowerCLI/PowerCLI65R1/html/Connect-CisServer.html" target="_blank">Connect-CisServer</a>, the <a href="https://www.vmware.com/support/developer/PowerCLI/PowerCLI65R1/html/Get-CisService.html" target="_blank">Get-CisService</a> and the <a href="https://www.vmware.com/support/developer/PowerCLI/PowerCLI65R1/html/Disconnect-CisServer.html" target="_blank">Disconnect-CisServer</a> cmdlets also give you access to the vSphere Automation SDK services. You can find great examples of working with those cmdlets in <a href="https://twitter.com/lamw" target="_blank">William Lam</a>&#8216;s series <a href="https://www.virtuallyghetto.com/?s=exploring+new+vcsa+VAMI+API+POWERCLI" target="_blank">Exploring new VCSA VAMI API w/PowerCLI</a>. Those should go into your reading list as well!</p>
<h2>Environment</h2>
<p>I do my exploration of the vSphere Automation SDK for REST in a vSphere 6.5 environment. My vCenter is a VCSA, and I&#8217;m using PowerShell 5.1.</p>
<p>Besides the PostMan tool, which is described in one of Kyle&#8217;s posts, I also find it handy to use <a href="https://en.wikipedia.org/wiki/Fiddler_(software)" target="_blank">Fiddler2</a> while coding and debugging. It acts as a proxy between my code and the VCSA.</p>
<p>When you&#8217;re working with the REST API you have to install the <a href="https://github.com/vmware/vsphere-automation-sdk-rest" target="_blank">REST API repository</a> on your system. They did a good job of producing this documentation.</p>
<p><a href="https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/doc/" rel="attachment wp-att-5597"><img loading="lazy" decoding="async" class="alignnone wp-image-5597 size-medium" src="https://www.lucd.info/wp-content/uploads/2017/03/doc-300x225.png" width="300" height="225" srcset="https://www.lucd.info/wp-content/uploads/2017/03/doc-300x225.png 300w, https://www.lucd.info/wp-content/uploads/2017/03/doc-768x577.png 768w, https://www.lucd.info/wp-content/uploads/2017/03/doc-1024x770.png 1024w, https://www.lucd.info/wp-content/uploads/2017/03/doc-720x541.png 720w, https://www.lucd.info/wp-content/uploads/2017/03/doc.png 1063w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p>
<p>The code for this series is available in my <a href="https://github.com/lucdekens/OpenSdk" target="_blank">OpenSdk</a> repository.</p>
<h2>Some Basics</h2>
<p>As I already demonstrated in my <a href="https://www.lucd.info/?s=ravello" target="_blank">Ravello</a> module, it is quite easy to consume REST API with PowerShell. With the <a href="https://msdn.microsoft.com/powershell/reference/5.1/microsoft.powershell.utility/Invoke-WebRequest" target="_blank">Invoke-WebRequest</a> cmdlet calling REST API becomes a breeze.</p>
<p>A few basics one needs to be aware of.</p>
<ul>
<li>The key function in the code is the <strong>Invoke-RestCall</strong> function. This function does the preparation of the parameters, the actual <a href="https://msdn.microsoft.com/powershell/reference/5.1/microsoft.powershell.utility/Invoke-WebRequest" target="_blank">Invoke-WebRequest </a>call and the handling of any errors resulting from the call. I&#8217;m also investigating of the use of <a href="https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.utility/invoke-restmethod" target="_blank">Invoke-RestMethod</a> would bring any advantages.</li>
<li>Separate methods will be wrapped in separate functions. For now I tried to use function names that resemble their PowerCLI counterparts. You&#8217;ll find Connect-rViServer, Get-rVMHost&#8230;</li>
<li>Authentication in my code, as well as in the SDK Samples, is done as <strong>Basic Authentication</strong>. Creating the Authorization string can be done with PowerShell and some .Net methods.</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">$Encoded = [System.Text.Encoding]::UTF8.GetBytes(($User,$Password -Join ':'))
$EncodedPassword = [System.Convert]::ToBase64String($Encoded)</pre><p></p>
<ul>
<li>As most REST API libraries, the vSphere Automation API also use a &#8220;Session&#8221; string for all calls after the authentication call. When using the Invoke-WebRequest cmdlet, there is no need to create a header with a session cookie. That is handled by the <strong>WebSession</strong> parameter. Note that Cookie header is in fact a Restricted Header, meaning you can just create it like any other regular header.</li>
<li>To store some basic information between different calls to the REST API, I use a Class object. That is easier to handle, and will open perspectives in future episodes of this series.</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">Class rOpenSdk
{
    [String]$Name
    [String]$User
    [String]$SessionSecret
    [String]$Auth
    [String]$Uri
}</pre><p></p>
<ul>
<li>I use the Verbose option a lot while writing code. So you&#8217;ll see my standard &#8220;verbosity lines&#8221; at the start of each function.</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">Write-Verbose -Message "$($MyInvocation.MyCommand.Name)"</pre><p>Write-Verbose -Message &#8220;<code>t$($PSCmdlet.ParameterSetName)" Write-Verbose -Message "</code>tCalled from $($stack = Get-PSCallStack; $stack[1].Command) at $($stack[1].Location)&#8221;</p>
<h2>Why?</h2>
<p>One might wonder why I would be looking at wrapping these REST API in PowerShell code! More so, since these API can be consumed via native PowerCLI cmdlets (see my earlier note). I did in fact the same before starting this series, and I came up with these (random order) arguments.</p>
<ul>
<li>Because we can! The fact that these REST API are open and public, make them an attractive alternative for all other available tools.</li>
<li>Open. The wrapper code and the REST API are Open, this in contrast to some of the binary products like PowerCLI. The wrapper code can be version controlled, and we can pull diffs between different versions</li>
<li>Appliance. We can avoid one extra dependency when installing an appliance.</li>
<li>Desired State Configuration (DSC). Same as the Appliance consideration, one less layer to take into account.</li>
<li>PowerShell. Although PostMan, Python, JavaScript&#8230; are most probably great tools, it pays to limit the number of tools one is using.</li>
<li>Uniformity. When PowerShell is the preferred tool of automation, it is an advantage to be able consume the REST API with the same tool. Note that if you have PowerCLI installed, you can use the CIS cmdlets (see my remark earlier)</li>
<li>Eco-system. To me it is more and more obvious that REST API are a preferred way to &#8220;talk&#8221; with many, if eventually not all, of VMware&#8217;s products.</li>
<li>Simplicity. The HTTP driven REST communication is simple to manage and control.</li>
</ul>
<h2>Sample Usage</h2>
<p>In this first part there are only two functions available, a function to connect, and another to get the list of ESXi nodes in the environment. These two functions allowed me to tackle a number of the basic issues, so future functions should come faster <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>These two functions do, currently, not allow for a lot of variation. Open a session, get the list. That&#8217;s it.</p><pre class="urvanov-syntax-highlighter-plain-tag">$user = 'administrator@vsphere.local'
$pswd = 'HomeLab2017!'
$vcsa = 'vcsa.local.lab'

Connect-rViServer -User $user -Password $pswd -Server $vcsa -Verbose

Get-rVMHost</pre><p>In Fiddler2 the connection call is seen as follows. Note the User-Agent is PowerShell 5.1.</p>
<p>Notice how the encoded Authorization header is passed to the REST API.</p>
<p><a href="https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/sample1/" rel="attachment wp-att-5586"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5586" src="https://www.lucd.info/wp-content/uploads/2017/03/sample1.png" alt="" width="672" height="98" srcset="https://www.lucd.info/wp-content/uploads/2017/03/sample1.png 672w, https://www.lucd.info/wp-content/uploads/2017/03/sample1-300x44.png 300w" sizes="auto, (max-width: 672px) 100vw, 672px" /></a></p>
<p>The REST API returns the session ID. Instead of using a <a href="https://msdn.microsoft.com/en-us/library/system.net.webclient.headers(v=vs.110).aspx" target="_blank">restricted header</a>, this will be passed along through the <strong>WebSession</strong> parameter on the Invoke-WebRequest cmdlet..</p>
<p><a href="https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/sample2/" rel="attachment wp-att-5587"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5587" src="https://www.lucd.info/wp-content/uploads/2017/03/sample2.png" alt="" width="567" height="66" srcset="https://www.lucd.info/wp-content/uploads/2017/03/sample2.png 567w, https://www.lucd.info/wp-content/uploads/2017/03/sample2-300x35.png 300w" sizes="auto, (max-width: 567px) 100vw, 567px" /></a></p>
<p>In the subsequent calls to other REST API, the session id will passed.</p>
<p><a href="https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/sample3/" rel="attachment wp-att-5588"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5588" src="https://www.lucd.info/wp-content/uploads/2017/03/sample3.png" alt="" width="666" height="102" srcset="https://www.lucd.info/wp-content/uploads/2017/03/sample3.png 666w, https://www.lucd.info/wp-content/uploads/2017/03/sample3-300x46.png 300w" sizes="auto, (max-width: 666px) 100vw, 666px" /></a></p>
<p>And of course the result of our REST API call.</p>
<p><a href="https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/sample4/" rel="attachment wp-att-5589"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5589" src="https://www.lucd.info/wp-content/uploads/2017/03/sample4.png" alt="" width="326" height="240" srcset="https://www.lucd.info/wp-content/uploads/2017/03/sample4.png 326w, https://www.lucd.info/wp-content/uploads/2017/03/sample4-300x221.png 300w" sizes="auto, (max-width: 326px) 100vw, 326px" /></a></p>
<p>As we can notice, the result is returned as a JSON object. In one of the next posts in this series, we will discuss how to convert this to a PowerShell object.</p>
<p>Enjoy!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.lucd.info/2017/03/10/vsphere-automation-sdks-powershell-part-1/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
