param ( [string]$vCenter = "", [string]$datafile = "", [switch]$backup = $false, [switch]$restore = $false, [switch]$savecred = $false, [switch]$help = $false ) $fileVersion = "2.0.0" $fileDate = "2025-09-29" function ConnectVcenter { try { if ((Test-Path -Path "$env:USERPROFILE\VMFolderTools-${vCenter}.cred" -PathType Leaf) -and (-not $savecred)) { Write-Host "Using stored credentials for $vCenter." $cred = Import-CliXML -path "$env:USERPROFILE\VMFolderTools-${vCenter}.cred" } else { $cred = Get-Credential -Message "Enter credentials for vCenter $vCenter" } Write-Host "Connecting to vCenter server $vCenter..." -NoNewLine $vcServer = connect-viserver $vCenter -credential $cred -ErrorAction Stop Write-Host " Success" -ForegroundColor Green } catch { Write-Host " Failure" -ForegroundColor Red Write-Host "ERROR: Invalid credentials" exit } if ($savecred) { Write-Host "Storing credentials for $vCenter." $cred | Export-CliXML -Path "$env:USERPROFILE\VMFolderTools-${vCenter}.cred" } } function BackupFolderStructure { New-VIProperty -Name 'FolderPath' -ObjectType 'VirtualMachine' -Value { param($vm) function Get-ParentName{ param($object) if($object.Folder){ $blue = Get-ParentName $object.Folder $name = $object.Folder.Name } elseif($object.Parent -and $object.Parent.GetType().Name -like "Folder*"){ $blue = Get-ParentName $object.Parent $name = $object.Parent.Name } elseif($object.ParentFolder){ $blue = Get-ParentName $object.ParentFolder $name = $object.ParentFolder.Name } if("vm","Datacenters" -notcontains $name){ $blue + "/" + $name } else{ $blue } } (Get-ParentName $vm).Remove(0,1) } -Force | Out-Null ConnectVcenter while ([string]::IsNullOrEmpty($datacenter)) { Write-Host "`n`nSelect datacenter:" -ForegroundColor Yellow $datacenters = Get-Datacenter -Server $vcServer | sort-object -property Name $counter = 1 foreach ($dc in $datacenters) { $dcName = $dc.Name Write-host -NoNewLine "${counter}" -ForegroundColor Yellow Write-Host ": $dcName" -ForegroundColor Green ++$counter } try { $dcNum = [int](Read-Host "`nSelection") } catch { $dcNum = 0 } if (($dcNum -ne 0) -and ($dcNum -le $datacenters.Length)) { $datacenter = $datacenters[$dcnum - 1] } } $dcName = $datacenter.Name $clusterlist = Get-Datacenter $datacenter | Get-Cluster | sort-object -property Name Write-Host "--------------------------------------------------" -ForegroundColor White while($true) { Write-Host "`n`nSelect cluster to inventory:" -ForegroundColor Yellow $counter = 1 for($counter = 1; $counter -le $clusterlist.Length; ++$counter) { $cluster = $clusterlist[$counter-1] $clusterName = $cluster.Name Write-host -NoNewLine "${counter}" -ForegroundColor Yellow Write-Host ": $clusterName" -ForegroundColor Green } Write-Host -NoNewLine "$counter" -ForegroundColor Yellow Write-Host ": All Clusters" -ForegroundColor Green try { $clusterNum = [int](Read-Host "`nSelection") } catch { $clusterNum = 0 } if (($clusterNum -ne 0) -and ($clusterNum -le $clusterlist.Length + 1)) { $clusters = @() if ($clusterNum -eq $counter) { $clusters = $clusterlist break } else { $cluster = $clusterlist[$clusterNum - 1] $clusters += $cluster break } } } Write-Host "`n`nProcessing..." -ForegroundColor Yellow $vmdata = [PSCustomObject]@{"vCenter" = $vCenter} $vmdata | Add-Member -MemberType NoteProperty -Name "Clusters" -Value @() foreach ($cluster in $clusters) { $clusterName = $cluster.Name Write-Host "Gathering folder inventory for cluster $clusterName" $myhosts = $cluster | get-VMHost if ($myhosts.Count -lt 1) { continue } $mydatacenter = ($myhosts[0] | get-datacenter).Name $vmlist = Get-VM -Location (Get-Cluster $cluster) | Select Name, @{Label="VMHost";Expression={$_.VMHost.Name}}, FolderPath $thiscluster = [PSCustomObject]@{Name="$clusterName";Datacenter="$mydatacenter"} $thiscluster | Add-Member -MemberType NoteProperty -Name "VMs" -Value $vmlist $vmdata.Clusters += $thiscluster } $jsonOutput = $vmdata | ConvertTo-Json -Depth 4 #-Compress $cwd = (Get-Location).Path $jsonOutput | Out-File -FilePath "$cwd\$clusterName-Folder.json" Write-Host "`nProcessing complete." -ForegroundColor Yellow Write-Host "Files have been placed into the folder $cwd." disconnect-viserver $vCenter -Confirm:$false } function RestoreFolderStructure { ConnectVcenter $newClusters = Get-Cluster -Server $vcServer | Sort-Object -Property Name $newClusters $data = Get-Content $datafile | Out-String | ConvertFrom-Json $oldvc = $data.vCenter $oldClusters = $data.Clusters while($true) { Write-Host "`n`nSelect source cluster from ${oldvc}:" -ForegroundColor Yellow $counter = 1 for($counter = 1; $counter -le $oldClusters.Length; ++$counter) { $cluster = $oldClusters[$counter-1] $clusterName = $cluster.Name Write-host -NoNewLine "${counter}" -ForegroundColor Yellow Write-Host ": $clusterName" -ForegroundColor Green } try { $clusterNum = [int](Read-Host "`nSelection") } catch { $clusterNum = 0 } if (($clusterNum -ne 0) -and ($clusterNum -le $oldClusters.Length)) { $oldcluster = $oldClusters[$clusterNum - 1] break } } $oldClusterName = $oldcluster.Name $oldDC = $oldcluster.Datacenter $newClusters while($true) { Write-Host "`n`nSelect destination cluster from ${vCenter}:" -ForegroundColor Yellow $counter = 1 for($counter = 1; $counter -le $newClusters.Length; ++$counter) { $cluster = $newClusters[$counter-1] $clusterName = $cluster.Name Write-host -NoNewLine "${counter}" -ForegroundColor Yellow Write-Host ": $clusterName" -ForegroundColor Green } try { $clusterNum = [int](Read-Host "`nSelection") } catch { $clusterNum = 0 } if (($clusterNum -ne 0) -and ($clusterNum -le $newClusters.Length)) { $newcluster = $newClusters[$clusterNum - 1] break } } $newClusterName = $newcluster.Name $newDC = $newcluster | Get-Datacenter Write-Host "`n" $clusterData = $oldcluster.VMs $clusterData | %{ $location = "vm" $folderPath = $_.FolderPath if ($folderpath -eq "") { $location = Get-Folder -Name "vm" -Server $vCenterenter -ErrorAction SilentlyContinue | Select-Object -First 1 $folderpath = "/" } else { $_.FolderPath.TrimStart('/').Split('/') | %{ $tgtFolder = Get-Folder -Name $_ -Server $vCenterenter -Location $location -ErrorAction SilentlyContinue | Select-Object -First 1 if(!$tgtFolder){ $location = New-Folder -Name $_ -Server $vCenterenter -Location $location } else{ $location = $tgtFolder } } } try { $vmName = $_.Name $thisVM = Get-VM $vmName -Server $vCenterenter -ErrorAction SilentlyContinue if ($thisVM -and (($thisVM.Folder | Select-Object -First 1).Name -ne $location.Name)) { Write-Host -NoNewLine "Moving VM $vmName to folder $folderPath..." $result = Move-VM -VM $thisVM -InventoryLocation $location -Confirm:$false Write-Host " Success" -ForegroundColor Green } else { If (($thisVM.Folder | Select-Object -First 1).Name -eq $location.Name) { Write-Host "VM $vmName does not need to be moved." } else { Write-Host "VM $vmName cannot be found in this Datacenter" } } } catch { Write-Host "Failure" -ForegroundColor Red Write-Host "ERROR: Unable to move VM $vmName" } } disconnect-viserver $vCenter -Confirm:$false } Write-Host "--------------------------------------------------" -ForegroundColor White Write-Host "vSphere VM Folder Structure Backup/Restore Utility" -ForegroundColor Green Write-Host "Version ${FileVersion} (${FileDate})" -ForegroundColor Green Write-Host "Written by Charlie Silverman" -ForegroundColor Green Write-Host "--------------------------------------------------" -ForegroundColor White if ($backup) { $cmdoption = "backup" } if ($restore) { $cmdoption = "restore" } if ($help) { $cmdoption = "backup" } switch($cmdoption) { "help" { Write-Host "Command Syntax:" -ForegroundColor Green Write-Host 'VMFolderTools.ps1 -oldvc="VCURI" [-$vCenter="VCURI"] -backup/-restore/-help [-savecred]' -ForegroundColor Green Write-Host "" Write-Host "Command line options:" -ForegroundColor Green Write-Host "---------------------" -ForegroundColor Green Write-Host "-$vCenter - Specify the URI for the appropriate vCenter server" -ForegroundColor Green Write-Host "-backup - Backup folder structure to CSV" -ForegroundColor Green Write-Host "-restore - Restore folder structure from CSV" -ForegroundColor Green Write-Host "-datafile - Specify the path to a file created with a backup process" -ForegroundColor Green Write-Host "-savecred - Save Credentials. If credentials were previously saved and this option is not selected," -ForegroundColor Green Write-Host " they will be used." -ForegroundColor Green Write-Host "-help - Show this help information." -ForegroundColor Green exit } "backup" { if ($vCenter -eq "") { Write-Host "ERROR: vCenter not specified." -ForegroundColor Red exit } BackupFolderStructure } "restore" { if ($vCenter -eq "") { Write-Host "ERROR: vCenter not specified." -ForegroundColor Red exit } if ($datafile -eq "") { Write-Host "ERROR: Data file not specified." -ForegroundColor Red exit } else { if (-not (Test-Path -Path $datafile)) { Write-Host "ERROR: Data file specified (${datafile}) does not exist." -ForegroundColor Red exit } } RestoreFolderStructure } default { Write-Host "ERROR: Command option not specified." -ForegroundColor Red } }