Raiders of the Lost VMX
One of the more popular post in the VMTN PowerCLI community is this one HowTo search for all VMX files in all datastores and register them into VC ?
The original script that I provided in the thread is now more than one year old and there seem to be some issues with clusters in a vSphere environment.
Time to re-write the script and make it a bit more userfriendly.
Update 1: there was an issue when scanning empty datastores
Update 2: added the option to find and register Templates
Update 3 – April 29th 2010: added the option to ignore specific folders
Update 4 – April 30th 2010: fixed parent datacenter lookup problem + added -checkNFS and -whatif option
Update 5 – May 2nd 2010: fixed a bug with nested folders and the -ignore parameter
Update 6 – August 6th 2010: minor changes to the parameter testing and the Get-Usage function
Update 7 – August 17th 2010: fixed a bug with the -dsNames option. Thanks to goonzie for reporting the bug
This is the latest version of the script
function Register-VMX {
param($entityName = $null,$dsNames = $null,$template = $false,$ignore = $null,$checkNFS = $false,$whatif=$false)
function Get-Usage{
Write-Host "Parameters incorrect" -ForegroundColor red
Write-Host "Register-VMX -entityName -dsNames [,...]"
Write-Host "entityName : a cluster-, datacenter or ESX hostname (not together with -dsNames)"
Write-Host "dsNames : one or more datastorename names (not together with -entityName)"
Write-Host "ignore : names of folders that shouldn't be checked"
Write-Host "template : register guests ($false)or templates ($true) - default : $false"
Write-Host "checkNFS : include NFS datastores - default : $false"
Write-Host "whatif : when $true will only list and not execute - default : $false"
}
if(($entityName -ne $null -and $dsNames -ne $null) -or ($entityName -eq $null -and $dsNames -eq $null)){
Get-Usage
break
}
if($dsNames -eq $null){
switch((Get-Inventory -Name $entityName).GetType().Name.Replace("Wrapper","")){
"Cluster"{
$dsNames = Get-Cluster -Name $entityName | Get-VMHost | Get-Datastore | where {$_.Type -eq "VMFS" -or $checkNFS} | % {$_.Name}
}
"Datacenter"{
$dsNames = Get-Datacenter -Name $entityName | Get-Datastore | where {$_.Type -eq "VMFS" -or $checkNFS} | % {$_.Name}
}
"VMHost"{
$dsNames = Get-VMHost -Name $entityName | Get-Datastore | where {$_.Type -eq "VMFS" -or $checkNFS} | % {$_.Name}
}
Default{
Get-Usage
exit
}
}
}
else{
$dsNames = Get-Datastore -Name $dsNames | where {$_.Type -eq "VMFS" -or $checkNFS} | Select -Unique | % {$_.Name}
}
$dsNames = $dsNames | Sort-Object
$pattern = "*.vmx"
if($template){
$pattern = "*.vmtx"
}
foreach($dsName in $dsNames){
Write-Host "Checking " -NoNewline; Write-Host -ForegroundColor red -BackgroundColor yellow $dsName
$ds = Get-Datastore $dsName | Select -Unique | Get-View
$dsBrowser = Get-View $ds.Browser
$dc = Get-View $ds.Parent
while($dc.MoRef.Type -ne "Datacenter"){
$dc = Get-View $dc.Parent
}
$tgtfolder = Get-View $dc.VmFolder
$esx = Get-View $ds.Host[0].Key
$pool = Get-View (Get-View $esx.Parent).ResourcePool
$vms = @()
foreach($vmImpl in $ds.Vm){
$vm = Get-View $vmImpl
$vms += $vm.Config.Files.VmPathName
}
$datastorepath = "[" + $ds.Name + "]"
$searchspec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchspec.MatchPattern = $pattern
$taskMoRef = $dsBrowser.SearchDatastoreSubFolders_Task($datastorePath, $searchSpec)
$task = Get-View $taskMoRef
while ("running","queued" -contains $task.Info.State){
$task.UpdateViewData("Info.State")
}
$task.UpdateViewData("Info.Result")
foreach ($folder in $task.Info.Result){
if(!($ignore -and (&{$res = $false; $folder.FolderPath.Split("]")[1].Trim(" /").Split("/") | %{$res = $res -or ($ignore -contains $_)}; $res}))){
$found = $FALSE
if($folder.file -ne $null){
foreach($vmx in $vms){
if(($folder.FolderPath + $folder.File[0].Path) -eq $vmx){
$found = $TRUE
}
}
if (-not $found){
if($folder.FolderPath[-1] -ne "/"){$folder.FolderPath += "/"}
$vmx = $folder.FolderPath + $folder.File[0].Path
if($template){
$params = @($vmx,$null,$true,$null,$esx.MoRef)
}
else{
$params = @($vmx,$null,$false,$pool.MoRef,$null)
}
if(!$whatif){
$taskMoRef = $tgtfolder.GetType().GetMethod("RegisterVM_Task").Invoke($tgtfolder, $params)
Write-Host "`t" $vmx "registered"
}
else{
Write-Host "`t" $vmx "registered" -NoNewline; Write-Host -ForegroundColor blue -BackgroundColor white " ==> What If"
}
}
}
}
}
Write-Host "Done"
}
}
# Register-VMX -entityName "MyDatacenter"
# Register-VMX -dsNames "datastore1","datastore2"
# Register-VMX -dsNames "datastore1","datastore2" -template:$true
# Register-VMX -entityName "MyDatacenter" -ignore "SomeFolder"
# Register-VMX -dsNames "datastore3","datastore4" -ignore "SomeFolder" -checkNFS:$true
# Register-VMX -entityName "MyDatacenter" -whatif:$true
Annotations
Line 15: both parameters (entityName and dsNames) can not be used together.
Line 20-36: if an entityName was given the switch will retrieve the list of datastores.
Line 37-39: if dsNames was used add or remove NFS datastores (depending on the checkNFS switch)
Line 42-67: retrieval of the required parameters for the SearchDatastoreSubFolders_Task and RegisterVM_Task methods
Line 56: if the datastore is shared between several ESX/ESXi servers, the script will take the first server
Line 67-69: the VMConfigFileQuery seems to have a problem finding VMX files on some datastores. That’s why the script uses the MatchPattern property.
Line 71-74: this loop waits for the SearchDatastoreSubFolders_Task to finish. To make sure it looks at the actual value of the Result property, the script calls the UpdateViewData method.
Line 77: this line takes care of folders that should be ignored. Update 5: for nested folders, the script checks each folderpath qualifier against the -ignore parameter.
Line 86: when you’re connected to an ESX/ESXi host the FolderPath property doesn’t have a forward slash as the last character. This line corrects that.
Line 88-93: the RegisterVM_Task method needs different parameters if it is called for a guest or for a template
Line 88 & 91: the RegisterVM_Task method should use the DisplayName field from the VMX file for the name of the VM that will be registered. Unfortunately you can’t pass a $null value for a parameter that is defined as a String. This bypasses that limitation.
Line 94-100: these lines take care of the -whatif switch
Line 109-114: some sample calls to the Register-VMX function
Usage
The function Register-VMX can be called as follows
- With the -entityName parameter, where you can pass a datacentername, a clustername or an ESX hostname
- Register-VMX -entityName “MyCluster”
- Register-VMX -entityName “MyDatacenter”
- Register-VMX -entityName “esx1.test.local”
- With the -dsNames parameter, where you can pass one or more datastorenames.
- Register-VMX -dsNames “datastore1″
- Register-VMX -dsNames “datastore1″,”datastore2″,”datastore3″
- With the -template parameter which allows you to specify if you want to register guests ($false) or templates ($true). The default is to register guests.
- Register-VMX -dsNames “datastore1″,”datastore2″,”datastore3″ -template:$true
- Register-VMX -entityName “MyCluster” -template:$false
- With the -ignore parameter which allows you to specify that scripts should not look in specific folders for unregistered VMX files. The default is to look in all folders.
- Register-VMX -entityName “MyCluster” -ignore “Folder1″,”Folder2″
- With the -checkNFS switch which allows you to specify if NFS datastores should be checked as well. The default is to not check NFS datastores.
- Register-VMX -entityName “MyCluster” -CheckNFS:$true
- Register-VMX -dsNames “datastore1″,”datastore2″,”datastore3″ -CheckNFS:$true
- With the -whatif switch set to $true the script will only show the actions and not actually execute them. The default is $false, which means the found .VMX (or .VMTX) files will be registered.
- Register-VMX -entityName “MyCluster” -whatif:$true
I hope this latest version will make it a bit more stable and user friendly.

