VMware Solutions Discussions

Automated Re-Deploy of VMs using VSC cmdlets (Powershell Script)

SCOTTS2012
7,022 Views

Hi,

Recently we had a daily powershell script that ran and re-deployed a group of servers daily. The servers were originally deployed using FlexClone and we used a Powershell script that connected to Kamino and VIServer to issue the redeploy on a scheduled task.

This was all working fine when we were using vCenter 4.1 and the Kamino Powershell module. However we have recently upgraded to vCenter 5.5 and now use VSC 4.2.1. According to https://communities.netapp.com/thread/25368 our old script should work with VSC with a few modifications to the Cmdlet names and importing the VSC .dll instead of Kamino.

However after modifying my script I can no longer connect to VSC.

Powershell Script: -

Import-Module C:\Windows\System32\WindowsPowerShell\v1.0\Modules\VSC\NetAppVSC.dll

$password = ConvertTo-SecureString -AsPlainText -Force "<password>"

$creds = New-Object System.Management.Automation.PSCredential "domain\username",$password
$controllers = New-Object com.netapp.vsc.wsapi.controllerSpec[] 1

Write-Host "Connecting to vCenter ....."
Connect-VIServer <IP Address> -User "domain\username" -Password ""

Write-Host "Connecting to VSC ...."
Connect-VSC <IP Address> -Credential $creds -Timeout 120000 -ServiceHost <IP Address>

Results of running the above is:-

Connecting to vCenter .....

Name                           Port      User                         

  ----                               ----         ----                         

<IP Address>                 443    domain\username

      

Connecting to VSC on Ferndownvc....

VCenterHostname    : <IP Address>

VCenterAddress     :  <IP Address>

VCenterCredentials : System.Net.NetworkCredential

ServiceHostname    :  <IP Address>

ServiceAddress     :   <IP Address>

VCenterVersion     :

Cannot tell from the above whether it has connected to vCenter and / or VSC. Previously running the above command under vCenter 4.1 and Kamino it would result in the VCenterVersion field populated as well.

The next line of code fails, which I think is due to not actually being connected: -

$targetMoref = Get-vscManagedObjectRef <datacenter name> Datacenter

Write-Host $targetMoref

Error message: -

Get-vscManagedObjectRef : The request failed with HTTP status 404: Not Found.

At C:\flexscripts\FlexRedeploy-rds_flexclone_a.ps1:19 char:39

+ $targetMoref = Get-vscManagedObjectRef <<<<  <datacenter name> Datacenter

    + CategoryInfo          : InvalidOperation: (com.netapp.vsc.vscServer:vscServer) [Get-vscManagedObjectRef], Web

   Exception

    + FullyQualifiedErrorId : ApiError,com.netapp.vsc.cmdlets.GetvscManagedObjectRef

I think If I can get passed this stage the rest of the script should be ok, however I have exchausted my troubleshooting to get to this stage. Are there any logs or tools I can use to troubleshoot this further, or if this is down to incorrect syntax then please advise.

The reason we are scrapping and rebuilding these servers on a daily basis is because they are Remote Desktop Servers. Any changes we make to a master clone, is then  re-deployed, thus not having to install new software 50+ times.

As previously mentioned this has been working well for months and has only broke since moving to vCenter 5.5 and VSC cmdlet module.

Any help on this matter would be much appreciated.

Regards,

Scott S.

1 ACCEPTED SOLUTION

mbeattie
7,022 Views

Hi Scott,

I've had a look into this for you. When using the GUI or automated methods, the vCenter task results do not mention applying guest customization for the virtual machines that have been redeployed (nor does the Kamino.log) so I wrote a script to test if applying the customization post redeploy process would work (using the vmID's returned by the VSC getVMs methods). Note that VSC and VMware return a different virtual machine ID syntax so a replace of ":" with "-" is required. See results and code below.

The customization fails to apply when running the script below after a VSC redeploy (invoked either using GUI or automation).

The error is "fault.CustomizationPending.summary" which seems to relate to:

http://kb.vmware.com/kb/1006809

