The optimal way to address this is to have a dedicated workstation or server in the data center that you can log into with PowerShell and PowerCLI installed. Since you are hopefully logging in with your administrator token and your administrator account has either been given permission directly on vSphere or through a security group, then you should be able to just log into the server when you try to use Connect-VIServer (https://vdc-repo.vmware.com/vmwb-repository/dcr-public/85a74cac-7b7b-45b0-b850-00ca08d1f238/ae65ebd9-158b-4f31-aa9c-4bbdc724cc38/doc/Connect-VIServer.html). The credential of the currently logged on user, that’s you, will get passed along with the request.
$VIRoleName = "View Manager Role"
$VIRolePrivileges = @(`
# Folder
'Create Folder', 'Delete Folder',`
# Datastore
'Allocate space',`
# Virtual Machine - Configuration
'Add or remove device', 'Advanced configuration', 'Modify device settings',`
# Virtual Machine - Interaction
'Power off', 'Power on', 'Reset', 'Suspend', 'Perform wipe or shrink operations',`
# Virtual Machine - Inventory
'Create new', 'Create from existing', 'Remove',`
# Virtual Machine - Provisioning
'Customize guest', 'Deploy template', 'Read customization specifications', 'Clone template', 'Clone Virtual Machine',`
# Resource
'Assign virtual machine to resource pool',`
# Global
'Act as vCenter Server',`
# Host
'Advanced settings',`
# Profile-driven Storage
'Profile-driven storage view', 'Profile-driven storage update'
)
try {
# Get list of current Roles
$VIRoles = Get-VIRole
# Check if Role exists
foreach($VIRole in $VIRoles) {
if ($VIRole.Name -like $VIRoleName) {
# Role exists
exit
}
}
# Assume the Role does not exist
# Create the new Role
New-VIRole -Name $VIRoleName
# Add the Privileges to the Role
foreach($VIRolePrivilege in $VIRolePrivileges) {
Set-VIRole -Role $VIRoleName -AddPrivilege $VIRolePrivilege
}
} catch {
}
Copy and paste the contents above to a new PowerShell file. This script will check if the given Role exists and exit or it will create the Role and add the Privileges. This script will not check the current assigned Privileges if the Role exists.
You are also going to need the ovftool that can be downloaded from https://code.vmware.com/web/tool/4.4.0/ovf. In this post, we are going to need the newest version, 4.4.1. Make sure you have installed the ovftool on your Windows machine you are going to deploy from. Also, ensure you can access ovftool from the command line. You may have to add this to the System PATH.
Optionally, you can use an Integrated Development Environment (IDE) of your choice. For this post, I am going to use Visual Studio Code, available at https://code.visualstudio.com/download.
I have TLS certificates from Let’s Encrypt, so I am going to deploy with them as well. For me, this is how I have created my directory structure.
certs – contains all of my TLS certificates needed
ova – contains the ova I am going to deploy
uag-001v_setting.ini – this is the settings file I use for one of my Unified Access Gateways (UAG). You would need one per UAG you want to deploy.
uagdeploy.ps1 – PowerShell script included in the PowerShell scripts zip
uagdeploy.psm1 – PowerShell module included in the PowerShell scripts zip
The following is my uag-001v_settings.ini file. You can get the “barebones” file after deploying and configuring at least one UAG in vSphere. Then you can export the settings and modify as necessary.
[General]
eth0ErrorMsg={"netmask":"SUCCESS","ip":"SUCCESS","defaultGateway":"SUCCESS"}
#netInternet: Portgroup used in vSphere for Internet/DMZ facing interface
netInternet=DMZ
#ip0: IP address for the netInternet interface
ip0=10.10.10.30
diskMode=
#source: The location of the OVA to deploy
source=.\ova\euc-unified-access-gateway-fips-20.09.0.0-16949983_OVF10.ova
#ip1: IP address for the internal interface
ip1=192.168.92.30
#defaultGateway: IP address for the gateway on the netInternet interface
defaultGateway=10.10.10.1
#target: User ([email protected]),
#target: vCenter(vcenter.aaronrombaut.com),
#target: host location (/Datacenter/host/Cluster/vmh-001p.aaronrombaut.com)
target=vi://[email protected]:[email protected]/Datacenter/host/Cluster/vmh-001p.aaronrombaut.com
#ds: Datastore to install to
ds=Synology-LUN01
netmask0=255.255.255.0
#netManagementNetwork= Portgroup used in vSphere
netManagementNetwork=LAN
#netBackendNetwork: Portgroup used in vSphere
netBackendNetwork=LAN
ip0AllocationMode=STATICV4
#name: Name of the Unified Access Gateway appliance
name=uag-001v
deploymentOption=twonic
ip1AllocationMode=STATICV4
netmask1=255.255.255.0
authenticationTimeout=300000
fipsEnabled=true
sysLogType=UDP
uagName=uag-001v
clockSkewTolerance=600
locale=en_US
tls12Enabled=true
tls13Enabled=false
ipMode=STATICV4
requestTimeoutMsec=10000
ipModeforNIC2=STATICV4
tls11Enabled=false
clientConnectionIdleTimeout=360
tls10Enabled=false
adminCertRolledBack=false
cookiesToBeCached=none
enableHTTPHealthMonitor=false
snmpEnabled=false
maxSystemCPUAllowed=100
healthCheckUrl=/favicon.ico
quiesceMode=false
#dns: The Domain Name System server in use
dns=192.168.92.10
isTLS13SetByUser=false
isCiphersSetByUser=false
tlsPortSharingEnabled=true
ceipEnabled=false
bodyReceiveTimeoutMsec=15000
monitorInterval=60
maxConnectionsAllowedPerSession=16
cipherSuites=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
adminPasswordExpirationDays=90
httpConnectionTimeout=120
#dnsSearch: The domain name to use in queries
dnsSearch=aaronrombaut.com
isTLS11SetByUser=false
sessionTimeout=36000000
syslogSystemMessagesEnabled=false
ssl30Enabled=false
#ntpServers: Servers for providing time in the infrastructure
ntpServers=time.cloudflare.com
#sshEnabled: Leave this blank to NOT enable ssh which is recommended in Production
sshEnabled=
[Horizon]
#proxyDestinationUrl: URL for the VMware Horizon Connection Server
proxyDestinationUrl=https://hzn7cs-001v.aaronrombaut.com
disableHtmlAccess=false
rewriteOriginHeader=false
healthCheckUrl=/favicon.ico
proxyDestinationIPSupport=IPV4
queryBrokerInterval=300
matchWindowsUserName=false
windowsSSOEnabled=false
pcoipDisableLegacyCertificate=false
gatewayLocation=External
securityHeaders={"X-Frame-Options":"SAMEORIGIN","Strict-Transport-Security":"max-age=63072000; includeSubdomains; preload","X-Content-Type-Options":"nosniff","Content-Security-Policy":"default-src 'self';font-src 'self' data:;script-src 'self' 'unsafe-inline' 'unsafe-eval' data:;style-src 'self' 'unsafe-inline';img-src 'self' blob: data:","X-XSS-Protection":"1; mode=block"}
proxyDestinationUrlThumbprints=
tunnelExternalUrl=https://myhorizon.aaronrombaut.com:443
blastExternalUrl=https://myhorizon.aaronrombaut.com:8443
radiusClassAttributeList=
smartCardHintPrompt=false
logoutOnCertRemoval=false
redirectHostMappingList=
proxyPattern=(/|/view-client(.*)|/portal(.*)|/appblast(.*))
pcoipExternalUrl=72.225.4.11:4172
[SSLCert] #External facing
pemPrivKey=
pemCerts=
#pfxCerts: The location where the certificates are located
pfxCerts=.\certs\myhorizon-prod.p12
pfxCertAlias=
[SSLCertAdmin] #Internal facing
pemPrivKey=
pemCerts=
#pfxCerts: The location where the certificates are located
pfxCerts=.\certs\myhorizon-prod.p12
pfxCertAlias=
[PackageUpdates]
packageUpdatesScheme=OFF
Open an elevated PowerShell window and navigate to the working directory. You will want to type the following to start the deployment:
.\uagdeploy.ps1 .\uag-001v_settings.ini
Enter and re-enter the root password and admin password.
Enter Yes or No if you want to join the VMware Customer Experience Improvement Program (CEIP).
Enter the password for the vCenter you specified in the settings.ini file above.
You should receive a “deployed successfully” message at the end. From here, you should be able to navigate to the appliance in a web browser and if you used certificates, your page should be accessible, securely. For me, I access the UAG at https://uag-001v.aaronrombaut.com:9443.
If you have other Unified Access Gateways to deploy, just modify the settings.ini file and deploy. Make sure you have records for each UAG added to your DNS forward and reverse lookup zones.
PowerShell error catching has been very frustrating for me. I try to do the right thing by putting code in a Try-Catch-Finally block, but continue to struggle with catching specific errors. I don’t know why the ‘thing’ I need to catch is not output with the error. I have to go through the error and ‘hunt’ for the thing to catch. Below is the basic syntax for a PowerShell Try-Catch-Finally block.
try {
# Do something
} catch [something here to catch] {
# Handle the error
} finally {
# This section will always run
}
The sample below is similar to what you would typically see after an error is caught. Unfortunately, there is nothing in the output that you can use to ‘catch’.
What you end up having to do is use
$Error[0] | Format-List * -Force
This will show a longer output of the error, and more specifically, the ‘thing’ to ‘catch’. See the highlighted text below.
Now that there is a specific Exception to catch, we can add a new catch block to our Try-Catch-Finally block.
try {
# Do something
} catch [System.Management.Automation.ParameterBindingException] {
# Handle the specific error
} finally {
# This section will always run
}
I am not quite sure why it is so hard to find the exception to use in the catch block or why it is just not part of the error output. I suppose for someone who codes in PowerShell everyday, this post will be laughable. I just wanted to make sure I recorded a post for future reference because I know for sure, I will struggle with this concept again.
So I wrote a script the other day in PowerShell ISE and it worked fine. But when I wanted to use the function in a standard PowerShell window, I was perplexed. It is actually a really easy thing to take care of.
In the case of my server, I wrote a PowerShell function and stored it in my Documents folder. So when opening a new PowerShell window, all I had to do was run the following command:
This made all of the functions written in the script available for use during my current session. For me and this case, I am OK with only running the script when needed.
One of the most annoying things for me (and I assume many other Systems Administrators) is going into an organization and querying users to find all variations of name formats. Some are ‘first name last name’, ‘last name, first name’, or some other variation. It’s almost like an archeological dig where you can see periods of time that there was one format and then later on another format came along.
What’s frustrating is knowing how easy it is to change the format and that it doesn’t happen. It’s fine if the organization wants to change the format, but if that’s the case, then be sure to change the information already contained to match. There is nothing wrong with periodically running a “cleanup” script over Active Directory to make everything uniform. The great thing about Active Directory is that it is a database, it already contains the information. The DisplayName property and the cn are display properties, they can be changed whenever without affecting the user object. Also, running a script like the one below can clean up and make Active Directory uniform in a matter of seconds, if not less.
# Where the users are located that you want to change
$exerciseUsersOU = 'OU=USERS,OU=TEMPORARY,DC=aaronrombaut,DC=com'
# An array of the users (adjust Properties as needed)
$exerciseUsers = Get-ADUser -Filter * -SearchBase $exerciseUsersOU -Properties Title, GivenName, Surname
# Loop through all the users
foreach ($exerciseUser in $exerciseUsers)
{
$title = $exerciseUser.Title
$firstName = $exerciseUser.GivenName
$lastName = $exerciseUser.Surname
# The following line will adjust the DisplayName
Set-ADUser -Identity $exerciseUser -DisplayName "$lastName, $firstName, $title"
# The following line will adjust the cn
Rename-ADObject -Identity $exerciseUser -NewName "$lastName, $firstName"
}
The following images show a before and after but are only a representation of what you can rename from and to. Your organization may use different naming standards. Either way, when standards change, be sure to adjust the objects already present. This will be much more professional and organized.