build 980262fe | content blog-content@c8490fa · 338 posts | profiles 20 · corpus 208 | 0 skipped |
Scripting · 2017-07-25

Find Zombie Files on Datastores

Everyone who works in our industry is familiar with RVTools. It's a great tool that achieves a lot in short time in terms of infrastructure checks. I would not want to do my job without it.

2017-07-25Date
Dario DörflingerAuthor
3Min read
661words
Topics capabilities · idf weight Automation automation 1.80
Vendors vendors · idf weight VMware vmware 0.91

This post is from 2017. It stays online because people still look for it, but it describes the products as they were then.

Everyone who works in our industry is familiar with RVTools. It’s a great tool that achieves a lot in short time in terms of infrastructure checks. I would not want to do my job without it.

One customer wanted to have a e-mail report that showed the “zombie” files on their datastores. After a RVTool check they saw that they wasted a lot of space for not removed snapshot delta disks that were not removed by their backup solution. This is a problem we see in almost every environment.

So we created a script that would report the files that are not associated with the running VMs.

The script takes a long time to check a large environment, so be patient when you use it. Thanks to Luc from the PowerCLI community for helping us get this function a little faster. (http://www.lucd.info/2016/09/13/orphaned-files-revisited/)

Update: this script is now published at GitHub.

The Script

##################################################################################
# Script:           RmOrphanedFiles.ps1
# Datum:            25.07.2017
# Version:          2.1
# History:          Added Comments
#                   Replaced Add-PSSnapin with Module Command
#                   Replaced Get-VmwOrphan Function
##################################################################################

[CmdletBinding(SupportsShouldProcess=$true)]
Param(
  [parameter()]
  [String]$vCenter = "virtualfrogvc.virtual.frog",
  # Change to a SMTP server in your environment
  [string]$SmtpHost = "mail.virtual.frog",
# Change to default email address you want emails to be coming from
    [string]$From = "virtualFrog@virtual.frog",
# Change to default email address you would like to receive emails
    [Array]$To = @("email@email.com","email2@email.com"),
# Change to default Report Filename you like
    [string]$Attachment = "$env:temp\OrphanedFileReport-"+(Get-Date -f "yyyy-MM-dd")+".csv"
)

function Get-VmwOrphan{
<#
.EXAMPLE
  PS> Get-VmwOrphaned -Datastore DS1
.EXAMPLE
  PS> Get-Datastore -Name DS* | Get-VmwOrphaned
#>

  [CmdletBinding()]
  param(
    [parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject[]]$Datastore
  )

  Begin{
    $flags = New-Object VMware.Vim.FileQueryFlags
    $flags.FileOwner = $true
    $flags.FileSize = $true
    $flags.FileType = $true
    $flags.Modification = $true

    $qFloppy = New-Object VMware.Vim.FloppyImageFileQuery
    $qFolder = New-Object VMware.Vim.FolderFileQuery
    $qISO = New-Object VMware.Vim.IsoImageFileQuery
    $qConfig = New-Object VMware.Vim.VmConfigFileQuery
    $qConfig.Details = New-Object VMware.Vim.VmConfigFileQueryFlags
    $qConfig.Details.ConfigVersion = $true
    $qTemplate = New-Object VMware.Vim.TemplateConfigFileQuery
    $qTemplate.Details = New-Object VMware.Vim.VmConfigFileQueryFlags
    $qTemplate.Details.ConfigVersion = $true
    $qDisk = New-Object VMware.Vim.VmDiskFileQuery
    $qDisk.Details = New-Object VMware.Vim.VmDiskFileQueryFlags
    $qDisk.Details.CapacityKB = $true
    $qDisk.Details.DiskExtents = $true
    $qDisk.Details.DiskType = $true
    $qDisk.Details.HardwareVersion = $true
    $qDisk.Details.Thin = $true
    $qLog = New-Object VMware.Vim.VmLogFileQuery
    $qRAM = New-Object VMware.Vim.VmNvramFileQuery
    $qSnap = New-Object VMware.Vim.VmSnapshotFileQuery

    $searchSpec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
    $searchSpec.details = $flags
    $searchSpec.Query = $qFloppy,$qFolder,$qISO,$qConfig,$qTemplate,$qDisk,$qLog,$qRAM,$qSnap
    $searchSpec.sortFoldersFirst = $true
  }

  Process{
    foreach($ds in $Datastore){
      if($ds.GetType().Name -eq "String"){
        $ds = Get-Datastore -Name $ds
        Write-Host "Checking Datastore $ds"
      }

# Only shared VMFS datastore
      if($ds.Type -eq "VMFS" -and $ds.ExtensionData.Summary.MultipleHostAccess){
        Write-Verbose -Message "$(Get-Date)`t$((Get-PSCallStack)[0].Command)`tLooking at $($ds.Name)"

# Define file DB
        $fileTab = @{}

# Get datastore files
        $dsBrowser = Get-View -Id $ds.ExtensionData.browser
        $rootPath = "[" + $ds.Name + "]"
        $searchResult = $dsBrowser.SearchDatastoreSubFolders($rootPath, $searchSpec) | Sort-Object -Property {$_.FolderPath.Length}
        foreach($folder in $searchResult){
          foreach ($file in $folder.File){
            $key = "$($folder.FolderPath)$(if($folder.FolderPath[-1] -eq ']'){' '})$($file.Path)"
            $fileTab.Add($key,$file)

            $folderKey = "$($folder.FolderPath.TrimEnd('/'))"
            if($fileTab.ContainsKey($folderKey)){
              $fileTab.Remove($folderKey)
            }
          }
        }

# Get VM inventory
        Get-VM -Datastore $ds | %{
          $_.ExtensionData.LayoutEx.File | %{
            if($fileTab.ContainsKey($_.Name)){
              $fileTab.Remove($_.Name)
            }
          }
        }

# Get Template inventory
        Get-Template | where {$_.DatastoreIdList -contains $ds.Id} | %{
          $_.ExtensionData.LayoutEx.File | %{
            if($fileTab.ContainsKey($_.Name)){
              $fileTab.Remove($_.Name)
            }
          }
        }

# Remove system files & folders from list
        $systemFiles = $fileTab.Keys | where{$_ -match "] \.|vmkdump"}
        $systemFiles | %{
          $fileTab.Remove($_)
        }

# Organise remaining files
        if($fileTab.Count){
          $fileTab.GetEnumerator() | %{
            $obj = [ordered]@{
              Name = $_.Value.Path
              Folder = $_.Name
              Size = $_.Value.FileSize
              CapacityKB = $_.Value.CapacityKb
              Modification = $_.Value.Modification
              Owner = $_.Value.Owner
              Thin = $_.Value.Thin
              Extents = $_.Value.DiskExtents -join ','
              DiskType = $_.Value.DiskType
              HWVersion = $_.Value.HardwareVersion
            }
            New-Object PSObject -Property $obj
          }
          Write-Verbose -Message "$(Get-Date)`t$((Get-PSCallStack)[0].Command)`tFound orphaned files on $($ds.Name)!"
        }
        else{
          Write-Verbose -Message "$(Get-Date)`t$((Get-PSCallStack)[0].Command)`tNo orphaned files found on $($ds.Name)."
        }
      }
    }
  }
}

Import-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue | Out-Null
Connect-VIServer $vCenter -WarningAction SilentlyContinue | Out-Null
Write-Host "Connected to $vCenter. Starting script"

$bodyh = (Get-Date -f "yyyy-MM-dd HH:mm:ss") + "  -  the following orphaned files were found on Datastores. `n"
$body = Get-Datastore | Get-VmwOrphan
$body | Export-Csv "$Attachment" -NoTypeInformation -UseCulture
$body = $bodyh + ($body | Out-String)
$subject = "Report - orphaned Files on Datastores for $vCenter"
send-mailmessage -from "$from" -to $to -subject "$subject" -body "$body" -Attachment "$Attachment" -smtpServer $SmtpHost
Disconnect-VIServer -Server $vCenter -Force:$true -Confirm:$false

Comments

7 comments from the previous blog. We no longer take new ones here.

Keith Bush 2020-03-31

Hello, I was interested in the PowerCLI: Find Zombie Files on Datastores. I did not find it on Gitgub. Has there been any updates to the script?

virtualFrog 2020-04-01

Hi Keith, Yes the script is on Github but I’ve renamed it to follow Powershell Best-Practices (Get-Verb). It is now called “Get-Oprhaned-Files.ps1” (https://github.com/virtualFrog/PowerCLI-Scripts/blob/master/Get-Orphaned-Files.ps1)

heal580 2020-10-27

Hi, Im getting error below, am i missing something? Exception calling “Add” with “2” argument(s): “Item has already been added. Key in dictionary: ‘[TEMPLATE-AFA-LUN] scratch/log/VP-ESX212.ANADOLU.COM/sdrsinjector.log’ Key being added: ‘[TEMPLATE-AFA-LUN] scratch/log/vp-esx212.anadolu.com/sdrsinjector.log'” At C:\Users\mk015369adm\Desktop\orphane.ps1:88 char:13 + $fileTab.Add($key,$file) + ~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : ArgumentException

virtualFrog 2021-01-28

Hi, Sorry for my late reply. I think this is a Bug in the script that I haven’t encountered. It would seem that the script has a problem when identical files are found from different scratch directories. I would suggest you open an Issue on Github.

Karl 2021-07-23

Hi there! Awesome script so far, I just tried it out in my lab and it works just fine! One thing I’m struggling with is adding a user and password into the script. I want to let it run on a schedule. I’ve got a read-only user for that and running the script with the prompt for username and password works. But the script tells me that that it cannot complete the login due to an incorrect user name or password when I integrate the credentials into the script with variables. Maybe I did it wrong: [String]$viuser = “readonly_user”, [String]$vipasswd = “password”, Connect-VIServer $vCenter -User $viuser -Password $vipasswd Many thanks! Cheers, Karl

virtualFrog 2021-07-23

Hey Karl! Awesome that you can make use of this script. Having scripts run unattended is something that I deal with quite regularly. I usually end up using the “VICredentialStoreItem” which is a secure way of saving your credentials for a script to use. However you need to be careful because the access rights are tricky. You’ll need to create the object as the user that will end up triggering the script otherwise you’ll get the same error message you mentioned (the object will be empty basically, but no access denied is thrown). I’d do something like this: New-VICredentialStoreItem -Host -User -Password -File The host is actually not relevant when you’re using it later on but still required. In the script you would then do it like this: $credStore = Get-VICredentialStoreItem -File And use the dot syntax to connect: Connect-VIServer $vCenter -User $credStore.User -Password $credStore.Password -Confirm:$false Hope this helps!

Karl 2021-07-23

Hi there! I just solved it this very moment 🙂 The password contained a @ sign. Therefore, the regular ” ” didn’t work. I had to use ‘ ‘ for that string containing the password.

You might also like