Adding a simple Menu to a PowerShell script

When sharing scripts that can do multiple operations with other teams I find that adding a simple operator menu that presents a list of choices makes it easier for folks to use the scripts. Here is the simple menu that i add to a lot of my scripts and a quick explanation of how it works.

The menu is created using a PowerShell Function called Menu (This can be anything really but i like to keep it simple!). Within the function we start with a Clear-Host – this will ensure that the menu starts on a clean PowerShell session screen.

In between lines 6 and 17 below you can edit any of the text in the quotes to change the menus item or heading text

Within each menu item you need to callout the PowerShell functions that you want to run as part of the menu option. You can list them one per line as in my example below or on one line with a ; between them (e.g. CreateVMSnapshot; anyKey)

To have the menu launch when the PowerShell script is executed just put the name of the menu function at the bottom of the script outside of any function, see line 53

For more information on the anyKey function go here

EDIT: Modified the exit option to be Q for Quit to keep all menus consistent. Thanks to SiliconBrian for the suggestion!


Function Menu 
{
    Clear-Host         
    Do
    {
        Clear-Host                                                                        
        Write-Host -Object 'Please choose an option'
        Write-Host     -Object '**********************'	
        Write-Host -Object 'Snapshot VM Options' -ForegroundColor Yellow
        Write-Host     -Object '**********************'
        Write-Host -Object '1.  Snapshot VMs '
	Write-Host -Object ''
        Write-Host -Object '2.  Revert to Last Snapshot '
	Write-Host -Object ''
	Write-Host -Object '3.  Revert To Specific Snapshot '
	Write-Host -Object ''
        Write-Host -Object 'Q.  Quit'
        Write-Host -Object $errout
        $Menu = Read-Host -Prompt '(0-3 or Q to Quit)'

        switch ($Menu) 
        {
           1 
            {
                CreateVMSnapshot 			
                anyKey
            }
            2 
            {
                RevertLastVMSnapshot
                anyKey
            }
			3 
            {
                RevertSpecificVMSnapshot
                anyKey
            }
            Q 
            {
                Exit
			}	
            default 
            {
                $errout = 'Invalid option please try again........Try 0-3 or Q only'
            }

        }
    }
    until ($Menu -eq 'q')
}   

# Launch The Menu
Menu

Enable LDAP Auth for RP4VMs

To avoid using local admin accounts it is recommended to integrate RecoverPoint for Virtual Machines (RP4VMs) with AD/LDAP. RP4VMs supports Role Based Access Control (RBAC) where you can create custom roles and assign those custom roles to an AD user or group. To do this you first need to setup LDAP integration. For simplicity this post uses default passwords for the local RP4VM users. Passwords for all local RP users should be changed once the system has been deployed for security reasons.

  1. Connect over SSH to a vRPA in the cluster
  2. Enter username security-admin
  3. Enter the password for the above user (default password is security-admin)
  4. To configure LDAP integration enter config_ldap
  5. To enable Active Directory Support select option 1 and press Enter
  6. Select either LDAP or LDAPs
  7. Enter the primary LDAP server (in my example i use the AD domain as this will allow round robin across all DCs)
  8. Enter the primary LDAP server port
  9. Enter the secondary LDAP server (in my example i left this blank)
  10. Enter the secondary LDAP server port
  11. Enter the base distinguished Name
  12. Enter the search base distinguished name
  13. Enter the bind distinguished name
  14. Enter the password for above AD bind account
  15. Enter a search time limit in seconds. Default is 30. Increase this in larger AD environments

Here is a screenshot of the entire sequence

Once LDAP is configured you then need to create a custom role to assign to an AD user/group.

  1. Connect over SSH to a vRPA in the cluster
  2. Enter username security-admin
  3. Enter the password for the above user (default password is security-admin)
  4. To configure a custom role enter add_role
  5. Enter a name for the new role (e.g. RP4VM_Admins)
  6. Select the desired permissions from the available role permissions. Separate selections with a space
  7. Press Enter when done

Here is a screenshot of the process

