unable to use [CmdletBinding()] advanced script with VMware cmdlet

Hi,

After reading PowerShell in a month of lunches I tend to write parameterized scripts with advanced [CmdletBinding()] method.

I hit a snag when trying to write a script to deploy multiple VMs to VMware vCloud Director. The cmdlet I am using (New-CIVM) seems to only have the functionality to create one VM. If I pass 2 computer names to it fails.

The only workaround I could get working was to use a foreach loop:

$vms = Import-Csv -Path "C:\Users\user.name\Desktop\Scripts\vms.csv"



ForEach ($vm in $vms) {
New-CIVM -VApp $vm.vapp -Name $vm.name -ComputerName $vm.hostname -VMTemplate (Get-CIVMTemplate | Where-Object {$_.Name -clike "*2012*" -and $_.OrgVdc -clike "VDC1"})
}
The script I wanted to write should work from the cli in this fashion:


.\New-CiVM.ps1 -vapp 'VDC1 servers' -name SRVR-TEST1,SRVR-TEST2 -hostname TEST-DC1,TEST-DC2 -os Win2019,win2016

Is the problem that VMware has not developed the cmdlet properly to work in this setup or is there another way in which I can collect the variables for multiple VMs without using a csv file?

Welcome to Powershell.org. Do not think you understand what [CmdLetBinding()] does. You can read the link below, but when you have that at the top of a function, that is just turning on some of the built in behaviors like Verbose and Debug. If you don’t have that defined and try to use Write-Verbose in a function, it won’t work because you have not ‘enabled’ that in the function:

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_cmdletbindingattribute?view=powershell-7.1

From what you post says, there are several relevant things you need to understand. It’s hard to know exactly what because you did not post the function code, just the function calls and you said it failed but not what error occurred or the behavior observed. Here are key pieces:

  • String[] - This indicates the param is a string array. If this is defined as just string (e.g. [string]$Name), it's expecting one value.
  • for loop - Since you want to process each name, there should be a for loop to process for every name.
  • ValueFromPipeline=$true - This is getting a bit more advanced, but if you want to pipe the names to the function, you need to set this in the parameter block. You can only have one parameter set with ValueFromPipeline that will be processes in the PROCESS{} block.

Here is a shell function to play with:

function New-CIVM {
    [CmdletBinding()]
    param (
            [Parameter(
                Mandatory=$true,
                ValueFromPipeline=$true
            )]
            [String[]]$Name

    )
    begin {}
    process {
        foreach ($n in $name) {
            Write-Verbose "Processing $n"
        }
    }
    end {}
}

New-CiVM -Name 'host1','host2' -Verbose

#ValueFromPipeline
'host1','host2' | New-CiVM -Verbose

Output:

PS C:\Users\rasim> New-CiVM -Name 'host1','host2' -Verbose
VERBOSE: Processing host1
VERBOSE: Processing host2
PS C:\Users\rasim> 'host1','host2' | New-CiVM -Verbose
VERBOSE: Processing host1
VERBOSE: Processing host2

Here is a link for ValueFromPipeline and other advanced params:
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-7.1

Thank you. great to be here!

Sorry, I should have posted the full code, but it’s not a function which I am using it is just a parameterized script with cmdletbinding set. I have tested functions before but seemed to have issues in getting them to load when moving the script around to different servers so I tend not to use them. I tested the script for creating one VM and it works. It is only when I changed it to for loop and string array it broke and does not report back any error.

Here is the full code:

 

[CmdletBinding()]
param (
    [Parameter(Mandatory=$True)]
    [string]$vapp,
    [Parameter(Mandatory=$True)]
    [string[]]$name,
    [Parameter(Mandatory=$True)]
    [string[]]$hostname,
    [Parameter(Mandatory=$True)]
    [ValidateSet('Win2012R2','Win2016','Win2019')]
    [string[]]$os,
    [Parameter(Mandatory=$True)]
    [ValidateSet('MKN1-PEC4','EDI3-PEC4')]
    [string]$pecplatform
)

switch ( $pecplatform )
    {
        MKN1-PEC4 { $pec2VDC = 'PEC2 mkn1clouc2 VDC'    }
        EDI3-PEC4 { $pec2VDC = 'PEC2 edi3clouc2 VDC'    }
}

ForEach ($name in $names) {
New-CIVM -VApp $vapp -Name $name -ComputerName $hostname -VMTemplate (Get-CIVMTemplate | Where-Object {$_.Name -clike "*$os*" -and $_.OrgVdc -clike "$pec2VDC"})
}

Try to make a look on the supported parameter for the command from here

https://vdc-repo.vmware.com/vmwb-repository/dcr-public/6fb85470-f6ca-4341-858d-12ffd94d975e/4bee17f3-579b-474e-b51c-898e38cc0abb/doc/New-CIVM.html

