I was reading through the CIS ESXi 6.7 hardening benchmark and trying to turn the process of checking and, if necessary, making configuration changes to align with the benchmark a fully automated script. The last thing I want is for my global deployment team to have to hand jam something if that something can be fully scripted. I ran into challenge when I got to 3.1 – Ensure a centralized location is configured to collect ESXi host core dumps. The easiest way to manage this is to have the VCSA run the dump collector service (netdumper). Unfortunately there’s no PowerCLI cmdlet to manage and monitor vCSA services and the netdumper service is disabled by default.
Fortunately there are RESTful API’s for interacting with vCenter services and so I began my journey of discovery on using Invoke-RestMethod in PowerShell.
I’ll spare the long, drawn out version of how I, through trial and error figured out how to authenticate a session and use that session to get the netdumper service so I can evaluate it’s status and force it to startup and simply drop a couple of functions here. I will make a couple of quick points:
- The hardest thing to get write on any of these requests was the formatting of the header and body parameters
- For headers, all calls except the the session initiation require ” ‘vmware-api-session-id’ = $Session.ID” in the hash table
- While headers are a hash table, bodies need to be sent as a json. Building the json as a hashtable then piping to ConvertTo-Json works nicely for this as you’ll see below.
- I’m not usually a fan of variable splatting as I’m lazy and it breaks intellisense in vsCode, but for Invoke-RestMethod I will make an exception
- VMware’s API documentation is pretty good once you figure out the header/body formatting. Nobody seems to want to write a post about the fundamentals of REST API calls in PowerShell only examples that are usually incomplete.
- ConvertTo-Json is your friend
First we have New-RestViSession(). This take the vCenter’s FQDN (or IP address) SSO administrator or appliance root user and secure string password and return an object with the session ID and input parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
function New-RestViSession { <# .SYNOPSIS Create a new RESTapi session with vCenter .DESCRIPTION When using REST API calls into vCenter, a unique authenticated HTTPS session is required. This function will build the header based on the inputs and POST the new session request. .EXAMPLE PS C:\> New-RestViSession -User Administrator@vsphere.local -Password VMware1! -Server vcenter.vsphere.local #> [CmdletBinding()] param ( # Server to authenticate against [Parameter(mandatory,position=0)] [string] $Server, # Username (user@fqdn) [Parameter(mandatory,position=1)] [ValidateScript({ ($_.Split("@"))[0] -match "[a-zA-Z0-9]" -and ` ($_.Split("@"))[1] -match "^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$" ` -and ($_.Split("@")).Length -eq 2 -or ($_.Split("@")).Length -eq 1 })] [string] $Username, # Password (as secure string) [Parameter(mandatory,position=2)] [securestring] $Password ) # Encode the username and plaintext password into a base 64 sting to pass to the session request $EncodedPassword = [System.Convert]::ToBase64String(([System.Text.Encoding]::UTF8.GetBytes(($Username, (ConvertTo-PlainText -SecureString $Password) -Join ':')))) $RestSplat = @{ Method = "POST" Uri = "https://$Server/rest/com/vmware/cis/session" Headers = @{Authorization = "Basic $EncodedPassword"} SkipCertificateCheck = $true ContentType = "application/json" } $Result = @{ ID = (Invoke-RestMethod @RestSplat).value Server = $Server Username = $Username Password = $Password } $Result } |
Having a session object allows us to call additional functions to get the services from the vCSA, set their properties (mostly startup mode) and start/stop them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
function Get-RestvCsaServices { <# .SYNOPSIS Retreives service status information from a vCenter appliance .DESCRIPTION This function makes a REST API call to vCenter and either returns the status of all services or returns the status of the specified service .EXAMPLE PS C:\> <example usage> Explanation of what the example does #> [CmdletBinding()] param( # Session object [parameter(mandatory,position=0)] [validatescript({$_.id -match "[a-fA-F0-9]{32}"})] [pscustomobject] $Session, [parameter(mandatory=$false,position=1)] [ValidateSet("updatemgr","vcha","rbd","statsmonitor","vmcam","pschealth","vsphere-ui","vmware-vpostgres","certificatemanagement","vmware-postgres-archiver","vpxd-svcs","imagebuilder","rhttpproxy","cis-license","sps","analytics","vmonapi","vsan-health","cm","topologysvc","netdumper","vsan-dps","mbcs","applmgmt","sca","eam","vpxd","vsm","content-library","vsphere-client","vapi-endpoint","perfcharts")] [string] $Service ) $RestSplat = @{ Method = "GET" SkipCertificateCheck = $true ContentType = "application/json" Headers = @{ 'vmware-api-session-id' = $Session.ID } Body = '' } if ($PSBoundParameters.ContainsKey("Service")) { $RestSplat.Uri = "https://$($Session.Server)/rest/vcenter/services/$Service" } else { $RestSplat.Uri = "https://$($Session.Server)/rest/vcenter/services/" } $Result = (Invoke-RestMethod @RestSplat).Value $Result } function Set-RestvCsaServices { <# .DESCRIPTION Change the settings of the specified vCSA service #> [CmdletBinding()] param( # Session object [parameter(mandatory,position=0)] [validatescript({$_.id -match "[a-fA-F0-9]{32}"})] [pscustomobject] $Session, # Service name [parameter(mandatory,position=1)] [ValidateSet("updatemgr","vcha","rbd","statsmonitor","vmcam","pschealth","vsphere-ui","vmware-vpostgres","certificatemanagement","vmware-postgres-archiver","vpxd-svcs","imagebuilder","rhttpproxy","cis-license","sps","analytics","vmonapi","vsan-health","cm","topologysvc","netdumper","vsan-dps","mbcs","applmgmt","sca","eam","vpxd","vsm","content-library","vsphere-client","vapi-endpoint","perfcharts")] [string] $Service, # Service startup type [parameter(Mandatory,position=2)] [validateset("MANUAL","AUTOMATIC","DISABLED")] [string] $StartupType ) $RestSplat = @{ Method = "PATCH" SkipCertificateCheck = $true ContentType = "application/json" Headers = @{ 'vmware-api-session-id' = $Session.ID } Body = @{spec=@{startup_type="$StartupType"}} | ConvertTo-Json Uri = "https://$($Session.Server)/rest/vcenter/services/$Service" } $Result = Invoke-RestMethod @RestSplat $Result } function Start-RestvCsaServices { <# .DESCRIPTION Start the specified vCSA service if it is not running #> [CmdletBinding()] param( # Session object [parameter(mandatory,position=0)] [validatescript({$_.id -match "[a-fA-F0-9]{32}"})] [pscustomobject] $Session, [parameter(mandatory=$false,position=1)] [ValidateSet("updatemgr","vcha","rbd","statsmonitor","vmcam","pschealth","vsphere-ui","vmware-vpostgres","certificatemanagement","vmware-postgres-archiver","vpxd-svcs","imagebuilder","rhttpproxy","cis-license","sps","analytics","vmonapi","vsan-health","cm","topologysvc","netdumper","vsan-dps","mbcs","applmgmt","sca","eam","vpxd","vsm","content-library","vsphere-client","vapi-endpoint","perfcharts")] [string] $Service ) $RestSplat = @{ Method = "POST" SkipCertificateCheck = $true ContentType = "application/json" Headers = @{ 'vmware-api-session-id' = $Session.ID } Body = '' Uri = "https://$($Session.Server)/rest/vcenter/services/$Service/start" } $Result = Invoke-RestMethod @RestSplat $Result } function Stop-RestvCsaServices { <# .DESCRIPTION Start the specified vCSA service if it is not running #> [CmdletBinding()] param( # Session object [parameter(mandatory,position=0)] [validatescript({$_.id -match "[a-fA-F0-9]{32}"})] [pscustomobject] $Session, [parameter(mandatory=$false,position=1)] [ValidateSet("updatemgr","vcha","rbd","statsmonitor","vmcam","pschealth","vsphere-ui","vmware-vpostgres","certificatemanagement","vmware-postgres-archiver","vpxd-svcs","imagebuilder","rhttpproxy","cis-license","sps","analytics","vmonapi","vsan-health","cm","topologysvc","netdumper","vsan-dps","mbcs","applmgmt","sca","eam","vpxd","vsm","content-library","vsphere-client","vapi-endpoint","perfcharts")] [string] $Service ) $RestSplat = @{ Method = "POST" SkipCertificateCheck = $true ContentType = "application/json" Headers = @{ 'vmware-api-session-id' = $Session.ID } Body = '' Uri = "https://$($Session.Server)/rest/vcenter/services/$Service/stop" } $Result = Invoke-RestMethod @RestSplat $Result } function Restart-RestvCsaServices { <# .DESCRIPTION Start the specified vCSA service if it is not running #> [CmdletBinding()] param( # Session object [parameter(mandatory,position=0)] [validatescript({$_.id -match "[a-fA-F0-9]{32}"})] [pscustomobject] $Session, [parameter(mandatory=$false,position=1)] [ValidateSet("updatemgr","vcha","rbd","statsmonitor","vmcam","pschealth","vsphere-ui","vmware-vpostgres","certificatemanagement","vmware-postgres-archiver","vpxd-svcs","imagebuilder","rhttpproxy","cis-license","sps","analytics","vmonapi","vsan-health","cm","topologysvc","netdumper","vsan-dps","mbcs","applmgmt","sca","eam","vpxd","vsm","content-library","vsphere-client","vapi-endpoint","perfcharts")] [string] $Service ) $RestSplat = @{ Method = "POST" SkipCertificateCheck = $true ContentType = "application/json" Headers = @{ 'vmware-api-session-id' = $Session.ID } Body = '' Uri = "https://$($Session.Server)/rest/vcenter/services/$Service/restart" } $Result = Invoke-RestMethod @RestSplat $Result } |
Now will a full set of cmdlets I can call into vCenter and get the status of netdumper, which I expect to be stopped and disabled then enable it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
try { $Session = New-RestViSession -Server $Server.Name -Username $Username -Password $Password } catch { Write-Host "Failed to estabilish connection to RESTapi" } $NetDumperService = Get-RestvCsaServices -Session $Session -Service netdumper if ($NetDumperService.startup_type -ne "AUTOMATIC") { # enable and start the service. try { Set-RestvCsaServices -Session $Session -Service netdumper } catch { Write-Host "Failed to set netdumper to automatically start" } $NetDumperService = Get-RestvCsaServices -Session $Session -Service netdumper } if ($NetDumperService.state -ne "STARTED") { # start the service try { Start-RestvCsaServices -Service netdumper -Session $Session } catch { Write-Host "Failed to start netdumper" } $NetDumperService = Get-RestvCsaServices -Session $Session -Service netdumper } |
I’ll finish by saying that I’m still reletively new to use RESTful methods to do anything especially in PowerShell, as such I’ll accept that there are probably better ways to pull this off, but in translating these REST methods into PowerShell cmdlets I’m trying to build a way to translate my teams PowerShell expertise into a broader set of APIs that can both do more in vCenter and eventually do more with the hardware it’s running on via Redfish.