Once the role is created you can then assign it to your AD user/group

  1. Connect over SSH to a vRPA in the cluster
  2. Enter username security-admin
  3. Enter the password for the above user (default password is security-admin)
  4. To add a user or group enter add_user (the same function is used to add users & groups)
  5. Select option to add either local user, LDAP user or LDAP group
  6. Enter the username/groupname (sAMAccountName format e.g. the group i am using in this example is called RP4VM_Admins)
  7. Enter the role created previously (e.g. RP4VM_Admins)
  8. For enter group names i tried entering the group name in multiple formats, none of which were accepted so i just hit enter without an entry and it completed
    1. The RP4VM documentation is not clear on what the Group Name field is for but I tested to ensure only users from the desired group have access to the vRPA.

Here is a screenshot of the process

Error restoring SRM placeholder VM

I’ve been doing some lab work this week staging a vSphere 6.0U1b with SRM 6.0 environment for some upgrade scenario testing and i hit an issue with SRM 6.0 that i had not seen before. When trying to restore the SRM placeholder VM for a protected VM I was getting the following error

No hosts with hardware version ‘7’ and datastore(s) “NFS02” which are powered on and not in maintenance mode are available

.srm-placeholder

Seemed like a pretty odd error given that my target host is 6.0 and it has the NFS02 datastore mounted. I checked all the obvious to ensure there were no host issues and then went on the KB hunt. Tried the solution outlined here to no avail https://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2079084

Also tried this, again no joy. http://pubs.vmware.com/srm-55/index.jsp?topic=%2Fcom.vmware.srm.admin.doc%2FGUID-FE6A85EC-B44E-415A-9C5F-1E17BC846119.html

As a last ditch effort i tried rebooting the target ESXi host and that fixed the issue and I was then able to restore the placeholder VM and continue testing. Not sure on the root cause. This is a fully nested environment, using vSphere Replication & a VNX File appliance so it may just be environmental. Will update this post if i figure it out!

Onwards with testing!

Setup multiple vRA tenants using powershell and the vRA7 REST API

Following on from my post on Creating a local user in vRA7 using the REST API i wanted to try and script the entire process of creating multiple vRealize Automation 7 tenants as in our lab as we often need to spin up multiple tenants for testing or development purposes.

Some assumptions:

  • Each tenant has the same prefix of “dev-” and is appended with a 3 digit number starting at “001”
  • Each tenant gets the same local user created with matching credentials
  • Each tenant gets the same AD directory added
  • Each tenant gets the same AD groups added

This script will do the following:

  • Log into the default tenant
  • Create a new tenant
  • Create a local user for the tenant
  • Add the local user as a tenant & IaaS admin
  • Log into the new tenant as the local user
  • Setup identity store directories
  • Log back into the default tenant
  • Edit the new tenant
  • Add domain users/groups as tenant & IaaS admins
  • Log into the new tenant as a tenant and IaaS admin and start configuring the tenant

So as to avoid the requirement to edit the powershell script directly i put all configuration variables in an external .cfg file. This file needs to be placed in the same directory as the powershell script.

Firstly here is the config file contents. Edit each variable to match your environment. Modify the numberOfTenants variable to set the number of tenants you want to create. The example below will create 20 tenants.

[vRA FQDN]
VRA=vra-vip.domain.local

[vRA Credentials to acquire authentication token]
vRAUsername=administrator@vsphere.local
vRAPassword=Password123!
vRADefaultTenant=vsphere.local

[Create tenant details]
numberOfTenants=2
tenantIDPrefix=dev-
tenantURLPrefix=dev-
tenantNamePrefix=dev-
tenantDescription=DevelopmentTenant
tenantemailAddress=admin@vsphere.local

[Local Admin User Details]
firstName=vRA
lastName=Admin
emailAddress=vraadmin@vsphere.local
description=vRAAdmin
locked=false
disabled=false
password=Password123!
domain=vsphere.local
userName=vraadmin
name=vraadmin

[Tenant Directory Details]
adDomain=domain.local
adDomainalias=Domain
type=AD
adUserNameDn=cn=adbind_vra,OU=EHC,DC=domain,DC=local
adBindPassword=Password123!
adURL=ldap://domain.local:389
adGroupBaseSearchDn=ou=EHC,DC=domain,DC=local
adUserBaseSearchDn=ou=EHC,DC=domain,DC=local