Maybe due to the template being cloned with a customization in pending state. Would be interested to know if you get the same results if you run the script to apply the customization post VSC redeploy. I'm note sure why VSC isn't applying the customization (didn't work for me in the GUI either) but the script below offers a work around example to complete the process.

Cheers Matt

Example Output:

Connected to Virtual Center "testvc01"

Enumerated Guest Customization "test1"

Connect to Virtual Machine "testxp022"

Failed Applying Guest Customization "test1" to "testxp022"

Connect to Virtual Machine "testxp021"

Failed Applying Guest Customization "test1" to "testxp021"

Connect to Virtual Machine "testxp020"

Failed Applying Guest Customization "test1" to "testxp020"

Done

<#'-----------------------------------------------------------------------------

'Script Name : ApplyCustomization.ps1  

'Author      : Matthew Beattie

'Email       : mbeattie@netapp.com

'Created     : 17/12/13

'Description : This script applies a guest customization to virtual machines

'            : using the VMware API.

#'----------------------------------------------------------------------------#>

$vCenterName       = "testvc01"

$protocol          = "https"

$snapInName        = "VMware.VimAutomation.Core"

$customizationName = "test1"

$username          = "testlab\administrator"

#'------------------------------------------------------------------------------

#'Prompt for credentials.

#'------------------------------------------------------------------------------

[System.Security.SecureString]$password = `

Read-Host "Please enter the password for user ""$username""" -AsSecureString

[System.Management.Automation.PSCredential]$credentials = `

New-Object System.Management.Automation.PSCredential -ArgumentList $username, $password

#'------------------------------------------------------------------------------

#'Ensure the VMware PowerShell SnapIn is added.

#'------------------------------------------------------------------------------

Try{

   Add-PSSnapin -Name $snapInName -ErrorAction SilentlyContinue

}Catch{

   Write-Host "The SnapIn ""$snapInName"" is added"

}

#'------------------------------------------------------------------------------

#'Connect to Virtual Center.

#'------------------------------------------------------------------------------

Try{

   Connect-VIServer -Server $vCenterName -Protocol $protocol -Credential $credentials -Force -ErrorAction Stop | Out-Null

   Write-Host "Connected to Virtual Center ""$vCenterName"""

}Catch{

   Write-Host "Error Connecting to ""$vCenterName"""

   Break;

}

#'------------------------------------------------------------------------------

#'Get the VMWare Guest Customization.

#'------------------------------------------------------------------------------

Try{

   $customSpec = Get-OSCustomizationSpec -Name $customizationName -ErrorAction Stop

   Write-Host "Enumerated Guest Customization ""$customizationName"""

}Catch{

   Write-Host "Failed Enumerating Guest Customization ""$customizationName"""

   Break;

}

#'------------------------------------------------------------------------------

#'Set the template for each VM (replace ":" with "-"). VSC and vSphere return different ID formats.

#'------------------------------------------------------------------------------

$vms = @("VirtualMachine:vm-350","VirtualMachine:vm-349","VirtualMachine:vm-348")

ForEach($vm In $vms){

   $vmId = $vm -Replace(":", "-")

   Do{

      #'------------------------------------------------------------------------

      #'Connect to the Virtual Machine by ID.

      #'------------------------------------------------------------------------

      Try{

         $virtualMachine = Get-VM -Id $vmId -ErrorAction Stop

         Write-Host "Connect to Virtual Machine ""$virtualMachine"""

      }Catch{

         Write-Host "Failed Connecting to Virtual Machine ""$vmId"""

         Break;    

      }

      #'------------------------------------------------------------------------

      #'Set the guest customization for the virtual machine.

      #'------------------------------------------------------------------------

      Try{

         Set-VM -VM $virtualMachine -OSCustomizationSpec $customSpec -Confirm:$False -ErrorAction Stop

         Write-Host "Applied Guest Customization ""$customizationName"" to ""$virtualMachine"""

      }Catch{

         Write-Host "Failed Applying Guest Customization ""$customizationName"" to ""$virtualMachine"""

         Break;

      }

   }Until($True)

}

Write-Host "Done"

#'------------------------------------------------------------------------------

If this post resolved your issue, help others by selecting ACCEPT AS SOLUTION or adding a KUDO.

View solution in original post

6 REPLIES 6

mbeattie
7,022 Views

Hi Scott,

I've succeeded in getting this working in my lab. Hope this helps.

Cheers Matt

Example output:

Connected to VSC on IPAddress "192.168.100.19" on Port "8143"

Enumerated Managed Object Reference for "testxp01" as "VirtualMachine:vm-163"

Enumerated Managed Object Reference for "Testlab" as "Datacenter:datacenter-2"

Enumerated Managed Object Reference for "Datastore1" as "Datastore:datastore-144"

Enumerating files for Virtual Machine Template "VirtualMachine:vm-163"

Enumerating Virtual Machines deployed from Template "VirtualMachine:vm-163"

VirtualMachine:vm-350

VirtualMachine:vm-349

VirtualMachine:vm-348

Initiated VSC Redeploy. VCenter TaskID "Task:task-2368"

Example source code:

<#'-----------------------------------------------------------------------------

'Script Name : redeploy.ps1  

'Author      : Matthew Beattie

'Email       : mbeattie@netapp.com

'Created     : 17/12/13

'Description : This script invokes the "redeployVMs" method of the VSC API.

'            : It redeploys all virtual machines provisioned from a specifed

'            : template to the source templates origional disk state. This

'            : has the potential for data loss in all virtual machines

'            : deployed from the template. Use at your own risk.

'            :

'Disclaimer  : (c) 2013 NetApp Inc., All Rights Reserved

'            :

'            : NetApp disclaims all warranties, excepting NetApp shall provide

'            : support of unmodified software pursuant to a valid, separate,

'            : purchased support agreement. No distribution or modification of

'            : this software is permitted by NetApp, except under separate

'            : written agreement, which may be withheld at NetApp's sole

'            : discretion.

'            : 

'            : THIS SOFTWARE IS PROVIDED BY NETAPP "AS IS" AND ANY EXPRESS OR

'            : IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

'            : WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR

'            : PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NETAPP BE LIABLE FOR ANY

'            : DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL

'            : DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE

'            : GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS

'            : INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,

'            : WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

'            : NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF

'            : THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'-----------------------------------------------------------------------------#>

[String]$vscIPAddress      = "192.168.100.19"

[String]$vscHostName       = "testvc01"

[Int]$portNumber           = 8143

[String]$username          = "testlab\administrator"

[String]$templateName      = "testxp01"

[String]$dataCenterName    = "Testlab"

[String]$vFilerHostName    = "testnv01"

[String]$vFilerIPAddress   = "192.168.100.23"

[String]$customizationName = "testxp01"

#'------------------------------------------------------------------------------

#'Prompt for VSC credentials.

#'------------------------------------------------------------------------------

[String]$username = "testlab\administrator"

[System.Security.SecureString]$password = `

Read-Host "Please enter the password for user ""$username""" -AsSecureString

[System.Management.Automation.PSCredential]$vscCredentials = `

New-Object System.Management.Automation.PSCredential -ArgumentList $username, $password

#'------------------------------------------------------------------------------

#'Connect to the VSC using the web service API.

#'------------------------------------------------------------------------------

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$True}

[String]$uri = "https://$vscIPAddress`:$portNumber/kamino/public/api?wsdl"

