From 201e579d851280d92861893c8a42898f43a4decc Mon Sep 17 00:00:00 2001 From: Charlie Silverman Date: Tue, 15 Sep 2026 11:50:36 -0700 Subject: [PATCH] Create VMFolderTools.ps1 --- vSphere Tools/VMFolderTools.ps1 | 303 ++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 vSphere Tools/VMFolderTools.ps1 diff --git a/vSphere Tools/VMFolderTools.ps1 b/vSphere Tools/VMFolderTools.ps1 new file mode 100644 index 0000000..e7cda57 --- /dev/null +++ b/vSphere Tools/VMFolderTools.ps1 @@ -0,0 +1,303 @@ +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 + } +} \ No newline at end of file