[AD Domain Groups to add as Tenant & IaaS Admins]
tenantAdmins=EHC_Tenant_Admins@domain.local
tenantRoleID=CSP_TENANT_ADMIN
iaasAdmins=EHC_IaaS_Admins@domain.local
iaasRoleID=COM_VMWARE_IAAS_IAAS_ADMINISTRATOR

And here is the script to create the tenants. It is broken up into multiple functions

# Script to create vRA7 Tenants in bulk
# Ensure you update the associated cfg file
# with the details of your vRA environment
# and details of the tenants you wish to create
# Created by Brian O'Connell
# Version 1.0.0

# Import configuration variables from external cfg file
Get-Content createvRATenants.cfg | Foreach-Object{
if ($_.length -gt 0) {
 $var = $_ -Split '=',2
 New-Variable -Name $var[0] -Value $var[1]
 }
 } 

Function getvRAAuthToken {
# Construct credentials from config file
$credentials=@{username=$vRAUsername;password=$vRAPassword;tenant=$vRADefaultTenant}
############# Get Auth token ###############
$headers=@{
 "Accept"="application/json"
}
$Global:token = Invoke-RestMethod -Uri "https://$($VRA)/identity/api/tokens" -Method Post -Headers $headers -ContentType application/json -Body (ConvertTo-Json $credentials) | Select -ExpandProperty id
Write-Host "vRA Authentication Token Acquired" -ForegroundColor Green
 } 

Function createvRATenant {
 # ############ Create Tenant ###############
$headers = @{"Accept" = "application/json"}
$headers.Add("Authorization", "Bearer $token")

#Create the Tenant
for ($firstTenantNumber=1; $firstTenantNumber -le $numberOfTenants; $firstTenantNumber++)
{
 New-Variable -Name "var$firstTenantNumber" -Value $firstTenantNumber
 $tenantNumber = $firstTenantNumber.ToString("000")
$tenantid = -join ($tenantIDPrefix,$tenantNumber)
$tenantURL = -join ($tenantURLPrefix,$tenantNumber)
$tenantName = -join ($tenantNamePrefix,$tenantNumber)
$tenantBody= @"
{
 "@type": "Tenant",
 "id": "$tenantid",
 "urlName": "$tenantURL",
 "name": "$tenantName",
 "description": "$tenantDescription",
 "contactEmail": "$tenantemailAddress"
}
"@ 

$createTenant = Invoke-RestMethod -Method PUT -URI "https://$($VRA)/identity/api/tenants/$($tenantID)" -headers $headers -ContentType application/json -body $tenantBody
Write-Host "Tenant $($tenantName) created successfully" -ForegroundColor Green
}
 }

Function createvRALocalAdminUser {
 ############# Create Local Admin User ###############

$headers = @{"Accept" = "application/json"}
$headers.Add("Authorization", "Bearer $token")
$userBody= @"
{ "@type": "User",
 "firstName": "$firstName",
 "lastName": "$lastName",
 "emailAddress": "$emailAddress",
 "description": "$description",
 "locked": false,
 "disabled": false,
 "password": "$password",
 "domain": "$domain",
 "userName": "$userName",
 "principalId": {
 "domain": "$domain",
 "name": "$name"
 }
}
"@

for ($firstTenantNumber=1; $firstTenantNumber -le $numberOfTenants; $firstTenantNumber++)
{
 New-Variable -Name "var$firstTenantNumber" -Value $firstTenantNumber
 $tenantNumber = $firstTenantNumber.ToString("000")
 $tenantid = -join ($tenantIDPrefix,$tenantNumber)
#Create the user
$createUser = Invoke-RestMethod -Method Post -URI "https://$($VRA)/identity/api/tenants/$($tenantID)/principals" -headers $headers -ContentType "application/json" -body $userBody
Write-Host "Local Admin User for tenant $($tenantid) created successfully" -ForegroundColor Green
} 

 }

