Orphaned files and folders – Spring cleaning
In our PowerCLI book we presented a Delete-Harddisk function in Chapter 7.
One of our readers asked if that function could be used to remove orphaned VMDK files from one or more datastores. Now unfortunately that is not the case since the function we presented in chapter 7 uses the ReconfigVM_Task method to remove the harddisk.
In the PowerCLI Community there are some thread that provide scripts to report on orphaned VMDK files, but most of these are quite old.
So I decided to write a new script that would report on orphaned folders and VMDK files and that would have an option to remove these folders and files.
Update March 19th 2013
The following changes were made to the function
- Fixed the problem where a VMDK in the root of a datastore, which you shouldn’t have of course, would erase the complete datastore content (ouch). Thanks Olivier
- Added WhatIf functionality. You can now check what the Delete switch will do before actually removing files and folders.
- Added details for lone VMDK. See the properties of the returned objects.
Update May 22nd 2011
Two corrections in this new version of the script.
- NiTRo reported a problem with guests that had only a .vmdk, and not their .vmx, file located on the datastore(s) to be cleaned
- The COS became a virtual machine with ESX 4.x and thus has a .vmdk file as well. Although the COS files should be located on a local datastore, the script now ignores COS .vmdk files when it encounters them on the datastore(s) to be cleaned.
The Script
function Remove-OrphanedData {
<#
.SYNOPSIS Remove orphaned folders and VMDK files
.DESCRIPTION The function searches orphaned folders and VMDK files
on one or more datastores and reports its findings.
Optionally the function removes the orphaned folders and VMDK files
.NOTES Author: Luc Dekens
.PARAMETER Datastore
One or more datastores.
The default is to investigate all shared VMFS datastores
.PARAMETER Delete
A switch that indicates if you want to remove the folders
and VMDK files
.EXAMPLE
PS> Remove-OrphanedData -Datastore ds1
.EXAMPLE
PS> Get-Datastore ds* | Remove-OrphanedData
.EXAMPLE
PS> Remove-OrphanedData -Datastore $ds -Delete
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[PSObject[]]$Datastore,
[switch]$Delete
)
begin{
$fldList = @{}
$hdList = @{}
$fileMgr = Get-View FileManager
}
process{
foreach($ds in $Datastore){
if($ds.GetType().Name -eq "String"){
$ds = Get-Datastore -Name $ds
}
if($ds.Type -eq "VMFS" -and $ds.ExtensionData.Summary.MultipleHostAccess){
Get-VM -Datastore $ds | %{
$_.Extensiondata.LayoutEx.File | where{"diskDescriptor","diskExtent" -contains $_.Type} | %{
$fldList[$_.Name.Split('/')[0]] = $_.Name
$hdList[$_.Name] = $_.Name
}
}
Get-Template | where {$_.DatastoreIdList -contains $ds.Id} | %{
$_.Extensiondata.LayoutEx.File | where{"diskDescriptor","diskExtent" -contains $_.Type} | %{
$fldList[$_.Name.Split('/')[0]] = $_.Name
$hdList[$_.Name] = $_.Name
}
}
$dc = $ds.Datacenter.Extensiondata
$flags = New-Object VMware.Vim.FileQueryFlags
$flags.FileSize = $true
$flags.FileType = $true
$disk = New-Object VMware.Vim.VmDiskFileQuery
$disk.details = New-Object VMware.Vim.VmDiskFileQueryFlags
$disk.details.capacityKb = $true
$disk.details.diskExtents = $true
$disk.details.diskType = $true
$disk.details.thin = $true
$searchSpec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchSpec.details = $flags
$searchSpec.Query += $disk
$searchSpec.sortFoldersFirst = $true
$dsBrowser = Get-View $ds.ExtensionData.browser
$rootPath = "[" + $ds.Name + "]"
$searchResult = $dsBrowser.SearchDatastoreSubFolders($rootPath, $searchSpec)
foreach($folder in $searchResult){
if($fldList.ContainsKey($folder.FolderPath.TrimEnd('/'))){
foreach ($file in $folder.File){
if(!$hdList.ContainsKey($folder.FolderPath + $file.Path)){
New-Object PSObject -Property @{
Folder = $folder.FolderPath
Name = $file.Path
Size = $file.FileSize
CapacityKB = $file.CapacityKb
Thin = $file.Thin
Extents = [string]::Join(',',($file.DiskExtents))
}
if($Delete){
If ($PSCmdlet.ShouldProcess(($folder.FolderPath + " " + $file.Path),"Remove VMDK")){
$dsBrowser.DeleteFile($folder.FolderPath + $file.Path)
}
}
}
}
}
elseif($folder.File | where {"cos.vmdk","esxconsole.vmdk" -notcontains $_.Path}){
$folder.File | %{
New-Object PSObject -Property @{
Folder = $folder.FolderPath
Name = $_.Path
Size = $_.FileSize
CapacityKB = $_.CapacityKB
Thin = $_.Thin
Extents = [String]::Join(',',($_.DiskExtents))
}
}
if($Delete){
if($folder.FolderPath -eq $rootPath){
$folder.File | %{
If ($PSCmdlet.ShouldProcess(($folder.FolderPath + " " + $_.Path),"Remove VMDK")){
$dsBrowser.DeleteFile($folder.FolderPath + $_.Path)
}
}
}
else{
If ($PSCmdlet.ShouldProcess($folder.FolderPath,"Remove Folder")){
$fileMgr.DeleteDatastoreFile($folder.FolderPath,$dc.MoRef)
}
}
}
}
}
}
}
}
}
Annotations
Line 22, 91, 110, 116: These handle the WhatIf switch
Line 26: The function accepts datastore by name or as a DatastoreImpl object. Hence the PSObject type. There has to be a Datastore argument !
Line 39-41: Cheap OBN implementation.
Line 42: The test makes sure only VMFS datastores that are shared are handled.
Line 43-48: The function creates a hash table with all the files that are used by the virtual machines on the datastore. The $fldList hash table contains the folders and the $hdList hash table contains all the disk related files (they have the diskDescriptor or the diskExtent type).
Line 49-54: The Get-Template cmdlet doesn’t have a Datastore parameter, that’s why this block uses a different method of determining if there are Templates on a specific datastore.
Line 58-75: The parameters for the SearchDatastoreSubFolders method are prepared and at the end the method is called.
Line 77-105: The returned results from the SearchDatastoreSubFolders method are analysed.
Line 87,103: The Extents property will contain all related filenames of the VMDK file. For example, a file called MyVM.vmdk will have an extent called MyVM-flat.vmdk.
Line 89-93: If the Delete switch is used, the script tests if the WhatIf switch was also specified. If not, the VMDK file is deleted.
Line 109: This condition handles the case when there is an orphaned VMDK file in the root of the datastore.
Line 110: For an orphaned VMDK in the root of the datastore, the DeleteFile method is used.
Line 118: For a folder with orphaned VMDK files, the DeleteDatastoreFile method is used
Sample runs
The function will only scan shared VMFS datastores for orphaned folders and files.
Remove-OrphanedData -Datastore DS1
This call will display all found folders and files and will not delete them.
When you add the Delete switch, the function will return the same information, but the folders and files will be deleted.
Remove-OrphanedData -Datastore DS1 -Delete
When you combine the Delete switch with the WhatIf switch, not files or folders will be deleted. But the function will show what actions will be taken without
You can specify more than one value with the Datastore parameter, the function will investigate all the datastores you specified.
You can pass the datastores by name
Remove-OrphanedData -Datastore "ds1","ds2"
or by object
$ds = Get-Datastore "ds1" Remove-OrphanedData -Datastore $ds
And the function can of course also be used in a pipeline construction
Get-Datastore ds* | Remove-OrphanedData -Delete
Make absolutely sure that you know what you are doing before using the Delete switch.
Use the WhatIf switch before actually deleting anything !
Enjoy !