Try{

   [System.Web.Services.Protocols.SoapHttpClientProtocol]$connection = `

   New-WebServiceProxy -uri $uri -Credential $credentials -ErrorAction Stop

   Write-Host "Connected to VSC on IPAddress ""$ipAddress"" on Port ""$portNumber"""

}Catch{

   Write-Host ("Error """ + $Error[0] + """ Connecting to ""$uri""")

   Break;

}

#'------------------------------------------------------------------------------

#'Create a namespace object from the connection object

#'------------------------------------------------------------------------------

[System.Object]$namespace = $connection.GetType().Namespace

#'------------------------------------------------------------------------------

#'Create a requestspec Object from the NameSpace object

#'------------------------------------------------------------------------------

[System.Object]$requestSpecType = ($namespace + '.requestSpec')

[System.Object]$requestSpec     = New-Object ($requestSpecType)

#'------------------------------------------------------------------------------

#'Enumerate the username and password from the credential object.

#'------------------------------------------------------------------------------

[String]$domain    = $credentials.GetNetworkCredential().domain

[String]$user      = $credentials.GetNetworkCredential().username

[String]$password  = $credentials.GetNetworkCredential().password

[String]$username  = "$domain\$user"

#'------------------------------------------------------------------------------

#'Set the properties of the RequestSpec object.

#'------------------------------------------------------------------------------

$requestSpec.serviceUrl = "https://" + $vscHostName + "/sdk"

$requestSpec.vcUser     = $username

$requestSpec.vcPassword = $password

#'------------------------------------------------------------------------------

#'Enumerate the Managed Object Reference of the VMWare Template.

#'------------------------------------------------------------------------------

[System.Object]$templateMoref = $connection.getMoref($templateName, "VirtualMachine", $requestSpec)

Write-Host "Enumerated Managed Object Reference for ""$templateName"" as ""$templateMoref"""

#'------------------------------------------------------------------------------

#'Enumerate the Managed Object Reference of the VMWare Datacenter.

#'------------------------------------------------------------------------------

[System.Object]$dataCenterMoref = $connection.getMoref($dataCenterName, "Datacenter", $requestSpec)

Write-Host "Enumerated Managed Object Reference for ""$dataCenterName"" as ""$dataCenterMoref"""

#'------------------------------------------------------------------------------

#'Enumerate the Managed Object Reference of the Datastore.

#'------------------------------------------------------------------------------

[System.Object]$dataStoreMoref = $connection.getMoref($dataStoreName, "Datastore", $requestSpec)

Write-Host "Enumerated Managed Object Reference for ""$dataStoreName"" as ""$dataStoreMoref"""

#'------------------------------------------------------------------------------

#'Enumerate the VMWare template files.

#'------------------------------------------------------------------------------

Write-Host "Enumerating files for Virtual Machine Template ""$templateMoref"""

Try{

   $files = $connection.getVMFiles($templateMoref, $requestSpec)

}Catch{

   Write-Host ("Error """ + $Error[0] + """ Enumerating Virtual Machine Files for ""$templateMoref""")

   Break;

}

#'------------------------------------------------------------------------------

#'Enumerate the VMWare template files.

#'------------------------------------------------------------------------------

Write-Host "Enumerating Virtual Machines deployed from Template ""$templateMoref"""

Try{

   $virtualMachines = $connection.getVMs($templateMoref, $requestSpec)

}Catch{

   Write-Host ("Error """ + $Error[0] + """ Enumerating Virtual Machines ""$templateMoref""")

   Break;

}

#'------------------------------------------------------------------------------

#'

#'------------------------------------------------------------------------------

[Array]$vms = @()

ForEach($virtualMachine In $virtualMachines){

   [Array]$vms += $virtualMachine.vmMoref

   Write-Host $virtualMachine.vmMoref

}

#'---------------------------------------------------------------------------

#'Create a controllerSpec Object from the NameSpace object and set properties.

#'---------------------------------------------------------------------------

[System.Object]$controllerType                    = ($namespace + '.controllerspec')

[System.Object]$controllerSpec                    = New-Object ($controllerType)

[System.Object]$controllerSpec.username           = $username

[System.Object]$controllerSpec.password           = $password

#'------------------------------------------------------------------------------

#'Set controller IP address (Ensure DNS A & PTR records exist)

#'------------------------------------------------------------------------------

[System.Object]$controllerSpec.ipAddress          = $vFilerIPAddress

[System.Object]$controllerSpec.passthroughContext = $vFilerHostName

[System.Object]$controllerSpec.ssl                = $True

#'------------------------------------------------------------------------------

#'Set the destination controller and datastore for each file.

#'------------------------------------------------------------------------------

ForEach($file In $files){

   $file.destDatastoreSpec.controller = $controllerSpec;

}

#'------------------------------------------------------------------------------

#'Create a "cloneSpec" object from the NameSpace object and set properties.

#'------------------------------------------------------------------------------

[System.Object]$cloneSpecType            = ($namespace + '.clonespec')

[System.Object]$cloneSpec                = New-Object ($cloneSpecType)

[System.Object]$cloneSpec.templateMoref  = $templateMoref

[System.Object]$cloneSpec.containerMoref = $dataCenterMoref

#'------------------------------------------------------------------------------

#'Create objects for each clone and set their properties.

#'------------------------------------------------------------------------------

[Array]$clones = @()

For($i = 0; $i -le ($vms.Count -1); $i++){

   #'---------------------------------------------------------------------------

   #'Create a vmSpec Object from the NameSpace object and set properties.

   #'---------------------------------------------------------------------------

   [System.Object]$vmSpecType         = ($namespace + '.vmSpec')

   [System.Object]$vmSpec             = New-Object ($vmSpecType)

   #'---------------------------------------------------------------------------

   #'Create a cloneSpecEntry Object from the NameSpace object and set properties.

   #'---------------------------------------------------------------------------

   [System.Object]$cloneSpecEntryType = ($namespace + '.cloneSpecEntry')

   [System.Object]$cloneSpecEntry     = New-Object ($cloneSpecEntryType)

   #'---------------------------------------------------------------------------

   #'Create a guestCustomizationSpecType Object from the NameSpace object and set properties.

   #'---------------------------------------------------------------------------

   [System.Object]$guestCustomizationSpecType  = ($namespace + '.guestCustomizationSpec')

   [System.Object]$guestCustomizationSpec      = New-Object ($guestCustomizationSpecType)

   [System.Object]$guestCustomizationSpec.Name = $customizationName

   [System.Object]$vmSpec.powerOn              = $powerOn

   [System.Object]$vmSpec.custSpec             = $guestCustomizationSpec

   [System.Object]$vmSpec.vmMoref              = $vms[$i]

   [System.Object]$cloneSpecEntry.key          = $vms[$i]

   [System.Object]$cloneSpecEntry.Value        = $vmSpec

   [Array]$clones                             += $cloneSpecEntry

   #'---------------------------------------------------------------------------

   #'Set the destination controller and datastore for the files.

   #'---------------------------------------------------------------------------

   ForEach($file In $files){

      $file.destDatastoreSpec.controller = $controllerSpec;

      $file.destDatastoreSpec.mor        = $dataStoreMoref;

   }  

}

#'------------------------------------------------------------------------------

#'Set the properties of the cloneSpec Object.

#'------------------------------------------------------------------------------

[System.Object]$cloneSpec.files       = $files

[System.Object]$cloneSpec.clones      = $clones

[System.Object]$requestSpec.cloneSpec = $cloneSpec

#'------------------------------------------------------------------------------

#'Initiate the Rapid clone task for the Even Numbered Clones.

#'------------------------------------------------------------------------------

Try{

   [String]$taskId = $connection.redeployVMs($requestSpec, $controllerSpec)

   [String]$taskId = $taskId.SubString($taskId.LastIndexOf(" ") + 1)

   Write-Host "Initiated VSC Redeploy. VCenter TaskID ""$taskId"""

}Catch{

   Write-Host "Failed Initiating VSC Redeploy"

   Break;

}

#'------------------------------------------------------------------------------

If this post resolved your issue, help others by selecting ACCEPT AS SOLUTION or adding a KUDO.

SCOTTS2012
7,022 Views

Hi Matt,

Many thanks for taking the time to look at this - much appreciated. I will give this a try this afternoon but does look promising!

Regards,

Scott S.

SCOTTS2012
7,022 Views

Hi Matt,

I have tested your script and initially it looks like it is working, however I have a couple of  anomalies : -

  • When the script runs the Write-Host output does not display the Datastore name (highlighted below in red):-

Connected to VSC on IPAddress "xxx.xxx.xxx.xxx" on Port "xxxx"

Enumerated Managed Object Reference for "testrctemplate" as "VirtualMachine:vm-2001"

Enumerated Managed Object Reference for "" as "Datacenter:datacenter-21"

Enumerated Managed Object Reference for "" as ""

Enumerating files for Virtual Machine Template "VirtualMachine:vm-2001"

Enumerating Virtual Machines deployed from Template

"VirtualMachine:vm-2001"

VirtualMachine:vm-2231

VirtualMachine:vm-2230

Initiated VSC Redeploy. VCenter TaskID "Task:task-26109"

 

  •    After running the script and returning to vSphere client I can see the task running and completing. However it never mentions "applying guest

   customization", which is crucial for the redeploy as we sysprep the VM, then run a script included in the image, which does some funky stuff;

   join domain and move to OU in Active directory, then install certificates, etc, etc.

  If I power on the redeployed machine the guest customization does not start, which makes me think the redploy script has not included it when

  submitting the task.

 

I appreciate your help with this issue to date and we are practically there bar these two small issues. If I could ask for your help one last time I

would be very grateful.

Thank you

Scott S

mbeattie
7,023 Views

Hi Scott,

I've had a look into this for you. When using the GUI or automated methods, the vCenter task results do not mention applying guest customization for the virtual machines that have been redeployed (nor does the Kamino.log) so I wrote a script to test if applying the customization post redeploy process would work (using the vmID's returned by the VSC getVMs methods). Note that VSC and VMware return a different virtual machine ID syntax so a replace of ":" with "-" is required. See results and code below.

The customization fails to apply when running the script below after a VSC redeploy (invoked either using GUI or automation).

The error is "fault.CustomizationPending.summary" which seems to relate to:

http://kb.vmware.com/kb/1006809

Maybe due to the template being cloned with a customization in pending state. Would be interested to know if you get the same results if you run the script to apply the customization post VSC redeploy. I'm note sure why VSC isn't applying the customization (didn't work for me in the GUI either) but the script below offers a work around example to complete the process.

Cheers Matt

Example Output:

Connected to Virtual Center "testvc01"

Enumerated Guest Customization "test1"

Connect to Virtual Machine "testxp022"

Failed Applying Guest Customization "test1" to "testxp022"

Connect to Virtual Machine "testxp021"

Failed Applying Guest Customization "test1" to "testxp021"

Connect to Virtual Machine "testxp020"

Failed Applying Guest Customization "test1" to "testxp020"

Done

<#'-----------------------------------------------------------------------------

'Script Name : ApplyCustomization.ps1  

'Author      : Matthew Beattie

'Email       : mbeattie@netapp.com

'Created     : 17/12/13

'Description : This script applies a guest customization to virtual machines

'            : using the VMware API.

#'----------------------------------------------------------------------------#>

$vCenterName       = "testvc01"

$protocol          = "https"

$snapInName        = "VMware.VimAutomation.Core"

$customizationName = "test1"

$username          = "testlab\administrator"

#'------------------------------------------------------------------------------

#'Prompt for credentials.

#'------------------------------------------------------------------------------

[System.Security.SecureString]$password = `

Read-Host "Please enter the password for user ""$username""" -AsSecureString

[System.Management.Automation.PSCredential]$credentials = `

New-Object System.Management.Automation.PSCredential -ArgumentList $username, $password

#'------------------------------------------------------------------------------

#'Ensure the VMware PowerShell SnapIn is added.

#'------------------------------------------------------------------------------

Try{

   Add-PSSnapin -Name $snapInName -ErrorAction SilentlyContinue

}Catch{

   Write-Host "The SnapIn ""$snapInName"" is added"

}

#'------------------------------------------------------------------------------

#'Connect to Virtual Center.

#'------------------------------------------------------------------------------

Try{

   Connect-VIServer -Server $vCenterName -Protocol $protocol -Credential $credentials -Force -ErrorAction Stop | Out-Null

   Write-Host "Connected to Virtual Center ""$vCenterName"""

}Catch{

   Write-Host "Error Connecting to ""$vCenterName"""

   Break;

}

#'------------------------------------------------------------------------------

#'Get the VMWare Guest Customization.

#'------------------------------------------------------------------------------

Try{

   $customSpec = Get-OSCustomizationSpec -Name $customizationName -ErrorAction Stop

   Write-Host "Enumerated Guest Customization ""$customizationName"""

}Catch{

   Write-Host "Failed Enumerating Guest Customization ""$customizationName"""

   Break;

}

#'------------------------------------------------------------------------------

#'Set the template for each VM (replace ":" with "-"). VSC and vSphere return different ID formats.

#'------------------------------------------------------------------------------

$vms = @("VirtualMachine:vm-350","VirtualMachine:vm-349","VirtualMachine:vm-348")

ForEach($vm In $vms){

   $vmId = $vm -Replace(":", "-")

   Do{

      #'------------------------------------------------------------------------

      #'Connect to the Virtual Machine by ID.

      #'------------------------------------------------------------------------

      Try{

         $virtualMachine = Get-VM -Id $vmId -ErrorAction Stop

         Write-Host "Connect to Virtual Machine ""$virtualMachine"""

      }Catch{

         Write-Host "Failed Connecting to Virtual Machine ""$vmId"""

         Break;    

      }

      #'------------------------------------------------------------------------

      #'Set the guest customization for the virtual machine.

      #'------------------------------------------------------------------------

      Try{

         Set-VM -VM $virtualMachine -OSCustomizationSpec $customSpec -Confirm:$False -ErrorAction Stop

         Write-Host "Applied Guest Customization ""$customizationName"" to ""$virtualMachine"""

      }Catch{

         Write-Host "Failed Applying Guest Customization ""$customizationName"" to ""$virtualMachine"""

         Break;

      }

   }Until($True)

}

Write-Host "Done"

#'------------------------------------------------------------------------------

If this post resolved your issue, help others by selecting ACCEPT AS SOLUTION or adding a KUDO.

SCOTTS2012
7,022 Views

Hi Matt,

Just a quick update to say I now have the two scripts working correctly. I have made a couple of adjustments mainly so it does not prompt for passwords (so the scripts can be scheduled to run in the early hours of the morning), instead uses a text file containing an encrypted password, this is purely to make the script a little more secure than keeping AD passwords in plain text. I also enumurate the VMs in the ApplyCustomization script instead of having to list them manually in the script.

Thanks again for all yor help in finding a resolution to this issue, couldn't have done it otherwise.

I have attached the final three scripts for anyone else reading this post, wishing to redeploy their VMs and apply customization. The first script (one I found on Internet) is for creating the text file that will contain the encrypted password for the Redeploy and  the ApplyCusomization scripts.

Encrypt Password and save to text file script

#STORED CREDENTIAL CODE

$AdminName = Read-Host "Enter your Admin AD username"

$CredsFile = "C:\usercredentials.txt"

$FileExists = Test-Path $CredsFile

if  ($FileExists -eq $false) {

    Write-Host 'Credential file not found. Enter your password:' -ForegroundColor Red

    Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File $CredsFile

    $password = get-content $CredsFile | convertto-securestring

    $Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist domain\$AdminName,$password}

else

    {Write-Host 'Using your stored credential file' -ForegroundColor Green

    $password = get-content $CredsFile | convertto-securestring

    $Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist domain\$AdminName,$password}

#END OF STORED CREDENTIAL CODE

Redeploy Script


<#'-----------------------------------------------------------------------------
'Script Name : redeploy.ps1  
'Author      : Matthew Beattie
'Email       : mbeattie@netapp.com
'Created     : 17/12/13
'Description : This script invokes the "redeployVMs" method of the VSC API.
'            : It redeploys all virtual machines provisioned from a specifed
'            : template to the source templates origional disk state. This
'            : has the potential for data loss in all virtual machines
'            : deployed from the template. Use at your own risk.
'            :
'Disclaimer  : (c) 2013 NetApp Inc., All Rights Reserved
'            :
'            : NetApp disclaims all warranties, excepting NetApp shall provide
'            : support of unmodified software pursuant to a valid, separate,
'            : purchased support agreement. No distribution or modification of
'            : this software is permitted by NetApp, except under separate
'            : written agreement, which may be withheld at NetApp's sole
'            : discretion.
'            : 
'            : THIS SOFTWARE IS PROVIDED BY NETAPP "AS IS" AND ANY EXPRESS OR
'            : IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
'            : WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
'            : PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NETAPP BE LIABLE FOR ANY
'            : DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
'            : DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
'            : GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
'            : INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
'            : WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
'            : NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
'            : THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'-----------------------------------------------------------------------------#>
[String]$vscIPAddress      = "<IP Address>"
[String]$vscHostName       = "<VSC Host>"
[Int]$portNumber           = 8143
[String]$username          = "<Domain>\<Username>"
[String]$vmServiceCredFile = "<Path to text file containing encrypted password for above user>"    
[String]$templateName      = "<Template VM Name>"
[String]$dataCenterName    = "<Datacenter Name>"
[String]$vFilerHostName    = "<NetApp HostNAme>"
[String]$vFilerIPAddress   = "<NetApp Host IP>"
[String]$customizationName = "<Name of customization file to use>"
#'------------------------------------------------------------------------------
#'Prompt for VSC credentials.
#'------------------------------------------------------------------------------
[String]$username = "<Domain>\<Username>"
$vmServiceCreds = get-content $vmServiceCredFile | convertto-securestring
[System.Management.Automation.PSCredential]$vscCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $vmServiceCreds
#'------------------------------------------------------------------------------
#'Connect to the VSC using the web service API.
#'------------------------------------------------------------------------------
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$True}
[String]$uri = "https://$vscIPAddress`:$portNumber/kamino/public/api?wsdl"
Try{
   [System.Web.Services.Protocols.SoapHttpClientProtocol]$connection = New-WebServiceProxy -uri $uri -Credential $vscCredentials -ErrorAction Stop
   Write-Host "Connected to VSC on IPAddress ""$vscipAddress"" on Port ""$portNumber"""
}Catch{
   Write-Host ("Error """ + $Error[0] + """ Connecting to ""$uri""")
   Break;
}
#'------------------------------------------------------------------------------
#'Create a namespace object from the connection object
#'------------------------------------------------------------------------------
[System.Object]$namespace = $connection.GetType().Namespace
#'------------------------------------------------------------------------------
#'Create a requestspec Object from the NameSpace object
#'------------------------------------------------------------------------------
[System.Object]$requestSpecType = ($namespace + '.requestSpec')
[System.Object]$requestSpec     = New-Object ($requestSpecType)
#'------------------------------------------------------------------------------
#'Convert Encrypted Password to Plain Text and Assign to Variable - ss
#'------------------------------------------------------------------------------
$EncrpytedPassword = get-content $vmServiceCredFile | convertto-securestring
   # Get the plain text version of the password
    $password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($EncrpytedPassword))