Function updatevRALocalAdminUserRoles {
 ############# Add Local Admin User to Tenant & IaaS Admin groups ###############

$headers = @{"Accept" = "application/json"}
$headers.Add("Authorization", "Bearer $token")
$principal = "vraadmin@vsphere.local"
$roleIDs = @("CSP_TENANT_ADMIN","COM_VMWARE_IAAS_IAAS_ADMINISTRATOR")

for ($firstTenantNumber=1; $firstTenantNumber -le $numberOfTenants; $firstTenantNumber++)
{
 New-Variable -Name "var$firstTenantNumber" -Value $firstTenantNumber
 $tenantNumber = $firstTenantNumber.ToString("000")
 $tenantid = -join ($tenantIDPrefix,$tenantNumber)
#Add the user to tenant & IaaS admins
foreach ($roleID in $roleIDs) {
$makeUserAdmin = Invoke-RestMethod -Method PUT -URI "https://$($VRA)/identity/api/authorization/tenants/$($tenantID)/principals/$($principal)/roles/$($roleID)" -headers $headers -body "{}"
}
Write-Host "Local Admin User Added to Tenant & IaaS Admins for tenant $($tenantid) " -ForegroundColor Green
 }
 }

Function createvRATenantDirectory {
 ############# Add AD Tenant directory ###############
$headers = @{"Accept" = "application/json"}
$headers.Add("Authorization", "Bearer $token")

$directoryBody= @"
{"@type": "IdentityStore",
"domain": "$adDomain",
"name": "$adDomain",
"alias": "$adDomainalias",
"type": "$type",
"userNameDn": "$adUserNameDn",
"password": "$adBindPassword",
"url": "$adURL",
"groupBaseSearchDn": "$adGroupBaseSearchDn",
"userBaseSearchDn": "$adUserBaseSearchDn"
}
"@
for ($firstTenantNumber=1; $firstTenantNumber -le $numberOfTenants; $firstTenantNumber++)
{
 New-Variable -Name "var$firstTenantNumber" -Value $firstTenantNumber
 $tenantNumber = $firstTenantNumber.ToString("000")
 $tenantid = -join ($tenantIDPrefix,$tenantNumber)
#Create the directory
$createDirectory = Invoke-RestMethod -Method Post -URI "https://$($VRA)/identity/api/tenants/$($tenantID)/directories" -headers $headers -ContentType "application/json" -body $directoryBody
Write-Host "Tenant Directory Created for tenant $($tenantid) " -ForegroundColor Green
}

 }

Function addDomainGroupstovRAAdmins {
############## Add AD Domain Groups to vRA Tenant & IaaS Admin groups ###############

$headers = @{"Accept" = "application/json"}
$headers.Add("Authorization", "Bearer $token")

#Add the user to tenant & IaaS admins
for ($firstTenantNumber=1; $firstTenantNumber -le $numberOfTenants; $firstTenantNumber++)
{
 New-Variable -Name "var$firstTenantNumber" -Value $firstTenantNumber
 $tenantNumber = $firstTenantNumber.ToString("000")
 $tenantid = -join ($tenantIDPrefix,$tenantNumber)
$addTenantAdmins = Invoke-RestMethod -Method PUT -URI "https://$($VRA)/identity/api/authorization/tenants/$($tenantID)/principals/$($tenantAdmins)/roles/$($tenantRoleID)" -headers $headers -body "{}"

$addIaaSAdmins = Invoke-RestMethod -Method PUT -URI "https://$($VRA)/identity/api/authorization/tenants/$($tenantID)/principals/$($iaasAdmins)/roles/$($iaasRoleID)" -headers $headers -body "{}"
Write-Host "Domain groups added to as tenant & IaaS admins for tenant $($tenantid) " -ForegroundColor Green
}
 }

# Call All functions to setup tenants
getvRAAuthToken; createvRATenant; createvRALocalAdminUser; updatevRALocalAdminUserRoles; createvRATenantDirectory; addDomainGroupstovRAAdmins 

Setting up a VMware Appliance update repo

When upgrading VMware appliances using the vami configuration interface the update that is available from VMware does not always match the version you want. mounting an ISO to the VM is not always possible (vCloud Director hosted VMs are an example). Follow this procedure to create a temporary repo to make the correct upgrade bundles available during appliance upgrade
In this example the vRA appliance is used to host the repo. Any VMware appliance can be used for this purpose.
1. SCP the upgrade ISO to /tmp on the vRA appliance
2. SSH to the vRA appliance and navigate to the tmp directory

 cd /tmp

3. Create a new directory called repo

Mkdir repo
 4. Type the following to mount the upgrade ISO to the new repo directory
