Using PowerCLI in Smart Card Based Environment

ref: https://kb.vmware.com/s/article/67789

Problem

You work in a hardened environment and you don’t have an administrative username and password because you only have smart cards or tokens.

Resolution

According to VMware, this is expected behavior. Uh, what?

Workaround

According to VMware, “Use Windows SSPI to pass through the Windows logged session Smart Card credentials to PowerCLI to authenticate to vCenter.”

What does that even mean? That was a lot of words that didn’t really explain how to “work around” the issue. So, let’s break it down.

Windows SSPI is Security Support Provider Interface. More details can be found here, https://docs.microsoft.com/en-us/windows/win32/rpc/security-support-provider-interface-sspi-

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.

PowerCLI Script to add vCenter Privileges for VMware Horizon 7

ref: https://docs.vmware.com/en/VMware-Horizon-7/7.12/horizon-installation/GUID-A878F876-B359-42FC-9124-A1E34BFB3319.html

ref: https://code.vmware.com/docs/11794/cmdlet-reference/doc/Get-VIPrivilege.html

ref: https://code.vmware.com/docs/11794/cmdlet-reference/doc/New-VIRole.html

ref: https://code.vmware.com/docs/11794/cmdlet-reference/doc/Set-VIRole.html

$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.

PowerShell Error Catching

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
}

The best help I was able to find for this came from a Spiceworks post by Duffney, https://community.spiceworks.com/how_to/121063-using-try-catch-powershell-error-handling. It was by far the easiest method to discover the specific error to use in the catch block.

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.