#'------------------------------------------------------------------------------
#'Enumerate the username and password from the credential object.
#'------------------------------------------------------------------------------
[String]$domain    = "<Domain>"
[String]$user      = "<Username>"
[String]$username  = "$domain\$user"
#'------------------------------------------------------------------------------
#'Set the properties of the RequestSpec object.
#'------------------------------------------------------------------------------
$requestSpec.serviceUrl = "https://" + $vscHostName + "/sdk"
$requestSpec.vcUser     = $username
$requestSpec.vcPassword = $password
#'------------------------------------------------------------------------------
#'Enumerate the Managed Object Reference of the VMWare Template.
#'------------------------------------------------------------------------------
[System.Object]$templateMoref = $connection.getMoref($templateName, "VirtualMachine", $requestSpec)
# Write-Host "Enumerated Managed Object Reference for ""$templateName"" as ""$templateMoref"""
#'------------------------------------------------------------------------------
#'Enumerate the Managed Object Reference of the VMWare Datacenter.
#'------------------------------------------------------------------------------
[System.Object]$dataCenterMoref = $connection.getMoref($dataCenterName, "Datacenter", $requestSpec)
# Write-Host "Enumerated Managed Object Reference for ""$dataCenterName"" as ""$dataCenterMoref"""
#'------------------------------------------------------------------------------
#'Enumerate the Managed Object Reference of the Datastore.
#'------------------------------------------------------------------------------
[System.Object]$dataStoreMoref = $connection.getMoref($dataStoreName, "Datastore", $requestSpec)
# Write-Host "Enumerated Managed Object Reference for ""$dataStoreName"" as ""$dataStoreMoref"""
#'------------------------------------------------------------------------------
#'Enumerate the VMWare template files.
#'------------------------------------------------------------------------------
# Write-Host "Enumerating files for Virtual Machine Template ""$templateMoref"""
Try{
   $files = $connection.getVMFiles($templateMoref, $requestSpec)
}Catch{
  # Write-Host ("Error """ + $Error[0] + """ Enumerating Virtual Machine Files for ""$templateMoref""")
   Break;
}
#'------------------------------------------------------------------------------
#'Enumerate the VMWare template files.
#'------------------------------------------------------------------------------
Write-Host "Enumerating Virtual Machines deployed from Template ""$templateMoref"""
Try{
   $virtualMachines = $connection.getVMs($templateMoref, $requestSpec)
}Catch{
  # Write-Host ("Error """ + $Error[0] + """ Enumerating Virtual Machines ""$templateMoref""")
   Break;
}
#'------------------------------------------------------------------------------
#'
#'------------------------------------------------------------------------------
[Array]$vms = @()
ForEach($virtualMachine In $virtualMachines){
   [Array]$vms += $virtualMachine.vmMoref
   Write-Host $virtualMachine.vmMoref
}
#'---------------------------------------------------------------------------
#'Create a controllerSpec Object from the NameSpace object and set properties.
#'---------------------------------------------------------------------------
[System.Object]$controllerType                    = ($namespace + '.controllerspec')
[System.Object]$controllerSpec                    = New-Object ($controllerType)
[System.Object]$controllerSpec.username           = $username
[System.Object]$controllerSpec.password           = $password
#'------------------------------------------------------------------------------
#'Set controller IP address (Ensure DNS A & PTR records exist)
#'------------------------------------------------------------------------------
[System.Object]$controllerSpec.ipAddress          = $vFilerIPAddress
[System.Object]$controllerSpec.passthroughContext = $vFilerHostName
[System.Object]$controllerSpec.ssl                = $True
#'------------------------------------------------------------------------------
#'Set the destination controller and datastore for each file.
#'------------------------------------------------------------------------------
ForEach($file In $files){
   $file.destDatastoreSpec.controller = $controllerSpec;
}
#'------------------------------------------------------------------------------
#'Create a "cloneSpec" object from the NameSpace object and set properties.
#'------------------------------------------------------------------------------
[System.Object]$cloneSpecType            = ($namespace + '.clonespec')
[System.Object]$cloneSpec                = New-Object ($cloneSpecType)
[System.Object]$cloneSpec.templateMoref  = $templateMoref
[System.Object]$cloneSpec.containerMoref = $dataCenterMoref
#'------------------------------------------------------------------------------
#'Create objects for each clone and set their properties.
#'------------------------------------------------------------------------------
[Array]$clones = @()
For($i = 0; $i -le ($vms.Count -1); $i++){
   #'---------------------------------------------------------------------------
   #'Create a vmSpec Object from the NameSpace object and set properties.
   #'---------------------------------------------------------------------------
   [System.Object]$vmSpecType         = ($namespace + '.vmSpec')
   [System.Object]$vmSpec             = New-Object ($vmSpecType)
   #'---------------------------------------------------------------------------
   #'Create a cloneSpecEntry Object from the NameSpace object and set properties.
   #'---------------------------------------------------------------------------
   [System.Object]$cloneSpecEntryType = ($namespace + '.cloneSpecEntry')
   [System.Object]$cloneSpecEntry     = New-Object ($cloneSpecEntryType)
   #'---------------------------------------------------------------------------
   #'Create a guestCustomizationSpecType Object from the NameSpace object and set properties.
   #'---------------------------------------------------------------------------
   [System.Object]$guestCustomizationSpecType  = ($namespace + '.guestCustomizationSpec')
   [System.Object]$guestCustomizationSpec      = New-Object ($guestCustomizationSpecType)
   [System.Object]$guestCustomizationSpec.Name = $customizationName
   [System.Object]$vmSpec.powerOn              = $powerOn
   [System.Object]$vmSpec.custSpec             = $guestCustomizationSpec
   [System.Object]$vmSpec.vmMoref              = $vms[$i]
   [System.Object]$cloneSpecEntry.key          = $vms[$i]
   [System.Object]$cloneSpecEntry.Value        = $vmSpec
   [Array]$clones                             += $cloneSpecEntry
   #'---------------------------------------------------------------------------
   #'Set the destination controller and datastore for the files.
   #'---------------------------------------------------------------------------
   ForEach($file In $files){
      $file.destDatastoreSpec.controller = $controllerSpec;
      $file.destDatastoreSpec.mor        = $dataStoreMoref;
   }  
}
#'------------------------------------------------------------------------------
#'Set the properties of the cloneSpec Object.
#'------------------------------------------------------------------------------
[System.Object]$cloneSpec.files       = $files
[System.Object]$cloneSpec.clones      = $clones
[System.Object]$requestSpec.cloneSpec = $cloneSpec
#'------------------------------------------------------------------------------
#'Initiate the Rapid clone task for the Even Numbered Clones.
#'------------------------------------------------------------------------------
Try{
   [String]$taskId = $connection.redeployVMs($requestSpec, $controllerSpec)
   [String]$taskId = $taskId.SubString($taskId.LastIndexOf(" ") + 1)
  # Write-Host "Initiated VSC Redeploy. VCenter TaskID ""$taskId"""
}Catch{
   Write-Host "Failed Initiating VSC Redeploy"
   Break;
}
#'------------------------------------------------------------------------------