mount -o loop "iso name" repo
 5. Change directory into repo/update
 cd repo/update
 6. Start the python SimpleHTTPServer
 python -m SimpleHTTPServer
 7. Return to the VAMI configuration interface of the appliance that you are upgrading and browse to the Update tab
 8. Click Settings
 9. Change the Update Repository option to Use Specified Repository
 10. For Repository URL enter http://vRA-FQDN:8000
 Note: Enter the FQDN of the appliance you are using. This may not be the vRA appliance
 11. Click Save Settings
 12. Click Status and click Check Updates
 13. The correct version that you copied to the temporary repo should be available to install

Distributed vRA validation script

From time to time a distributed vRA deployment can have issues…here is a quick script to verify and validate the important components are up and functioning…without the need to log into multiple components. Here is a diagram of my distributed vRA setup

Distributed vRA v4

And here is the script! It has multiple functions to do the following

  • Basic ping tests to each component
  • Get the status of all vRA component services
  • Test the Web & manager server URLs

# Script to check the status of each component of a distributed vRA deployment
# Modify the hostnames to match your environment
$vRAAppliance01FQDN = "vra01.domain.local"
$vRAAppliance02FQDN = "vra02.domain.local"
$vRAWeb01FQDN = "web01.domain.local"
$vRAWeb02FQDN = "web02.domain.local"
$vRAManager01FQDN = "manager01.domain.local"
$vRAManager02FQDN = "manager02.domain.local"
$vRADEM01FQDN = "demw01.domain.local"
$vRADEM02FQDN = "demw02.domain.local"
$vRAAgent01FQDN = "agent01.domain.local"
$vRAAgent02FQDN = "agent02.domain.local"
$vRAComponentServiceURL = "https://vra-vip.domain.local/component-registry/services/status/current"
$webVIPURL = "https://web-vip.domain.local/WAPI"
$managerVIPURL = "https://manager-vip.domain.local/VMPS2"



### DO NOT MODIFY ANYTHING BELOW THIS LINE ###



Function pingHosts {
@"
===============================================================================
Performing basic ping test to each defined component
===============================================================================
"@
$vms = @($vRAAppliance01FQDN, $vRAAppliance02FQDN, $vRAWeb01FQDN, $vRAWeb02FQDN, $vRAManager01FQDN, $vRAManager02FQDN, $vRADEM01FQDN, $vRADEM02FQDN, $vRAAgent01FQDN, $vRAAgent02FQDN)
$collection = $()
foreach ($vm in $vms)
{
 $status = @{ "ServerName" = $vm; "TimeStamp" = (Get-Date -f s) }
 if (Test-Connection $vm -Count 1 -ea 0 -Quiet)
 { 
 $status["Ping Results"] = "Up"
 } 
 else 
 { 
 $status["Ping Results"] = "Down" 
 }
 New-Object -TypeName PSObject -Property $status -OutVariable serverStatus
 $collection += $serverStatus

}
 }
 
Function testvRAServices {
@"
===============================================================================
Getting Status of all vRA component services
===============================================================================
"@
Write-Host "Checking status of vRA Component Services" -ForegroundColor Yellow
# Request Service Information from $vRAComponentServiceURL
$vRAURL = Invoke-WebRequest $vRAComponentServiceURL
# Convert the Json response
$json = ConvertFrom-Json -InputObject $vRAURL.content
# Get Service name & status
$serviceInfo = $json.content
# Loop through each service
foreach ($service in $serviceInfo) {
# Get the Service Name
$serviceName = $service.serviceName
# Get the Service status
$serviceStatus = $service.serviceStatus.serviceInitializationStatus
# If Service Status is blank report it as BLANK POSSIBLY STOPPED
 if (!$serviceStatus) {
 $serviceStatus = "BLANK - POSSIBLY STOPPED?"
 }
# If Service Status is FAILED print to screen in red 
if ($serviceStatus -eq "FAILED") {
 write-host "$serviceName $serviceStatus" -ForeGroundColor Red
 }
# Otherwise print to screen as normal (Remove this if you only want to report failed services) 
 else {
 Write-Host "$serviceName $serviceStatus"}
 }

}


