PowerCLI

 View Only
Expand all | Collapse all

Create a report of all unregistered VMs

  • 1.  Create a report of all unregistered VMs

    Posted Dec 10, 2024 09:39 AM

    Hello,

    I have found a large number of VMs which were not properly deleted from inventory and have instead been removed from inventory leaving behind many TB's of VMDKs. I would like to re-register these VMs (eventually) so that they can be put through our proper decom process.

    I've found a few scripts to play around with, but many of these seem to focus on re-registering the VMs, when there are a large number of VMs I need to ensure are NOT re-registered, such as replicated VMs that should not be turned on.

    For this reason I'm looking for a script I can run which simply focuses on generating a list of unregistered VMs at this point, along with relevant information such as the path to the VMX file, the last time the disk was modified. Once I've been able to generate this list in a CSV file I plan to exclude all the VMs until I get a list of only the VMs I want to re-register, then I'll import the CSV into another script and register only those VMs.

    I've been playing around with this:

    Get-Datastore -name IOMEGA | ForEach-Object { Get-ChildItem -Path $_.Datastore -Recurse | Where-Object { $_.Extension -eq ".vmx" -and ! (Get-VM -Name $_.BaseName -ErrorAction SilentlyContinue) }} | Select-Object *

    However this isn't even working on the datastore I'm specifying (IOMEGA), instead I'm getting results from my local laptop (I don't even understand how it's finding these). Sample output from this command here:

    PSPath            : Microsoft.PowerShell.Core\FileSystem::C:\Users\dbutc\Documents\Virtual Machines\nest7\nest7.vmx
    PSParentPath      : Microsoft.PowerShell.Core\FileSystem::C:\Users\dbutc\Documents\Virtual Machines\nest7
    PSChildName       : nest7.vmx
    PSDrive           : C
    PSProvider        : Microsoft.PowerShell.Core\FileSystem
    PSIsContainer     : False
    Mode              : -a----
    VersionInfo       : File:             C:\Users\dbutc\Documents\Virtual Machines\nest7\nest7.vmx
                        InternalName:     
                        OriginalFilename: 
                        FileVersion:      
                        FileDescription:  
                        Product:          
                        ProductVersion:   
                        Debug:            False
                        Patched:          False
                        PreRelease:       False
                        PrivateBuild:     False
                        SpecialBuild:     False
                        Language:         
                        
    BaseName          : nest7
    Target            : {}
    LinkType          : 
    Name              : nest7.vmx
    Length            : 2761
    DirectoryName     : C:\Users\dbutc\Documents\Virtual Machines\nest7
    Directory         : C:\Users\dbutc\Documents\Virtual Machines\nest7
    IsReadOnly        : False
    Exists            : True
    FullName          : C:\Users\dbutc\Documents\Virtual Machines\nest7\nest7.vmx
    Extension         : .vmx
    CreationTime      : 2020-12-29 4:26:55 PM
    CreationTimeUtc   : 2020-12-29 9:26:55 PM
    LastAccessTime    : 2024-12-09 1:28:25 PM
    LastAccessTimeUtc : 2024-12-09 6:28:25 PM
    LastWriteTime     : 2020-12-29 4:26:55 PM
    LastWriteTimeUtc  : 2020-12-29 9:26:55 PM
    Attributes        : Archive


    How is it finding a result from my C: when I'm specifying at datastore presented to my esxi hosts?? Can this be refined to get me the information I'm looking for?



  • 2.  RE: Create a report of all unregistered VMs

    Posted Dec 11, 2024 07:15 AM

    Hi.

    You may want to download RVTools from robware.net and use this.  It has a CLI.  Here it is running in a scheduled task each day to provide us information.




  • 3.  RE: Create a report of all unregistered VMs

    Posted Dec 12, 2024 02:34 PM

    I've looked through RVtools and found a great tab for possible zombie vmdk's under the vHealth tab, however I have found nothing about unregistered VMs. Is there a specific place I can check or report I can run? I'm only interested in VMs that can be re-registered, I have a separate report which contains orphaned VMDKs. 




  • 4.  RE: Create a report of all unregistered VMs

    Posted Dec 12, 2024 02:45 PM

    There is a flaw in your code.
    The object returned by Get-Datastore does not have a Datastore property.
    So the Get-ChildItem is starting from an empty string, which explains why you see that local file.

    You might try something like this

    $ds = Get-Datastore -Name IOMEGA
    
    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    Remove-PSDrive -Name TgtDS
    





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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 5.  RE: Create a report of all unregistered VMs

    Posted Dec 12, 2024 05:05 PM

    Thanks LucD,

    This is getting me a complete list of all VMs, both registered and unregistered on the datastore. Can I use this to compare to a list of registered VMs and only give results for VMs it cannot find? I'm not sure how to trim the .vmx in the .name field below, after that I can add an if / else statement and only return results with Name,DatastoreFullPath,LastWriteTime

    $ds = Get-Datastore -Name vsanDatastore
    $VMs = Get-VM

    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    foreach ($VMX in $VMXS) { 
        Get-VM -name $VMX.name(how do I trim the .vmx here?)
        }
    Remove-PSDrive -Name TgtDS






  • 6.  RE: Create a report of all unregistered VMs

    Posted Dec 12, 2024 05:17 PM

    You could do

    Get-VM -name $VMX.name.replace('.vmx','')


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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 7.  RE: Create a report of all unregistered VMs

    Posted Dec 13, 2024 09:03 AM
    Edited by LucD Dec 13, 2024 10:09 AM

    Thanks LucD,

    Not sure if my approach is the best way, but it's working to some degree:

    $ds = Get-Datastore -Name vsanDatastore
    $VMs = Get-VM
    $ErrorActionPreference = "Stop"

    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    foreach ($VMX in $VMXS) {
        try {
            Get-VM -name $VMX.name.replace('.vmx','') -ErrorAction:Stop
            } 
            catch {
            $unregistered += $VMX.Name 
            }
        }
    Write-Host $unregistered
    Remove-PSDrive -Name TgtDS

    The results look like this:

    Name                 PowerState Num CPUs MemoryGB       <------------This is a list of registered VMs, I don't want these at all
    ----                 ---------- -------- --------       
    passthrough          PoweredOn  2        16.000         
    Skyline              PoweredOff 2        4.000          
    LogInsight           PoweredOn  2        8.000          
    vROPs                PoweredOn  2        8.000          
    torrent              PoweredOn  2        16.000         
    unregistered_VSAN2.vmxvc7b.vmxvc7a.vmxDeletemetest1.vmxunregistered_VSAN3.vmxunregistered_VSAN1.vmx    <------- These are correct, however the formatting of the out is unreadable and I would like to add DatastoreFullPath,LastWriteTime to the results. Any chance you could give me a push in the right direction?












  • 8.  RE: Create a report of all unregistered VMs

    Posted Dec 13, 2024 09:07 AM

    That also shows the VMs that are registered.
    Try something like this

    $ds = Get-Datastore -Name vsanDatastore
    $VMs = Get-VM
    #$ErrorActionPreference = "Stop"
    $unregistered = @()
    
    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    foreach ($VMX in $VMXS) {
        try {
            Get-VM -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
            } 
        catch {
            $unregistered += $VMX.Name 
        }
    }
    Remove-PSDrive -Name TgtDS
    
    $unregistered
    


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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 9.  RE: Create a report of all unregistered VMs

    Posted Dec 13, 2024 10:10 AM

    This is almost perfect:

    $ds = Get-Datastore -Name vsanDatastore
    $VMs = Get-VM
    #$ErrorActionPreference = "Stop"
    $unregistered = @()
    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    foreach ($VMX in $VMXS) {
        try {
            Get-VM -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
            } 
        catch {
            $unregistered += $VMX.Name 
        }
    }
    Remove-PSDrive -Name TgtDS
    $unregistered


        I just need to format this data before exporting it to csv, I'm believe I need to add something like this, but I'm not sure where:
    ForEach-Object {
            [PSCustomObject] @{
            Name = $vmx.Name
            DatastoreFullPath = $vmx.DatastoreFullPath
            LastWriteTime = $vmx.LastWriteTime
    }
    }





  • 10.  RE: Create a report of all unregistered VMs
    Best Answer

    Posted Dec 13, 2024 10:15 AM

    Something like this

    $ds = Get-Datastore -Name vsanDatastore
    $VMs = Get-VM
    #$ErrorActionPreference = "Stop"
    $unregistered = @()
    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    foreach ($VMX in $VMXS) {
        try {
            Get-VM -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
            } 
        catch {
            $unregistered += PSCustomObject] @{
                Name = $vmx.Name
                DatastoreFullPath = $vmx.DatastoreFullPath
                LastWriteTime = $vmx.LastWriteTime
        }
        }
    }
    Remove-PSDrive -Name TgtDS
    
    $unregistered | Export-Csv -Path .\report.csv -NoTypeInformation
    


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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 11.  RE: Create a report of all unregistered VMs

    Posted Dec 13, 2024 03:31 PM

    One minor typo (missing [ in [PSCustomObject]

    This is working perfectly, thanks LucD!

    $ds = Get-Datastore -Name vsanDatastore
    $VMs = Get-VM
    #$ErrorActionPreference = "Stop"
    $unregistered = @()
    New-PSDrive -Name TgtDS -Location $ds -PSProvider VimDatastore -Root '\' | Out-Null
    $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx
    foreach ($VMX in $VMXS) {
        try {
            Get-VM -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
            } 
        catch {
            $unregistered += [PSCustomObject] @{
                Name = $vmx.Name
                DatastoreFullPath = $vmx.DatastoreFullPath
                LastWriteTime = $vmx.LastWriteTime
        }
        }
    }
    Remove-PSDrive -Name TgtDS

    $unregistered # | Export-Csv -Path .\report.csv -NoTypeInformation




  • 12.  RE: Create a report of all unregistered VMs

    Posted Dec 13, 2024 03:31 PM
    Edited by dbutch1976 Dec 13, 2024 04:08 PM

    Took one final kick at this with the goal of checking all the datastores in the environment and only returning unregistered VMs. Looks like it's working and it's giving me the output I'm looking for. Changes where:

    Removed $ErrorActionPreference = "Stop" since it is not required.
    Removed $VMs = Get-VM since it's not required.
    Added $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*"} In order to remove all vCLS VMs from the results.

    Below is the final script:

     Mind giving it a quick sanity check and suggesting any improvements?

    $Datastores = get-datastore
    $unregistered = @()
    ForEach ($datastore in $datastores) {
        $psds = Get-Datastore -Name $datastore
        New-PSDrive -Name TgtDS -Location $psds -PSProvider VimDatastore -Root '\' | Out-Null
        $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*"}
        foreach ($VMX in $VMXS) {
                try {
                    Get-VM -datastore $datastore -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
                    } 
                catch {
                    $unregistered += [PSCustomObject] @{
                        Name = $vmx.Name
                        DatastoreFullPath = $vmx.DatastoreFullPath
                        LastWriteTime = $vmx.LastWriteTime
                    }
                }
            }
            Remove-PSDrive -Name TgtDS
    }
    $unregistered | export-csv -Path C:\output\UnregisteredVms1.csv -NoTypeInformation

    Output:
    Name                     DatastoreFullPath                                                           LastWriteTime        
    ----                     -----------------                                                           -------------        
    unregistered_IOMEGA1.vmx [IOMEGA] unregistered_IOMEGA1/unregistered_IOMEGA1.vmx                      2024-12-12 2:01:48 PM
    unregistered_IOMEGA2.vmx [IOMEGA] unregistered_IOMEGA2/unregistered_IOMEGA2.vmx                      2024-12-12 2:02:34 PM
    unregistered_IOMEGA3.vmx [IOMEGA] unregistered_IOMEGA3/unregistered_IOMEGA3.vmx                      2024-12-12 2:03:00 PM
    unregistered_VSAN2.vmx   [vsanDatastore] e6335b67-9262-facd-fedc-48210b505bbb/unregistered_VSAN2.vmx 2024-12-12 2:05:11 PM
    vc7b.vmx                 [vsanDatastore] 686d1a67-1204-6504-bcbc-48210b506c23/vc7b.vmx               2024-11-05 2:48:57 PM
    vc7a.vmx                 [vsanDatastore] 8e611a67-5c6b-ae15-634c-48210b506c23/vc7a.vmx               2024-11-05 2:35:54 PM
    Deletemetest1.vmx        [vsanDatastore] 7cd74467-9424-8307-af2a-48210b506c23/Deletemetest1.vmx      2024-11-25 3:01:04 PM
    unregistered_VSAN3.vmx   [vsanDatastore] 03355b67-4c56-4145-73f1-48210b505bbb/unregistered_VSAN3.vmx 2024-12-12 2:09:57 PM
    unregistered_VSAN1.vmx   [vsanDatastore] 7f335b67-f9b3-5722-bf2e-8d07950ebe8e/unregistered_VSAN1.vmx 2024-12-12 2:03:31 PM





  • 13.  RE: Create a report of all unregistered VMs

    Posted Dec 13, 2024 04:32 PM

    Look ok, great script



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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 14.  RE: Create a report of all unregistered VMs

    Posted Dec 17, 2024 10:16 AM
    Edited by dbutch1976 Dec 17, 2024 10:32 AM

    Hi LucD,

    The script is great, but I see a potential for false positives in the event that the VM is registered to the vCenter using a name which is different than the name as it appears in the datastore. 

    Is there a way to modify this line:

     $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*"}

    I would like to exclude all results in which there is an .nvram file located in the same folder as the .vmx file. Not sure how I can approach this, any suggestions? 

    *Update* after doing some additional testing, I can see that several legitimately unregistered VMs also have NVRAM files. Perhaps these were created when the VM was turned on, and are not deleted automatically. Perhaps I could check for the presence of a .vmx.lck file instead? (Although this has the disadvantage of only located powered on VMs, but it's better than nothing).

    Any other suggestions would be appreciated.




  • 15.  RE: Create a report of all unregistered VMs

    Posted Dec 17, 2024 07:14 PM

    I feel like I'm close on this but I'm stuck. I know exactly where the error is I think: 

    $Datastores = Get-Datastore | Where-Object {$_.name -notmatch "local" -and $_.name -notmatch "NFS"}
    #$Datastores = Get-Datastore -name IOMEGA
    $unregistered = @()
    ForEach ($datastore in $datastores) {
        $psds = Get-Datastore -Name $datastore
        New-PSDrive -Name TgtDS -Location $psds -PSProvider VimDatastore -Root '\' | Out-Null
        $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*" -and $_.name -notmatch "zerto*"}
        #$VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -eq "unregistered_IOMEGA1.vmx"}
        Write-Host "Starting $datastore"
        foreach ($VMX in $VMXS) {
            try {
                    Get-VM -datastore $datastore -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
                    } 
                catch {
                    $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object {$_.name -contains "$vmx.name.lck"}
                    $unregistered += [PSCustomObject] @{
                        Name = $vmx.Name
                        DatastoreFullPath = $vmx.DatastoreFullPath
                        LastWriteTime = $vmx.LastWriteTime
                        vCNameMismatch = $mismatch.Name 
                    }
                }
            }
            Write-Host "Done"
            Remove-PSDrive -Name TgtDS
    }
    $unregistered # | export-csv -Path C:\output\UnregisteredVms1.csv -NoTypeInformation
    



    $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object {$_.name -contains "$vmx.name.lck"}

    How do I make the bold into $vmx.name + .lck as the extention? I've tried
    "$vmx.name".lck
    $vmx.name.lck
    $vmx.name'.lck'

    The goal is to simply add the vmx.lck file name to the output to simply indicate that the VM is powered on and running under a different name. I'll tackle figuring out the actual name from the .vmx file next.




  • 16.  RE: Create a report of all unregistered VMs

    Posted Dec 18, 2024 03:43 AM

    Try with

    $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object {$_.name -contains "$($vmx.name).lck"}


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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 17.  RE: Create a report of all unregistered VMs

    Posted Dec 18, 2024 04:48 AM

    That worked perfectly, thanks LucD. Now that I have a way of detecting a false positive, is there a way to then get the correct name from the .vmx file and add that to a variable? I have found the correct name of the VM within the .vmx file under "displayName".

    Current script:

    $Datastores = Get-Datastore | Where-Object {$_.name -notmatch "local" -and $_.name -notmatch "NFS"}
    #$Datastores = Get-Datastore -name IOMEGA
    $unregistered = @()
    ForEach ($datastore in $datastores) {
        $psds = Get-Datastore -Name $datastore
        New-PSDrive -Name TgtDS -Location $psds -PSProvider VimDatastore -Root '\' | Out-Null
        $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*" -and $_.name -notmatch "zerto*"}
        #$VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -eq "unregistered_IOMEGA1.vmx"}
        Write-Host "Starting $datastore"
        foreach ($VMX in $VMXS) {
            try {
                    Get-VM -datastore $datastore -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
                    } 
                catch {
                    $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object {$_.name -contains "$($vmx.name).lck"}
                    $unregistered += [PSCustomObject] @{
                        Name = $vmx.Name
                        DatastoreFullPath = $vmx.DatastoreFullPath
                        LastWriteTime = $vmx.LastWriteTime
                        vCNameMismatch = $mismatch.Name 
                    }
                }
            }
            Write-Host "Done"
            Remove-PSDrive -Name TgtDS
    }
    $unregistered | export-csv -Path C:\output\UnregisteredVms2.csv -NoTypeInformation
    



    Output:

    Name DatastoreFullPath LastWriteTime vCNameMismatch
    unregistered_IOMEGA1.vmx [IOMEGA] unregistered_IOMEGA1/unregistered_IOMEGA1.vmx 2024-12-17 18:47 unregistered_IOMEGA1.vmx.lck
    unregistered_IOMEGA2.vmx [IOMEGA] unregistered_IOMEGA2/unregistered_IOMEGA2.vmx 2024-12-12 14:02
    unregistered_VSAN3.vmx [vsanDatastore] 03355b67-4c56-4145-73f1-48210b505bbb/unregistered_VSAN3.vmx 2024-12-12 14:09
    unregistered_VSAN1.vmx [vsanDatastore] 7f335b67-f9b3-5722-bf2e-8d07950ebe8e/unregistered_VSAN1.vmx 2024-12-17 18:48 unregistered_VSAN1.vmx.lck



  • 18.  RE: Create a report of all unregistered VMs

    Posted Dec 18, 2024 08:06 AM

    You will have to read the content of the VMX file to get the DisplayName.
    Something like this

    $Datastores = Get-Datastore | Where-Object { $_.name -notmatch "local" -and $_.name -notmatch "NFS" }
    #$Datastores = Get-Datastore -name IOMEGA
    $unregistered = @()
    ForEach ($datastore in $datastores) {
      $psds = Get-Datastore -Name $datastore
      New-PSDrive -Name TgtDS -Location $psds -PSProvider VimDatastore -Root '\' | Out-Null
      $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object { $_.name -notmatch "vCLS-*" -and $_.name -notmatch "zerto*" }
      #$VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -eq "unregistered_IOMEGA1.vmx"}
      Write-Host "Starting $datastore"
      $tempFile = New-TemporaryFile
      foreach ($VMX in $VMXS) {
        try {
          # Check for DisplayName
          Copy-DatastoreItem -Item $VMX.FullName -Destination $tempFile
          $displayName = Get-Content -Path $tempFile | where{$_ -match 'displayName'}
          if($displayName){
            $vmName = $displayName.Split('"')[1]
          }
          else{
            $vmName = $VMX.name.replace('.vmx', '')
          }
          Write-Host "Checking if VM $vmName is registered"
          Get-VM -Datastore $datastore -Name $vmName -ErrorAction:Stop | Out-Null
        } catch {
          $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object { $_.name -contains "$($vmx.name).lck" }
          $unregistered += [PSCustomObject] @{
            Name = $vmx.Name
            DisplayName = $vmName
            DatastoreFullPath = $vmx.DatastoreFullPath
            LastWriteTime = $vmx.LastWriteTime
            vCNameMismatch = $mismatch.Name
          }
        }
      }
      Write-Host "Done"
      Remove-Item -Path $tempFile
      Remove-PSDrive -Name TgtDS
    }
    $unregistered | Export-Csv -Path C:\output\UnregisteredVms2.csv -NoTypeInformation
    


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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 19.  RE: Create a report of all unregistered VMs

    Posted Dec 18, 2024 02:25 PM

    Hi LucD,

    With your help I think I have created a thing of beauty. I changed your script a little to only check the .vmx file for a different displayname in the event that it actually finds a matching vmx.lck file, I believe this makes the script run faster. There's just one thing that can be done to improve the script at this point, make the script capable of running with only read-only + datastore.browse privileges. Any idea if/how this could be done? Otherwise, is would "Datastore.Create" and "Datastore.Delete" be required?  This script is borderline magic, tested and working with VMFS and VSAN datastores and probably any other type:

    $Datastores = Get-Datastore | Where-Object {$_.name -notmatch "local" -and $_.name -notmatch "NFS"}
    $unregistered = @()
    ForEach ($datastore in $datastores) {
        $psds = Get-Datastore -Name $datastore
        New-PSDrive -Name TgtDS -Location $psds -PSProvider VimDatastore -Root '\' | Out-Null
        $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*" -and $_.name -notmatch "zerto*"}
        #$VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -eq "unregistered_IOMEGA1.vmx"}
        Write-Host "Starting $datastore"
        foreach ($VMX in $VMXS) {
            try {
                    Get-VM -datastore $datastore -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
                    } 
                catch {
                    $VMname = $VMX.name.replace('.vmx','')
                    $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object {$_.name -contains "$($vmx.name).lck"}
                    if ($mismatch -ne $null) {
                        $tempFile = New-TemporaryFile
                        Copy-DatastoreItem -Item $VMX.FullName -Destination $tempFile
                        $displayname = Get-Content -Path $tempFile | where{$_ -match 'displayName'}
                        $vmxfilename  = $displayName.Split('"')[1]
                        $result = "NAME MISMATCH - Powered on and running as $vmxfilename."
                        Write-host "$VMname is running using a different name. Result-MISMATCH."
                        }
                        else{
                        write-host "$VMname is not found on $global:DefaultVIServers.name and is not running. Result-UNREGISTERED."
                        $result = "UNREGISTERED."
                        }
                            $unregistered += [PSCustomObject] @{
                            Name = $vmx.Name
                            DatastoreFullPath = $vmx.DatastoreFullPath
                            LastWriteTime = $vmx.LastWriteTime
                            Result = $result
                    }
                }
            }
            Write-Host "Done"
            Remove-PSDrive -Name TgtDS
    }
    $unregistered  | export-csv -Path C:\output\UnregisteredVms3.csv -NoTypeInformation





    Sample output:

    Name DatastoreFullPath LastWriteTime Result
    unregistered_IOMEGA2.vmx [IOMEGA] unregistered_IOMEGA2/unregistered_IOMEGA2.vmx 2024-12-12 14:02 UNREGISTERED.
    unregistered_IOMEGA3.vmx [IOMEGA] unregistered_IOMEGA3/unregistered_IOMEGA3.vmx 2024-12-12 14:03 UNREGISTERED.
    unregistered_IOMEGA1.vmx [IOMEGA] unregistered_IOMEGA1/unregistered_IOMEGA1.vmx 2024-12-17 18:47 NAME MISMATCH - Powered on and running as Unmatched_IOMEGA1.
    unregistered_VSAN2.vmx [vsanDatastore] e6335b67-9262-facd-fedc-48210b505bbb/unregistered_VSAN2.vmx 2024-12-12 14:05 UNREGISTERED.
    vc7b.vmx [vsanDatastore] 686d1a67-1204-6504-bcbc-48210b506c23/vc7b.vmx 2024-11-05 14:48 UNREGISTERED.
    vc7a.vmx [vsanDatastore] 8e611a67-5c6b-ae15-634c-48210b506c23/vc7a.vmx 2024-11-05 14:35 UNREGISTERED.
    Deletemetest1.vmx [vsanDatastore] 7cd74467-9424-8307-af2a-48210b506c23/Deletemetest1.vmx 2024-11-25 15:01 UNREGISTERED.
    unregistered_VSAN3.vmx [vsanDatastore] 03355b67-4c56-4145-73f1-48210b505bbb/unregistered_VSAN3.vmx 2024-12-12 14:09 UNREGISTERED.
    unregistered_VSAN1.vmx [vsanDatastore] 7f335b67-f9b3-5722-bf2e-8d07950ebe8e/unregistered_VSAN1.vmx 2024-12-17 18:48 NAME MISMATCH - Powered on and running as Unmatched_VSAN1.




  • 20.  RE: Create a report of all unregistered VMs

    Posted Dec 18, 2024 02:55 PM

    There might be a few flaws in your code.

    You are creating a temp file for each VMX file that is found, you should only need one since it is overwritten for each VMX.
    And you can remove that Temp file at the end of the loop.

    I used a test to determine if the VMX file contained a DisplayName entry, that is not always the case.
    You left that test out, so you can end up with an empty string in the $displayName variable.

    I don't think there is a .lck file when a VM is powered off.

    To have an account with the privileges "read-only + datastore.browse privileges" depends on the permissions you placed on your Datastores and VMs.
    I would start with a read-only account and check which missing privileges are reported, and continue till all privileges are there.
    Also note that PowerCLI requires some rather basic privileges to read the inventory.



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


    Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference


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



  • 21.  RE: Create a report of all unregistered VMs

    Posted Dec 18, 2024 03:15 PM

    I see your point RE "I don't think there is a .lck file when a VM is powered off." There isn't one, so some VMs will be marked as unregistered when they're actually in inventory under a different name and powered off. I'm willing to accept these, I'll catch them when I attempt to re-register them and I get an error, my main concern was misreporting a powered on (potentially critical) VM as unregistered.
    I've added fixed the temporary file creation/removal as suggested.
    From my initial testing it appears that the permission needed are Datastore.Low level file operations, which is a little unfortunate because these permissions also allows performing write, delete, and rename operations in the datastore browser in addition to read, but looks like it can't be helped.

    #PERMISSIONS REQUIRED - Datastore.Browse + Datastore.Low level file operations
    $Datastores = Get-Datastore | Where-Object {$_.name -notmatch "local" -and $_.name -notmatch "NFS"}
    $unregistered = @()
    ForEach ($datastore in $datastores) {
        $psds = Get-Datastore -Name $datastore
        New-PSDrive -Name TgtDS -Location $psds -PSProvider VimDatastore -Root '\' | Out-Null
        $VMXS = Get-ChildItem -Path TgtDS: -Recurse -Filter *.vmx | Where-Object {$_.name -notmatch "vCLS-*" -and $_.name -notmatch "zerto*"}
        Write-Host "Starting $datastore"
        $tempFile = New-TemporaryFile
        foreach ($VMX in $VMXS) {
            try {
                    Get-VM -datastore $datastore -name $VMX.name.replace('.vmx','') -ErrorAction:Stop | Out-Null
                    } 
                catch {
                    $VMname = $VMX.name.replace('.vmx','')
                    $mismatch = Get-ChildItem -Path $VMX.PSParentpath -Filter *.lck | Where-Object {$_.name -contains "$($vmx.name).lck"}
                    if ($mismatch -ne $null) {
                        Copy-DatastoreItem -Item $VMX.FullName -Destination $tempFile
                        $displayname = Get-Content -Path $tempFile | where{$_ -match 'displayName'}
                        $vmxfilename  = $displayName.Split('"')[1]
                        $result = "NAME MISMATCH - Powered on and running as $vmxfilename."
                        Write-host "$VMname is running using a different name. Result-MISMATCH."
                        }
                        else{
                        write-host "$VMname is not found on $global:DefaultVIServers.name and is not running. UNREGISTERED VM found."
                        $result = "UNREGISTERED."
                        }
                            $unregistered += [PSCustomObject] @{
                            Name = $vmx.Name
                            DatastoreFullPath = $vmx.DatastoreFullPath
                            LastWriteTime = $vmx.LastWriteTime
                            Result = $result
                    }
                }
            }
            Write-Host "Done"
            Remove-PSDrive -Name TgtDS
            Remove-Item -Path $tempFile
    }
    
    $unregistered  | export-csv -Path C:\output\UnregisteredVms3.csv -NoTypeInformation