This command may not accept an array or it might need some additional input from a different command pipeline, to be able to provide better help, kindly share the error message you are getting when you run the script.

I agree with @Rob about the usage of cmdletbinding(), its totally fine and you can write your script without it, and just use the regular parameters and the validation set.

 

Yes, I think that it maybe doesn’t accept an array. The VMware documentation doesn’t have much information in and only one example. How do you confirm it accepts an array? There is no error message to share, the command runs and does nothing at all.

Here is the full help page:

 

PS S:\Scripts\Delivery\provisioning\vCloud> help new-civm -full

NAME
    New-CIVM
    
SYNOPSIS
    This cmdlet creates a new cloud virtual machine.
    
    
SYNTAX
    New-CIVM [[-Name] <String>] [-ComputerName <String>] [-RunAsync] [-Server <CIServer[]>] -VApp <CIVApp> -VMTemplate <CIVMTemplate> [-Confirm] [-WhatIf] 
    [<CommonParameters>]
    
    
DESCRIPTION
    This cmdlet creates a new cloud virtual machine into an existing vApp by using a specified virtual machine template.
    

PARAMETERS
    -ComputerName <String>
        Specifies the computer name to be applied to the cloud virtual machine with guest customization.
        
        Required?                    false
        Position?                    named
        Default value                None
        Accept pipeline input?       False
        Accept wildcard characters?  false
        
    -Name <String>
        Specifies the name of the newly created cloud virtual machine. If not specified, the name of the virtual machine template is used.
        
        Required?                    false
        Position?                    1
        Default value                None
        Accept pipeline input?       False
        Accept wildcard characters?  false
        
    -RunAsync [<SwitchParameter>]
        Indicates that the command returns immediately without waiting for the task to complete. In this mode, the output of the cmdlet is a Task object. For more 
        information about the RunAsync parameter run "help About_RunAsync" in the VMware PowerCLI console.
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       False
        Accept wildcard characters?  false
        
    -Server <CIServer[]>
        Specifies the cloud servers on which you want to run the cmdlet. If no value is given to this parameter, the command runs on the default servers. For more 
        information about default servers, see the description of Connect-CIServer.
        
        Required?                    false
        Position?                    named
        Default value                None
        Accept pipeline input?       False
        Accept wildcard characters?  true
        
    -VApp <CIVApp>
        Specifies the vApp to which you want to add the cloud virtual machine.
        
        Required?                    true
        Position?                    named
        Default value                None
        Accept pipeline input?       True (ByValue)
        Accept wildcard characters?  false
        
    -VMTemplate <CIVMTemplate>
        Specifies the virtual machine template from which the new cloud virtual machine is created.
        
        Required?                    true
        Position?                    named
        Default value                None
        Accept pipeline input?       True (ByValue)
        Accept wildcard characters?  false
        
    -Confirm [<SwitchParameter>]
        If the value is $true, indicates that the cmdlet asks for confirmation before running. If the value is $false, the cmdlet runs without asking for user 
        confirmation.
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       False
        Accept wildcard characters?  false
        
    -WhatIf [<SwitchParameter>]
        Indicates that the cmdlet is run only to display the changes that would be made and actually no objects are modified.
        
        Required?                    false
        Position?                    named
        Default value                False
        Accept pipeline input?       False
        Accept wildcard characters?  false
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see 
        about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216). 
    
INPUTS
    
OUTPUTS
    The newly created CIVM object
        
    
    
NOTES
    
    
        
    
    -------------------------- Example 1 --------------------------
    
    $templateVM = Get-CIVMTemplate -Name "myTemplateVM"
    Get-CIVApp "myCIVApp" | New-CIVM -Name "myVM" -VMTemplate $templateVM
    
    Retrieves a virtual machine template named "myTemplateVM", creates a cloud virtual machine named "myVM" from the template, and adds it to a cloud vApp named 
    "myCIVApp".
    
RELATED LINKS
    Online Version: https://code.vmware.com/doc/preview?id=6330#/doc/New-CIVM.html
    Get-CIVM 
    Restart-CIVM 
    Start-CIVM 
    Suspend-CIVM 

UPDATE - if I run it like this I do get an error

S S:\Scripts\Delivery\provisioning\vCloud> New-CIVM -VApp 'PROV mkn1clouc2 VDC1 servers' -Name SRVR-JAMESTEST1,SRVR-JAMESTEST2 -ComputerName JW-DC1,JW-DC2 -VMTemplate (Get-CIVMTemplate | Where-Object {$_.Name -clike "*2012*" -and $_.OrgVdc -clike "PEC2 mkn1clouc2 VDC"})
New-CIVM : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Name'. Specified method is not supported.
At line:1 char:53
+ ... kn1clouc2 VDC1 servers' -Name SRVR-JAMESTEST1,SRVR-JAMESTEST2 -Comput ...
+                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [New-CIVM], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,VMware.VimAutomation.Cloud.Commands.Cmdlets.NewCIVM