Function testWebVIP {
@"
===============================================================================
Checking status of vRA Web API URL
===============================================================================
"@
Write-Host "Testing IaaS Web Service VIP URL $webVIPURL" -ForegroundColor Yellow
# Create Web Request
$HTTP_Request = [System.Net.WebRequest]::Create($webVIPURL)

# Get a response
$HTTP_Response = $HTTP_Request.GetResponse()

# Get the HTTP code
$HTTP_Status = [int]$HTTP_Response.StatusCode

If ($HTTP_Status -eq 200) { 
 Write-Host "IaaS Web Service is OK!" -ForegroundColor Green
 # Close HTTP request
$HTTP_Response.Close()
}
Else {
 Write-Host "IaaS Web Service is not responding. Restart IIS on Web01. If that does not resolve then Reboot Web01" -ForegroundColor Red
}
 }
 
Function testMgrVIP {
@"
===============================================================================
Checking status of vRA Manager API URL
===============================================================================
"@
Write-Host "Testing IaaS Manager Service VIP URL $managerVIPURL" -ForegroundColor Yellow
# Create Web Request
$HTTP_Request = [System.Net.WebRequest]::Create($managerVIPURL)

# Get a response
$HTTP_Response = $HTTP_Request.GetResponse()

# Get the HTTP code
$HTTP_Status = [int]$HTTP_Response.StatusCode

If ($HTTP_Status -eq 200) { 
 Write-Host "IaaS Manager Service is OK!" -ForegroundColor Green
 # Close HTTP request
$HTTP_Response.Close()
}
Else {
 Write-Host "IaaS Manager Service is not responding. Ensure all vRA services are running on manager01. If that does not resolve then Reboot manager01" -ForegroundColor Red
}
 } 
 

 pingHosts; testvRAServices; testWebVIP; testMgrVIP

The function pingHosts is a basic ping test to each defined vRA component

The function testvRAServices was an interesting one to write as I’m not overly familiar with working with APIs so it was a learning experience. I wanted to be able to report the status of all vRA services listed on the VAMI administration UI of a vRA appliance (https://vra:5480). The URL that the services are listed on is https://vra-vip.domain.local/component-registry/services/status/current so using the powershell Invoke-WebRequest you get back the page information.

Invoke-WebRequest

Line 56 in the script puts the page contents into a variable we can work with. You can see that the information we want is stored in Content (ServiceStatus) in Json format so you need to take that Json and convert it to  to powershell readable text using the ConvertFrom-Json function (ConvertFrom-Json converts a JSON-formatted string to a custom object (PSCustomObject) that has a property for each field in the JSON string) Line 58 does this

We then put each service into the $serviceinfo variable and loop through them to get the service name and service status.

Side note here: Originally I was querying $json.content.serviceStatus to get the details i wanted but i noticed I wasnt getting a full list of services, i was getting some blank content and also some duplicate service names. This is how i was doing it

$vRAURL = Invoke-WebRequest "https://vra-vip.vlab.local/component-registry/services/status/current" 
$json = ConvertFrom-Json -InputObject $vRAURL.content 
$serviceInfo = $json.content.serviceStatus | Select serviceName,serviceInitializationStatus $serviceInfo 

Here is that that returns. As you can see its not the full list and there is a duplicate entry so its not much use

Duplicate Results

I dug a little into the API and it seems that it does indeed contain inconsistent information. Here is an excerpt with some issues where the actual service name is content-management but the serviceStatus reports the name as advanced-designer-service

Service Name issue

So to get an accurate list i queried the serviceName field to get the name and the serviceStatus.serviceInitializationStatus to get the service status. Unfortunately doing it this way doesnt allow creating a nice formatted table (at least i havent figured out how to do it yet!) but i did get it to print out each service & status on the same line.

Line 68: In my lab i use a vRO appliance so the internal vRO service on the vRA appliance is stopped. The service status comes back blank so i added a check to report blank service status as “BLANK – POSSIBLY STOPPED?”.

Line 72: I also added a check to print any failed services in red so they stand out.

The testWebVIP and testManagerVIP functions use the powershell System.Net.WebRequest to get the HTTP status code for a given URL. If the status code comes back as 200 then everything is ok. If not there is an issue with your IaaS components

So there you have it. A quick way to verify the status of all of the important vRealize Automation components and services. In my example below the iaas-service is in a failed state (The driving reason for creating this script! 🙂 )

Script Results