ApplyCustomization Script

[String]$vscIPAddress      = "<VSC IP Address>"
[String]$vscHostName       = "<VSC Hostname>"
[Int]$portNumber           = 8143
[String]$username          = "<Domain>\<username>"
[String]$vmServiceCredFile = "<Path and filename to text file containing encrypted password for above user>"    
[String]$templateName      = "<Template VM Name>"
[String]$dataCenterName    = "<Datacentr Name>"
[String]$vFilerHostName    = "<NetApp Hostname>"
[String]$vFilerIPAddress   = "<NetApp IP Address>"
[String]$customizationName = "<Guest Customization to use>"
[String]$vCenterName       = "<FQDN of vCenter Server>"
[String]$protocol          = "https"
[String]$snapInName        = "VMware.VimAutomation.Core"
#'------------------------------------------------------------------------------
#'Provide VSC credentials from an encrypted string in text file.
#'------------------------------------------------------------------------------
[String]$username = "<domain>\<username>"
$vmServiceCreds = get-content $vmServiceCredFile | convertto-securestring
[System.Management.Automation.PSCredential]$vscCredentials = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $vmServiceCreds
#'------------------------------------------------------------------------------
#'Connect to the VSC using the web service API.
#'------------------------------------------------------------------------------
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$True}
[String]$uri = "https://$vscIPAddress`:$portNumber/kamino/public/api?wsdl"
Try{
   [System.Web.Services.Protocols.SoapHttpClientProtocol]$connection = New-WebServiceProxy -uri $uri -Credential $vscCredentials -ErrorAction Stop
   Write-Host "Connected to VSC on IPAddress ""$vscipAddress"" on Port ""$portNumber"""
}Catch{
   Write-Host ("Error """ + $Error[0] + """ Connecting to ""$uri""")
   Break;
}
#'------------------------------------------------------------------------------
#'Create a namespace object from the connection object
#'------------------------------------------------------------------------------
[System.Object]$namespace = $connection.GetType().Namespace
#'------------------------------------------------------------------------------
#'Create a requestspec Object from the NameSpace object
#'------------------------------------------------------------------------------
[System.Object]$requestSpecType = ($namespace + '.requestSpec')
[System.Object]$requestSpec     = New-Object ($requestSpecType)
#'------------------------------------------------------------------------------
#'Convert Encrypted Password to Plain Text and Assign to Variable
#'------------------------------------------------------------------------------
$EncrpytedPassword = get-content $vmServiceCredFile | convertto-securestring
    # Get the plain text version of the password
    $password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($EncrpytedPassword))