Hi Luc,
Firstly great work on this and all your other scripts. Is this script meant to detect orphaned folders too? Just trying to work out if its something I’m doing or if it’s just not in the design of the script in its current form.
I have seen Orphaned folders, without VMDKs, not being detected by the script.
Hi,
This is great script, but i’m having trouble modifying it to include more data like: the timestamp of the vmdk file (this would help to know if the vm has not been used for a long time) or the timestamp of the oldest and newest file in the folder where the vmdk was found.
do you have a piece of code that can help with that?
thanks,
ionut
If there is an orphaned vmdk file at the root level (that should never happen …) , the script try to suppress the whole datastore (in this case the parent directory is the datastore itself)
Olivier
Thanks for finding that potential danger in the script.
I’ll add a test to avoid that.
I get this in the report “WARNING: 2 columns do not fit into the display and were removed.” and it cuts off the names of the files. Can anyone help with this?
Does anyone have the same skript rebuild with the vSphere Perl SDK?
@Alex, I don’t know I’m afraid.
But why don’t you raise the question in the VMTN CLI Community ?
@LucD
I realized it was just the folder search portion that was returning the NetApp snapshots and, I don’t need that portion of the script so I commented it out. If you wish to tweak the script for others, the path is typically like this:
.snapshot/nightly.0/VMNAME/
Now that I was getting the results I wanted, I attempted to run the delete as follows:
Remove-OrphanedData -Datastore DATASTORENAME -delete
However, I get the following error:
Exception calling “DeleteFile” with “1″ argument(s): “Cannot delete file [DATASTORENAME] VM/VM-000003.vmdk”
At C:\snapremove\Remove-OrphanedData.ps1:94 char:40
+ $dsBrowser.DeleteFile <<<< ($folder.FolderPath + $file.Path)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Any suggestions?
I’m scanning NFS vols (removed the option for VMFS) and trying to get the script to ignore our NetApp snapshot files in the results. Something like
where ($folder.FolderPath + $file.Path) -notcontains “*snapshot*”
The NetApp snapshot files always start with .snapshot/ in the path
I’m not having much luck despite trying different methods. Any suggestions?
And thanks for all your scripting work!
@MarkD, I’m afraid the script in its current form only does VMFS datastores.
I’ll see if I can include NFS datastores as well.
I ran the script and it only detected orphaned deltas on HBA attached disk. It didn’t detect any of the deltas on NFS (and we have lots of them). Any idea why this would be the case? PS 4.1.1 VC5.0 ESXi5.0U2
Thanks for all your scripting work Luc!
@MarkD, can you give me an example of such a NetApp snapshot file ?
Something like ?
[datastore] .snapshot/VM1/VM1.vmdk
Or is the path different ?
I have tried this with powercli 5.1 and it does nothing. Are you considering to do a new version for powercli 5.0 or 5.1?
Thanks.
@Richard, just did some tests with PowerCLI 5.1 and the function seems to be reporting all the orphaned folders and VMDK files.
Are you sure there are some orphaned folders or VMDK files on the datastore you ran the function against ?
Perhaps describe how you tested so I can replay that in my test environment.
Hey Christian,One thing to make not of thoguh. Even thoguh you can upgrade your VMFS3 datastores to VMFS5 it will retain the existing blocksize of the VMFS3 datastore. This becomes a problem if you want to do Storage vMotions across datastores with different block sizes as you’ll see a performance hit. Also this really becomes a problem if you mix datastores with different block sizes inside of a Storage DRS Cluster. Just something to keep in mind. It might be a good idea once the upgrade of VMFS3 to VMFS5 is complete to create a new VMFS5 datastore and migrate all your VMs off of the upgraded VMFS datastore. Thanks for the great article!!
Hi Luc, script works great. Is there a parameter show size in MB or GB and export search results to CSV so I can get it by email? Kind regards
Cheers Luc, where will the Delete parameter be added in your script, line 003 is it? Is it added already?@LucD
@Nicky, the Delete switch is already in the function.
When you call the function, you can specify the Delete switch, in that case the files will actually be removed. If you do not specify the Delete switch, the function will only list the orphaned files and folders.
The Sample runs section has examples.
Can you please tell me how to run this from client machine? I have Vcenter in different machine and connecting that by vcenter client so how to run this script from client machine and delete all unused cmx/vmdk/ files.
Hi, there are several ways you can do this.
One of the simplest is as follows:
. ./yourfile.ps1Remove-OrphanedDataI hope this helps you to use the function.
Hey Luc, I just wanted to display the orphaned folders or disks but don’t want to delete them? Is it possible? Cheers!
Hi NIcky, yes, if you don’t add the Delete parameter, the function will just display the orphaned files and folders. See also the Sample Runs section at the end of the post.
@LucD Good point, I agree.
The lines:
if($ds.GetType().Name -eq “String”){
$ds = Get-Datastore -Name $ds
}
should come before:
foreach($ds in $Datastore){
because it is possible for datastores in an enterprise to have the same name, thus this will assign a collection of objects instead of a single object and the rest of the loop won’t handle it properly.
@Rick, you have a point there. The current version of the script doesn’t handle datastores with the same name.
But I think a better solution would be to do
if(!$Datastore){
$Datastore = Get-Datastore
}
else{
$Datastore = $Datastore | %{
if($_.GetType().Name -eq "string"){
Get-Datastore -Name $_
}
else{
$_
}
}
}
foreach($ds in $Datastore){
if($ds.Type -eq "VMFS" -and $ds.ExtensionData.Summary.MultipleHostAccess){
...
That way you can pass names, but also Datastore objects through the Datastore parameter.
Let me know if that works for you, and I will update the script.
@LucD
Does seem strange, as no one else has said the same, it did delete folders, but it deleted folders that contained files, not the empty folders, I thought this script would delete empty (apart from phd) folders, and leave the folders that still contained virtual machines…
@Karl, so you’re saying that the function deleted a folder that still had active VMDK files in there ?
Or where there other files in the folder ? Could you tell me what type ?
@LucD
Hey, Thanks LucD, I got the script working, though, maybe I am missunderstood, but it didn’t go and delete all the empty folders from my datastore, it deleted files from folders that still contained files? (luckly I didn’t need them) I thought it would removed the folder and the phd folder that was left after a machine deletion, guess I was wrong.
thanks
@Karl, strange. The function should also remove the folders.
Hi, I am trying to use your script, to remove empty folders on my datastores, when I am trying to run the script via PowerShell (running command ‘Remove-OrphanedData’ i get the error -
The term ‘Remove-OrphanedData’ is not recognized as the name of a cmdlet, function, script file, or check the spelling of the name, or if a path was included, verify that the path is correct and try again
At line:1 char:20
+ Remove-OrphanedData <<<<
+ CategoryInfo : ObjectNotFound: (Remove-OrphanedData:String) [], CommandNotFoundExcept
+ FullyQualifiedErrorId : CommandNotFoundException
any idea what I am doing wrong? thanks
Hi Karl, before calling a function, you have to make the function known to PowerShell. You can save the function in a .ps1 file (for example orphan.ps1) and then dot-source the file.
. .\orphan.ps1
Note that there is a blank between the 2 dots.
Now the function is known to your PowerShell session and you can call it by it’s name.
.Remove-OrphanedData
Hi LucD,
Just an observation – I’ve got 3 vCenters which all use the same SAN NFS shares. When querying one vCenter, the folders relating to the other 2 vCenters will appear to have orphaned disks (as they obviously aren’t registered on the first vCenter).
I’m new to PowerCLI but I guess a way around this would be to use arrays or objects and make comparisons between the 3 vCenters – as I’m new, I might just start off with a manual reconciliation.
Thanks for the script!
Hi Luc,
Trying to run the script on vSphere5 vCenter, getting this error:
The script ‘orphaned_files.ps1′ cannot be run because the following snap-ins th
at are specified by the “#requires” statements of the script are missing: VMwar
e.VimAutomation.Core (Version 4.1).
> Get-PSSnapin vmware.vimautomation.core | select Name,version
Name Version
—- ——-
VMware.VimAutomation.Core 5.0.0.0
Suggestions?
Grtz
Roderick
@Roderick, that is a problem with the #requires line.
It allows you to specify the exact version of the snapin, but not this version or higher.
You can safely take out the #requires line at the top.
Hu Luc
How would I change this to produce a report, maybe in CSV format ?
And can I assume that this will not detect snapshot VMDK files as orphans ?
Cheers
Simon
Can someone post the initial part of this script? There must be connection strings etc that is missing here.
Thanks
Mark
@Mark, you only need to connect to your vCenter and then call the function.
Connect-VIServer -Server your-vCenter -User your-account -Password your-password
You can use the account with which you are logged, provided that account has the required permissions.
Connect-VIServer -Server your-vCenter
@sysadmin
Thanks LucD , Its works well now.
Thanks a lot… it really helped a lot.
@LucD
Thanks Luc for your assistance , I am running with old version
Name Version
—- ——-
VMware.VimAutomation.Core 4.0.1.0
Will get it corrected now.
@LucD
Thanks Luc for your quick response…. when I am running
” Add-PSSnapin VMware.VimAutomation.Core ” its giving me below mentioned error.
PS P:\> Add-PSSnapin VMware.VimAutomation.Core
Add-PSSnapin : Cannot add Windows PowerShell snap-in VMware.VimAutomation.Core because it is already added. Verify the name of the snap-in and try again.
At line:1 char:13
+ Add-PSSnapin <<<< VMware.VimAutomation.Core
+ CategoryInfo : InvalidArgument: (VMware.VimAutomation.Core:String) [Add-PSSnapin], PSArgumentException
+ FullyQualifiedErrorId : AddPSSnapInRead,Microsoft.PowerShell.Commands.AddPSSnapinCommand
Please assist….
@sysadmin, it looks as if you’re using an older PowerCLI version.
You can check with
Get-PSSnapin vmware.vimautomation.core | select Name,version
The #requires line at the beginning of the script says it need at least PowerCLI 4.1. The reason is that the script uses some features that were introduced in PowerCLI 4.1
Hi Luc ,
Sorry I am new to scripting world , how should I run this script ?
I am trying to call this function , everytime I am getting below error. Please help
+ . <<<< 'C:\Documents and Settings\ankitjain\Desktop\PS Script\orphan-disk.ps1'
+ CategoryInfo : ResourceUnavailable: (orphan-disk.ps1:String) [], ScriptRequiresException
+ FullyQualifiedErrorId : ScriptRequiresMissingPSSnapIns
The script 'orphan-disk.ps1' cannot be run because the following snap-ins that are specified by the "#requires" statements of the script are missing: VMware
.VimAutomation.Core (Version 4.1).
At line:1 char:2
+ . <<<< 'C:\Documents and Settings\—-\Desktop\PS Script\orphan-disk.ps1'
+ CategoryInfo : ResourceUnavailable: (orphan-disk.ps1:String) [], ScriptRequiresException
+ FullyQualifiedErrorId : ScriptRequiresMissingPSSnapIns
@sysadmin, it looks like the PowerCLI snapin wasn’t loaded when you executed the script.
Do a
Add-PSSnapin VMware.VimAutomation.Core
first.
Running this returned some good hits, but also a multitude of false positives. A few examples are similar to the ones below. The script output was the following. But a quick look in SSH shows the truth. Any idea why?
Name :
CapacityKB :
Thin :
Extents :
Folder : [ARRAY 02381 - LUN 25] VM-Disks/ICSS/WPQMA310/
/vmfs/volumes/498ae5a6-b8d1bed9-5115-00215ab17094/VM-Disks # cd ICSS/
/vmfs/volumes/498ae5a6-b8d1bed9-5115-00215ab17094/VM-Disks/ICSS # ls
WPQMA310 WPQMD310 WPQMW310
/vmfs/volumes/498ae5a6-b8d1bed9-5115-00215ab17094/VM-Disks/ICSS # cd WPQMA310
/vmfs/volumes/498ae5a6-b8d1bed9-5115-00215ab17094/VM-Disks/ICSS/WPQMA310 # ls -l
-rw-r–r– 1 root root 37 Jun 16 16:38 WPQMA310-155e0bfd.hlog
-rw——- 1 root root 2147483648 Apr 14 01:14 WPQMA310-155e0bfd.vswp
-rw——- 1 root root 171798691840 Jun 18 07:12 WPQMA310-flat.vmdk
-rw——- 1 root root 8684 May 13 01:51 WPQMA310.nvram
-rw——- 1 root root 503 Oct 28 2010 WPQMA310.vmdk
-rw-r–r– 1 root root 0 Feb 10 01:09 WPQMA310.vmsd
-rwxr-xr-x 1 root root 4600 Jun 15 17:50 WPQMA310.vmx
-rw-r–r– 1 root root 1572 Jun 15 17:50 WPQMA310.vmxf
-rw——- 1 root root 214748364800 Jun 18 05:32 WPQMA310_1-flat.vmdk
-rw——- 1 root root 505 Oct 28 2010 WPQMA310_1.vmdk
-rw——- 1 root root 12884901888 Jun 18 07:13 WPQMA310_2-flat.vmdk
-rw——- 1 root root 526 Feb 10 01:10 WPQMA310_2.vmdk
-rw——- 1 root root 8589934592 Mar 21 19:57 WPQMA310_3-flat.vmdk
-rw——- 1 root root 471 Mar 21 19:57 WPQMA310_3.vmdk
-rw-r–r– 1 root root 170612 May 27 06:35 vmware-92.log
-rw-r–r– 1 root root 170730 May 27 22:35 vmware-93.log
-rw-r–r– 1 root root 170711 May 27 22:37 vmware-94.log
-rw-r–r– 1 root root 170736 Jun 10 17:12 vmware-95.log
-rw-r–r– 1 root root 170427 Jun 10 17:12 vmware-96.log
-rw-r–r– 1 root root 192775 Jun 16 16:38 vmware-97.log
-rw-r–r– 1 root root 190043 Jun 17 00:31 vmware.log
@Nicholas, when the script only returns a folder, that means that the script didn’t find a registered VM that points to that folder.
You can have a look inside the .vmx file, find the Display value and check if the name is a registered VM.
Awesome script… Luc,
However, when run this script, its not pulling up all data, as expected … the following paramaters are blank…whenever i run this script…
Name :
CapacityKB :
Thin :
Extents :
Size :
But its identifing folder name correctly…. Need your help…Luc
@M, that happens when the script encounters folder that is not related to any known guest at all. This can be caused by a remove of a guest with the “Remove from Inventory” option. In that case the guest is removed from vCenter but the folder and files are kept on the datastore.
@LucD
Thanks Luc, it works very nice indeed
Very nice piece of script !
@LucD
Thanks luc, now the code annotations is wrong (bad line numbers)
Is there a specific limitation to VMFS or it could be extend to nfs ?
@NiTRo, the line numbers got confused after the copy/paste of the script. That should be fixed now.
There is no specific reason why it would only support VMFS datastores. The reason I put the test in line 47, is because I only have guests on VMFS datastores and as such I couldn’t test the proper functioning of the script against NFS datastores.
If you change line 47 from
if($ds.Type -eq "VMFS" -and $ds.ExtensionData.Summary.MultipleHostAccess){
into
if($ds.ExtensionData.Summary.MultipleHostAccess){
you should be able to test this.
Don’t use the Delete switch before you are sure you get the correct files and folders back from the script.
Let me know if this works for NFS datastores and I will update the script to make it more general.
Something’s wrong for me Luc, it reports a folder from an existing vm. the home datastore of this vm is located on an nfs datastore an the reported folder contains a vmdk atached to this vm but located on a vmfs datastore. you can easily reproduce that issue.
Thanks for finding this bug.
I published a new version on May 22nd 2011, and I think this solved the specific problem you have seen.
Fantastic work, Luc! Yes, as you said most of the orphaned vmdk file reporting scripts found in net were old. And i was the user who asked this, not sure whether only me