I don’t think this code will work, the Name should be a string and what you pass is not considered as a string. it should be an object.

As I saw on VMWare help, this command position is 1 try the following, I don’t think it will work as this command don’t accept input from pipeline, but give it a try

“SRVR-JAMESTEST1”,“SRVR-JAMESTEST2” | New-CIVM -VApp ‘PROV mkn1clouc2 VDC1 servers’ -ComputerName JW-DC1,JW-DC2 -VMTemplate (Get-CIVMTemplate | Where-Object {$.Name -clike “2012” -and $.OrgVdc -clike “PEC2 mkn1clouc2 VDC”})

I tried another variation too by removing the non-mandatory hostname, but it didn’t work:

PS S:\Scripts\Delivery\provisioning\vCloud> "SRVR-JAMESTEST1","SRVR-JAMESTEST2" | New-CIVM -VApp 'PROV mkn1clouc2 VDC1 servers' -ComputerName JW-DC1,JW-DC2 -VMTemplate (Get-CIVMTemplate | Where-Object {$_.Name -clike "*2012*" -and $_.OrgVdc -clike "PEC2 mkn1clouc2 VDC"})
New-CIVM : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ComputerName'. Specified method is not supported.
At line:1 char:99
+ ... pp 'PROV mkn1clouc2 VDC1 servers' -ComputerName JW-DC1,JW-DC2 -VMTemp ...
+                                                     ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [New-CIVM], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,VMware.VimAutomation.Cloud.Commands.Cmdlets.NewCIVM
 

PS S:\Scripts\Delivery\provisioning\vCloud> "SRVR-JAMESTEST1","SRVR-JAMESTEST2" | New-CIVM -VApp 'PROV mkn1clouc2 VDC1 servers' -VMTemplate (Get-CIVMTemplate | Where-Object {$_.Name -clike "*2012*" -and $_.OrgVdc -clike "PEC2 mkn1clouc2 VDC"})
New-CIVM : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do 
not match any of the parameters that take pipeline input.
At line:1 char:39
+ ... MESTEST2" | New-CIVM -VApp 'PROV mkn1clouc2 VDC1 servers' -VMTemplate ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (SRVR-JAMESTEST1:String) [New-CIVM], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectNotBound,VMware.VimAutomation.Cloud.Commands.Cmdlets.NewCIVM
 
New-CIVM : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do 
not match any of the parameters that take pipeline input.
At line:1 char:39
+ ... MESTEST2" | New-CIVM -VApp 'PROV mkn1clouc2 VDC1 servers' -VMTemplate ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (SRVR-JAMESTEST2:String) [New-CIVM], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectNotBound,VMware.VimAutomation.Cloud.Commands.Cmdlets.NewCIVM

Thread has gone very quiet. Am I correct in thinking this cmdlet is very limited in its options for creating multiple VMs and the only available option is to use a CSV file and for loop? Struggling to come up with any more creative solutions. Seems like a very poor effort from VMware

The short answer is probably yes, there are limited options. Typically, if you’re managing a single item, that is a limitation on the API in the backend. Even if you create a wrapper for the function or try to edit the function, you are essentially just adding a for loop around their call that is affecting a single item.

[quote quote=288373]Thread has gone very quiet. Am I correct in thinking this cmdlet is very limited in its options for creating multiple VMs and the only available option is to use a CSV file and for loop? Struggling to come up with any more creative solutions. Seems like a very poor effort from VMware

[/quote]
I am using also VMWare and got stuck in similar issues multiple time, I am using vCenter vSphere, not Cloud Infra

thanks, guys. Interesting you mention APIs Rob as this seems to be a common problem I run into. I often find I can’t do what I need to with Powershell then after searching online people start talking about interacting with APIs using Powershell. At this point is where I usually get lost. Is it common for Powershell users to learn APIs or is that typically for the hardcore programmers?

Not sure I would denote myself as a hard core programmer, but I do consulting for products and need product A to integrate with product B. Powershell has grown exponentially with core cmdlets and then with online repositories. When someone asks to perform Create Read Update Delete (CRUD) in an application, I start by searching for a module or code examples (no need to re-invent the wheel) and then go down the rabbit hole REST API, SOAP API, cmd, database until I get accomplish what is required. API’s are typically built to support a GUI function, not to support massive multi-threaded automation operations, hence why you see single operation methods from a vendor.