#'------------------------------------------------------------------------------
#'Enumerate the username and password from the credential object.
#'------------------------------------------------------------------------------
[String]$domain    = "<Domain>"
[String]$user      = "<Username>"
[String]$username  = "$domain\$user"
#'------------------------------------------------------------------------------
#'Set the properties of the RequestSpec object.
#'------------------------------------------------------------------------------
$requestSpec.serviceUrl = "https://" + $vscHostName + "/sdk"
$requestSpec.vcUser     = $username
$requestSpec.vcPassword = $password
#'------------------------------------------------------------------------------
#'Enumerate the Managed Object Reference of the VMWare Template.
#'------------------------------------------------------------------------------
[System.Object]$templateMoref = $connection.getMoref($templateName, "VirtualMachine", $requestSpec)
# Write-Host "Enumerated Managed Object Reference for ""$templateName"" as ""$templateMoref"""
#'------------------------------------------------------------------------------
#'Enumerate the Managed Object Reference of the VMWare Datacenter.
#'------------------------------------------------------------------------------
[System.Object]$dataCenterMoref = $connection.getMoref($dataCenterName, "Datacenter", $requestSpec)
# Write-Host "Enumerated Managed Object Reference for ""$dataCenterName"" as ""$dataCenterMoref"""
#'------------------------------------------------------------------------------
#'Enumerate the Managed Object Reference of the Datastore.
#'------------------------------------------------------------------------------
[System.Object]$dataStoreMoref = $connection.getMoref($dataStoreName, "Datastore", $requestSpec)
# Write-Host "Enumerated Managed Object Reference for ""$dataStoreName"" as ""$dataStoreMoref"""
#'------------------------------------------------------------------------------
#'Enumerate the VMWare template files.
#'------------------------------------------------------------------------------
# Write-Host "Enumerating files for Virtual Machine Template ""$templateMoref"""
Try{
   $files = $connection.getVMFiles($templateMoref, $requestSpec)
}Catch{
  # Write-Host ("Error """ + $Error[0] + """ Enumerating Virtual Machine Files for ""$templateMoref""")
   Break;
}
#'------------------------------------------------------------------------------
#'Enumerate the VMWare template files.
#'------------------------------------------------------------------------------
Write-Host "Enumerating Virtual Machines deployed from Template ""$templateMoref"""
Try{
   $virtualMachines = $connection.getVMs($templateMoref, $requestSpec)
}Catch{
  # Write-Host ("Error """ + $Error[0] + """ Enumerating Virtual Machines ""$templateMoref""")
   Break;
}
[Array]$vms = @()
ForEach($virtualMachine In $virtualMachines){
   [Array]$vms += $virtualMachine.vmMoref
   # Write-Host $virtualMachine.vmMoref
   }