Is there a similar script to look for unused VMDK’s? I love this script and run it across our infrastructure once a month.
@Michael, I think the script in Orphaned files and folders – Spring cleaning might bring what you are looking for.
@LucD
LucD,
Thank you, i was finally able to get it to work by changing my folder name to not have a space in it, and surrounding it with single quotes. So i used:
$VMFolder = Get-Folder -Name ‘NewVMs’
Thanks again!
Sean
LucD,
Thank you very much for the great script. I have a question, how do you best change the folder the VMs get registered to? I see the value of $tgtfolder is the root DC folder, but I cant figure a way to change it without error. Thank you again for all your great work.
Sean
Hello LucD,
Thank you for the great script. I have one question, what is the proper way to change the folder that registered VMs get placed into? The script by default places them in the root datacenter folder. I tried changing the $tgtfolder to something like this:
$VMFolder = Get-Folder -Name “New VMs”
$tgtfolder = Get-View $VMFolder
but end up with errors like this:
Get-Folder : 10/2/2011 6:22:38 PM Get-Folder Folder with name ‘New VMs’ was not found, using the specified filter(s).
At C:\Scripts\Failover_v1.1.ps1:173 char:25
+ $VMFolder = Get-Folder <<<< -Name "New VMs"
+ CategoryInfo : ObjectNotFound: (:) [Get-Folder], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetFolder
Any help is most appreciated. Thank you again.
Sean
@Sean, the statements you used are correct, but it looks as if the folder “New VMs” doesn’t exist exist on your vCenter.
Hi LucD,
I’m experiencing the same issue described by Mark Hodges, however I find that if I rerun the same command multiple times, all of the VMs do get registered (just in increments of 1 or 2 at a time).
I suspect the issue might be resolved if there were a small pause between each task, to allow the previous VM registration to complete before VC moves on to the next.
While watching vClient as the script runs, I noticed each VM first showing up in inventory with a name similar to the following:
‘sanfs://vm-fs_uuid:/foldername/subfoldername’
This is subsequently changed a moment later with the proper VM name as indicated in the .vmx.
When registering multiple VMs, I see the following error in vClient show up for each VM not successfully registered:
The name ‘sanfs://vm-fs_uuid:’ already exists.
I suspect if there were a small pause to allow the name to be updated from the VMFS path to the VM name before proceeding to the next task, the issue might be resolved.
Hope the extra detail helps – and thanks a million for all your work!
-tom
@Thomas, great find, that is most probably exactly what goes wrong sometimes.
Since the script uses the DisplayName of the VMX file to register the new VM, apparently vCenter first assigns a temporary name and then renames the VM.
The ‘sanfs://vm-fs_uuid’ string is of course not unique when you stay on the same datastore.
I’m going to investigate if I can check the status of the registration task to sequence the tasks.
If that is not possible I will use a timer to wait a couple of seconds (like you suggested).
I’ve been testing this out as part of a datacenter migration with netapp snaps and in testing everything went well, however when testing against a clone of a production volume it only wants to register 2 out of approximately 30 VM’s.
All directory structure is the same and name structure is consistent across all VM’s.
I’ve validated that the vmx looks fine but I cannot get it to register anything but 2/30 vm’s.
No errors occur when running whatif…manually using VIC toregister the VM’s work fine
@Mark, strange never saw that before.
Can you try the following script ?
It will list all the .vmx files that are found on a datastore. Update the $dsName variable before running.
$dsName = "MyDS"
$pattern = "*.vmx"
$ds = Get-Datastore $dsName | Select -Unique | Get-View
$dsBrowser = Get-View $ds.Browser
$searchspec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchspec.MatchPattern = $pattern
$datastorePath = "[" + $dsName + "]"
$taskMoRef = $dsBrowser.SearchDatastoreSubFolders_Task($datastorePath, $searchSpec)
$task = Get-View $taskMoRef
while ("running","queued" -contains $task.Info.State){
$task.UpdateViewData("Info.State")
}
$task.UpdateViewData("Info.Result")
$task.Info.Result | Select @{N="VMX";E={$_.FolderPath + $_.File[0].Path}}
@LucD
Thanks for fast reply and again thanks for the script
Hi LucD, thanks for great script. Unfortunately my knowledge of power shell if far fat from good i have some questions. I have some small test environment (vSphere 4.1 ) with several datacenters. Is it possible to run script for searching specific datastores (i.e. datastore1, datastore2, datastore4 ) but registering VMs in specific datacenter (i.e datacenter-test ) ?
@Drunky, the script registers the discovered guests in the root of the datacenter to which the datastore, where the .VMX file was found, belongs.
If your datastores do not belong to the same datacenter I’m afraid you will have to run the script multiple times.
This script worked for me before.
We recently upgraded VC to 4.1.
Plus the data store I specify in dsNames is only mounted on the first 4 ESX servers out of 20.
Now I get this error:
Get-View : Cannot validate argument on parameter ‘VIObject’. The argument is nu
ll or empty. Supply an argument that is not null or empty and then try the comm
and again.
At V:\Scripts\VMWARE\Alpine\Alpine_Inventory.ps1:70 char:18
+ $dc = Get-View <<<< $dc.Parent
+ CategoryInfo : InvalidData: (:) [Get-View], ParameterBindingVal
idationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutom
ation.ViCore.Cmdlets.Commands.DotNetInterop.GetVIView
@Roger, the script works with vSphere 4.1.
Could it be that your datastores are only NFS mounts ?
In that case you will have to use the -checkNFS switch.
On line 15 I’ve changed the check for “$entityName -eq $null” to “![string]::IsNullOrEmpty($entityName)” based on a couple of parameter exceptions I ran across when calling this script from other scripts that use empty strings instead of $nulls for default string param values.
Thanks for the info Ben.
Very nice script! I just found it and am trying to use it and getting the error below. I am using VC 4.1 and ESX 4.0 U1. I have tried connecting to VC/ESX and tried using the entity and the dsnames seperately and still get the same error. I re-copied and pasted with the icon and still no luck.
Cannot validate argument on parameter ‘Id’. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
At :line:53 char:17
+ $dc = Get-View <<<< $dc.Parent
and the code looks like it copied correctly.
while($dc.MoRef.Type -ne "Datacenter"){
$dc = Get-View $dc.Parent
}
@Brian. Thanks. You should be connected to the vCenter when calling the function.
In the lines where you get the error, the script tries to determine the Datacenter to which the datastore belongs. The list of datastores are either the ones you passed via the -dsNames parameter or the datastores that are know in the entity you passed via the -entityName parameter. You can’t use both of these parameters together in a call.
Perhaps you can give me the line where you called the Register-VMX function and a bit of information on the hierarchy of the environment (datacenter, cluster, host, datastore) ?
You may email the info to lucd(at)lucd(dot)info
LucD,
Thansk for the script . Can you please help me with this :
$dc= Get-View <<<< $dv.Parent
Get-View : The argument cannot be null or empty.
I am trying against a single esxi host , not a VC . do I need to change anything .
Thanks in advance
Amit
@Amit, did something go wrong with the copy ?
That line should say:
$dc = Get-View $ds.ParentI was wondering how I could target VMs and templates in a specific datastore and have them added to a specific Cluster.
Thanks,
Hi CHris, the logic in the script takes the first ESX(i) that has the datastore attached. For that ESX(i) server, the script finds the hidden ‘Resources’ pool. The discovered guests are registered to that poool. If the ESX(i) was part of a cluster, the guest will be located in that cluster.
What exactly do you want to do ? Are your datastores visible/mapped to multiple clusters ?
Luc.
I would like to make a couple of suggestions if I may be bold. Firstly changing the “exit” after the Get-Usage calls to “return” so that the powershell window doesn’t close immediately after displaying the usage. I find very difficult to finishing reading the usage in a few hundredths of a second before the powershell window closes.
In the first “if” statement changing both “-ne” to “-eq” as I believe this is attempting to check that at least one parameter $entityName or $dsNames has actually been passed something, so if both *are* $null then display Get-Usage.
@Herschelle, all suggestions are more than welcome.
I replaced the ‘exit’ with a ‘break’, that way the Register-VMX function will return immediatly.
The ‘if’ statement was meant to capture the situation when both -entityName and -dsNames were passed, which is not permitted.
But your suggestion was indeed useful. I now added a test to see if no parameters are passed.
I hope this makes the function a bit more user-friendly.
@LucD
Thats what I did and when I excute it it still comes up blank, where do I place “Register-VMX -entityName “MyDatacenter”" inside the script? it seems I am asking a total newb question but I can’t seem to get this right,
Thanks,
Alan
At the end of the script you copied there are some lines that are commented out (they start with the # sign).
Remove the hash on one of these lines, and update the line to reflect your environment.
If you want to search all datastores in a datacenter for example you would do:
Register-VMX -entityName "MyDatacenter"If you want to search a specific datastore you would do:
Register-VMX -dsNames "datastore1"The script will display a line on the console (Ex “Checking datastore1″) for each datastore it is investigating.
Could you be alittle more indepth about the copy paste problem? I’m have issues with every way I copy and paste,
Thank you so much for this great script.
Sure can. When you hover your mouse over the form that contains the code, there will be 4 icons that appear at the top right of the form.
Click the 2nd icon from the left and the code will be copied to your clipboard.
Without the line numbers.
Hi LucD,
I’ve seen your name in many forums etc. I’m hoping you can help. I’m having the same problem where I’m running your script on a Vsphere4 environment and it displays nothing and does nothing. I have copied and pasted the code several times as I saw someone else said they’d copied it wrong but I still can’t get it to work and I’m an uber green newbie to all of this so I know its something I did wrong.
Any help would be greatly appreciated
Warm Regards
Mel
@LucD ,
Thank you for making this script, and it appears to be exactly what I am looking for.
But I have been attempting to test in our environment and get no output or anything from the script with an ESX 4 host.
I could use a hand if you have a few moments, I can supply you any version or environment info via email.
Thank you.
After some emails we discovered that it was most probably a copy-paste problem.
@LucD
That worked. Thanks again for all your help!
@LucD
Thanks again. I am still having the problem with vm’s registering in the folder(s) I set to ignore.
Hi Stone, there was indeed a problem with nested folder and the -ignore parameter.
The latest version should have solved that problem.
you totaly ROCK Luc !
@stone
Thanks for the quick update. I used the ◦Register-VMX -entityName “MyCluster” -ignore “Folder1″ line and it only scanned the local storage on my hosts and ignored our NFS storage. Would it be possible to add the -ignore switch to the ◦Register-VMX -dsNames “datastore1″ line? I tried it and it still scanned all folders.
In the previous version the script only checked VMFS datastores. In the latest version I have added the -checkNFS switch, which will allow you to also scan NFS datastores.
The -dsName parameter should now work with the -ignore parameter. Let me know if you still experience a problem with that.
Luc.
Luc, i just got an error on the “$tgtfolder = Get-View $dc.VmFolder”, i guess you should loop the “Get-View $ds.Parent” until the objet got VmFolder properties (my DS are in folders)
There was indeed a bug in that line with some specific configurations. That should be fixed in the new version, the script now loops upwards till it finds a datacenter.
Thanks for finding this “feature”.
Luc.
Luc, you should also provide a “list only” option. I’d a -whatif param
That shouldn’t be too hard to implement.
The latest version has a -whatif option.
Very nice script Luc, i Guess someone like me would also love the “Raiders of the Lost vmdk” version
Hi NiTRo, that’s a good suggestion. It’s on my (long) list
Just remembered, there is already a Find Orphaned VMDK’s script in the Developer: Sample Code community.
Nice Script! Is there away to ignore spacific folders in the datastore? We have a .snapshot folder that needs to be ignored.
Hi Stone. That is indeed a good suggestion.
The script has been updated and now contains an -ignore parameter.
LucD,
if i have a list of VMs in a text file and want to register them, how would I pipe it into your script?
Awesome script. This saved me a lot of work and time. Thanks!
Nice script! Definitely one to keep in my toolbox.
Isn’t the equation “-ne $null” unnecesarry?
if ($entityName -and $dsNames) is the same as if ($entityName -ne $null -and $dsNames -ne $null)
Thanks Arnim.
You are of course right, the “-ne $null” is not really necessary.
But I kept the condition like that for legibility.
Thanks so much!!!
Love it! How about a script to search for templates and register them as well?
Good idea.
In fact I already published a script like that in the PowerCLI community (see unregistered template).
But it should be rather easy to adapt this new version to find and register “lost” templates.
Watch this space