VMware Solutions Discussions
VMware Solutions Discussions
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.
Solved! See The Solution
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"
#'------------------------------------------------------------------------------
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;
}
#'------------------------------------------------------------------------------
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.
Hi Matt,
I have tested your script and initially it looks like it is working, however I have a couple of anomalies : -
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"
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
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"
#'------------------------------------------------------------------------------
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"
#'------------------------------------------------------------------------------
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)
This product is powershell based and can cache credentials so you can avoid using encrypted registry keys or text files.
Cheers Matt