# ***********************************************************
# ********  Apply Customization to Redeployed VMs     ********
# ***********************************************************

#'Get credentials for vCenter Login.
#'------------------------------------------------------------------------------
$vmServiceCreds = get-content $vmServiceCredFile | convertto-securestring
[System.Management.Automation.PSCredential]$credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $vmServiceCreds
#'------------------------------------------------------------------------------
#'Ensure the VMware PowerShell SnapIn is added.
#'------------------------------------------------------------------------------
Try{
   Add-PSSnapin -Name $snapInName -ErrorAction SilentlyContinue
}Catch{
   Write-Host "The SnapIn ""$snapInName"" is added"
}
#'------------------------------------------------------------------------------
#'Connect to Virtual Center.
#'------------------------------------------------------------------------------
       #'--------------------------------------------------------------------------- 
       #'Bypass SSL certificate confirmation 
       #'--------------------------------------------------------------------------- 
       [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$True} 
Try{
   Connect-VIServer -Server $vCenterName -Protocol $protocol -Credential $credentials -Force -ErrorAction Stop | Out-Null
   Write-Host "Connected to Virtual Center ""$vCenterName"""
}Catch{
   Write-Host "Error Connecting to ""$vCenterName"""
   Break;
}
#'------------------------------------------------------------------------------
#'Get the VMWare Guest Customization.
#'------------------------------------------------------------------------------
Try{
   $customSpec = Get-OSCustomizationSpec -Name $customizationName -ErrorAction Stop
   Write-Host "Enumerated Guest Customization ""$customizationName"""
}Catch{
   Write-Host "Failed Enumerating Guest Customization ""$customizationName"""
   Break;
}
#'------------------------------------------------------------------------------
#'Set the template for each VM (replace ":" with "-"). VSC and vSphere return different ID formats.
#'------------------------------------------------------------------------------
ForEach($vm In $vms){
   $vmId = $vm -Replace(":", "-")
   Do{
      #'------------------------------------------------------------------------
      #'Connect to the Virtual Machine by ID.
      #'------------------------------------------------------------------------
      Try{
         $virtualMachine = Get-VM -Id $vmId -ErrorAction Stop
         Write-Host "Connect to Virtual Machine ""$virtualMachine"""
      }Catch{
         Write-Host "Failed Connecting to Virtual Machine ""$vmId"""
         Break;   
      }
      #'------------------------------------------------------------------------
      #'Set the guest customization for the virtual machine.
      #'------------------------------------------------------------------------
      Try{
         Set-VM -VM $virtualMachine -OSCustomizationSpec $customSpec -Confirm:$False -ErrorAction Stop
         Write-Host "Applied Guest Customization ""$customizationName"" to ""$virtualMachine"""
         Get-VM $virtualMachine | Start-VM
         Write-Host "Power on Virtual Machine: ""$virtualMachine""" 
      }Catch{
         Write-Host "Failed Applying Guest Customization ""$customizationName"" to ""$virtualMachine"""
         Break;
      }
   }Until($True)
}
Write-Host "Done"
#'------------------------------------------------------------------------------

mbeattie
7,022 Views

Hi Scott,

Good to see you've managed to get it working, happy to help.

You might also want to take a look at NetApp OnCommand Workflow Automation available below (it's free)

https://communities.netapp.com/community/products_and_solutions/storage_management_software/workflow-automation

This product is powershell based and can cache credentials so you can avoid using encrypted registry keys or text files.

Cheers Matt

If this post resolved your issue, help others by selecting ACCEPT AS SOLUTION or adding a KUDO.
Public