From e78eed48b6744e49125f9c464a8903b651f5b760 Mon Sep 17 00:00:00 2001 From: Charlie Silverman Date: Fri, 24 Jul 2026 17:25:22 -0700 Subject: [PATCH] Create CollectVMInventory.ps1 --- vCenter Tools/CollectVMInventory.ps1 | 326 +++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 vCenter Tools/CollectVMInventory.ps1 diff --git a/vCenter Tools/CollectVMInventory.ps1 b/vCenter Tools/CollectVMInventory.ps1 new file mode 100644 index 0000000..8308a0c --- /dev/null +++ b/vCenter Tools/CollectVMInventory.ps1 @@ -0,0 +1,326 @@ +param ( + [string]$vCenter = "", + [string]$cluster = "", + [string]$filename = "vmexport.xlsx", + [string]$Customer = "", + [switch]$Help = $false, + [switch]$SaveCred = $false +) + +# Variables +$fileVersion = "1.3.1" +$fileDate = "2026-07-02" +$scriptName = Split-Path -Path $PSCommandPath -Leaf + +# Display Script Banner +Write-Host "--------------------------------------------------" -ForegroundColor White +Write-Host "Collect VM Information for Migration" -ForegroundColor Green +Write-Host "Version $($FileVersion) ($($FileDate))" -ForegroundColor Green +Write-Host "Written by Charlie Silverman" -ForegroundColor Green +Write-Host "--------------------------------------------------" -ForegroundColor White + +function CheckModuleInstalled($moduleName, $command) { + Write-Host "Checking that package $moduleName is installed..." -NoNewLine + if (-not(get-command $command -erroraction silentlycontinue)) { + Write-Host " Missing" -ForegroundColor Red + if (-not (Get-Module -ListAvailable -Name $moduleName)) { + $response = "" + while (($response -ne "yes") -and ($response -ne "no")) { + $response = (Read-Host "ImportExcel module not instaled. Install now? (yes/no)").ToLower() + } + if ($response -eq "no") { + Write-Host "Operation Cancelled." -ForegroundColor Yellow + exit + } + Write-Host "Installing PowerShell module $moduleName..." -NoNewLine + Install-Module -Name $moduleName -Scope CurrentUser -Force + Write-Host " Success" -ForegroundColor Green + } + Write-Host "Loading PowerShell module $moduleName..." -NoNewLine + Import-Module $moduleName + Write-Host " Success" -ForegroundColor Green + } else { + Write-Host " Found" -ForegroundColor Green + } +} + +# Check for ImportExcel and install if missing +CheckModuleInstalled -moduleName "VCF.PowerCLI" -command "connect-viserver" +CheckModuleInstalled -moduleName "ImportExcel" -command "import-excel" + +$moduleName = "ImportExcel" +Import-Module ImportExcel + +if ($Help) { + Write-Host + Write-Host "Purpose: Generate a migration spreadsheet from vCenter VMs." + Write-Host "Command Syntax:" + Write-Host " $scriptName (-vcenter [vcenter host name]) (-cluster ""[cluster]"") (-filename ""[filename]"") (-customer ""[customer]""" -foregroundcolor green + Write-Host "Command Line Options:" + Write-Host + Write-Host -NoNewLine " -vCenter " -foregroundcolor Green + Write-Host "- The FQDN or IP address of a vCenter server." + Write-Host -NoNewLine " -cluster " -foregroundcolor Green + Write-Host "- The name of the cluster as shown in vCenter." + Write-Host -NoNewLine " -customer " -foregroundcolor Green + Write-Host "- The name of the customer to display on the sheet title." + Write-Host -NoNewLine " -filename " -foregroundcolor Green + Write-Host "- The CSV filename to save the result." + Write-Host + Write-Host "If any of the information is not provided, you will be prompted. If a credential has been previously stored, it will" + Write-Host "be used again unless the -savecred option is specified." + Write-Host + Write-Host "If a filename is not specified, the file vmexport.csv will be created in the current directory." + Write-Host + + exit +} + +$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + +$quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false + +if ($vCenter -eq "") { + $vCenter = Read-Host "Enter name of vCenter Server" +} + +if ($SaveCred) { + $credential = Get-Credential -Message "Enter credentials for vCenter server $vCenter" + $credential | Export-Clixml -path $env:USERPROFILE\kistools-$vcenter.cred + Write-Host "Saving credentials." +} + +if (Test-Path -path $env:USERPROFILE\kistools-$vcenter.cred -PathType Leaf) { + $credentials = Import-Clixml -path $env:USERPROFILE\kistools-$vcenter.cred + Write-Host "Using stored credentials" +} else { + $credentials = Get-Credential -Message "Enter credentials for vCenter server $vCenter" + if (!$credentials) { + Write-Host "ERROR: No credentials were entered" -foregroundcolor yellow + } +} + +if (Test-Path -path $filename -PathType Leaf) { + Write-Host "File $filename already exists. Overwrite?" -ForegroundColor Yellow -NoNewLine + Write-Host " (Y/N) " -NoNewLine + if ((Read-Host) -eq "Y") { + Remove-Item -Path $filename + } else { + Write-Host "Operation Aborted." + exit + } +} + +try { + Write-Host -NoNewLine "Connecting to vCenter $vcenter..." + $vcconn = Connect-VIServer $vcenter -Credential $credentials -ErrorAction SilentlyContinue + Write-Host " Success" -ForegroundColor green +} catch { + Write-Host " Failure" -ForegroundColor Yellow -BackgroundColor Red + exit +} + +if ($cluster -eq "") { + $Clusterlist = Get-Cluster | Sort-Object + While (($cluster -eq "") -or (-not $cluster)) { + Write-Host "`nCluster Selection:`n" + for ($x = 0; $x -lt $Clusterlist.length; $x++) { + Write-Host "$(([string]($x+1)).PadLeft(3,' '))" -NoNewLine -ForegroundColor Green + Write-Host ": " -NoNewLine -ForegroundColor Gray + Write-Host "$($Clusterlist[$x].name)" -ForegroundColor Yellow + } + $Clusternum = Read-Host "`nSelect Cluster Number" + if ($Clusternum -as [int]) { + $Clusternum = [int]$Clusternum - 1 + $cluster = $Clusterlist[$Clusternum] + } + } +} + +try { + $mycluster = get-cluster $cluster -ErrorAction Stop +} catch { + Write-Host + Write-Host "Cluster $cluster cannot be found in vCenter $vCenter" -ForegroundColor Red -BackgroundColor Yellow -NoNewLine + Write-Host + Write-Host "Operation aborted." + exit +} + +Write-Host "Gathering information about VMs in cluster $cluster..." -NoNewLine +$vmlist = $mycluster[0] | get-vm +$vmcount = $vmlist.length + +$vmresponse = @() + +for ($x = 0; $x -lt $vmlist.length; $x++) { + $percent = [Math]::Round(($x / $vmlist.length) * 100,0) + Write-Progress -ID 1 -Activity "Collecting VM Information" -Status "Processing VM $($x+1) of $($vmlist.length)" -PercentComplete $percent + $thisvm = $vmlist[$x]; + $vmname = $thisvm.Name; + $vmcpu = $thisvm.NumCPU + $vmram = $thisvm.MemoryGB + $vmstorageprov = [Math]::Round($thisvm.ProvisionedSpaceGB,2) + $vmstorageused = [Math]::Round($thisvm.UsedSpaceGB,2) + if ((($thisvm | Get-HardDisk) | Where-Object StorageFormat -ne "Thin").length -gt 0) { + $vmdiskformat = "Thick" + } else { + $vmdiskformat = "Thin" + } + if ($thisvm | Get-Snapshot) { + $vmhassnapshots = "X" + } else { + $vmhassnapshots = "" + } + $vmnics = $thisvm | get-NetworkAdapter + $vmisomounted = (($thisvm | get-cddrive).IsoPath -ne $null) ? "X" : "" + if ($vmnics.length -gt 0) { + $vmnic1pg = $vmnics[0].NetworkName; + } else { + $vmnic1pg = "---"; + } + if ($vmnics.length -gt 1) { + $vmnic2pg = $vmnics[1].NetworkName; + } else { + $vmnic2pg = "---"; + } + if ($vmnics.length -gt 2) { + $vmnic3pg = $vmnics[2].NetworkName; + } else { + $vmnic3pg = "---"; + } + + $thisvmobj = [PSCustomObject]@{ + "Priority" = "" + "Name" = $vmname + "CPU" = $vmcpu + "RAM" = $vmram + "Has ISO Mounted" = $vmisomounted + "Has Snapshots" = $vmhassnapshots + "Provisioned Storage" = $vmstorageprov + "Used Space Original" = $vmstorageused + "Used Space Now" = "" + "Calcualted Space" = "" + "Disk Format" = $vmdiskformat + "NIC1 Port Group" = $vmnic1pg + "NIC2 Port Group" = $vmnic2pg + "NIC3 Port Group" = $vmnic3pg + "VM Migrated" = $null + "DRS Group Updated" = $null + "Storage Migrated" = $null + "In Prog" = "" + "Migrating" = "" + "Complete" = "" + } + + $vmresponse += $thisvmobj +} +Write-Progress -ID 1 -Completed +Write-Host " Success" -ForegroundColor Green +Write-Host "Writing information about $($vmresponse.length) VMs to file $filename..." -NoNewLine +try { + $sheetTitle = "VM Export for Cluster $cluster - $timestamp" + if ($customer -ne "") { + $sheetTitle = "$customer - $sheetTitle" + } + $exceltable = $vmresponse | Export-Excel -path $filename -WorksheetName "vCenter VM Data" -PassThru -Autosize -FreezeTopRow ` + -TableName "VMData" -TableStyle Medium15 -Title $sheetTitle -TitleBold -TitleSize 22 ` + -TableTotalSettings @{ CPU = "Sum"; RAM = "Sum"; "Provisioned Storage" = "Sum"; "Used Space Original" = "Sum" } +} catch { + Write-Host + Write-Host "ERROR! Unable to create file $filename" -ForegroundColor Yellow + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red -BackgroundColor Yellow +} +try { + $sheet = $exceltable.Workbook.Worksheets["vCenter VM Data"] + $sheet.InsertColumn(1,1) + $sheet.Column(8) | Set-ExcelRange -NFormat '#,##0 " GB"' + $sheet.Column(9) | Set-ExcelRange -NFormat '#,##0.00 " GB"' + $sheet.Column(10) | Set-ExcelRange -NFormat '#,##0.00 " GB"' + $sheet.Column(11) | Set-ExcelRange -NFormat '#,##0.00 " GB"' + Set-Format -Address $sheet.Cells["2:2"] -WrapText + $sheet.Column(1).Width = 2 + $sheet.Column(2).Width = 8 + $sheet.Cells["I:T"].Style.HorizontalAlignment = "Center" + $sheet.Cells["D2:T2"].Style.HorizontalAlignment = "Center" + $sheet.Column(6).Width = 10 + $sheet.Column(7).Width = 10 + $sheet.Column(8).Width = 10 + $sheet.Column(9).Width = 13 + $sheet.Column(10).Width = 13 + $sheet.Column(11).Width = 13 + $sheet.Column(12).Width = 12 + $sheet.Column(18).Width = 12 + $sheet.Column(19).Width = 12 + $sheet.Column(20).Width = 12 + $sheet.Column(21).Width = 12 + $sheet.Column(22).Width = 12 + for($counter = 3; $counter -lt 3 + $vmcount; ++$counter) { + Write-Progress -ID 1 -Activity "Collecting VM Information" -Status "Processing VM $($counter-2) of $($vmcount)" -PercentComplete $percent + $CalculatedSpaceFormula = '=if(AND($J' + $counter + '>0,$J' + $counter + '<>""), $I' + $counter + ',"")' + Set-ExcelRange -Address $sheet.Cells["K" + $counter] -Formula $CalculatedSpaceFormula + $InProgFormula = '=IF(AND(COUNTIF(P' + $counter + ':R' + $counter + ',"X")<3,COUNTIF(P' + $counter + ':R' + $counter + ',"")<3),"X","")' + Set-ExcelRange -Address $sheet.Cells["S" + $counter] -Formula $InProgFormula + $MigratingFormula = '=IF(OR(P' + $counter + '="O",Q' + $counter + '="O",R' + $counter + '="O"),"X","")' + Set-ExcelRange -Address $sheet.Cells["T" + $counter] -Formula $MigratingFormula + $CompleteFormula = '=IF(OR(AND(P' + $counter + '="X",Q' + $counter + '="X",R' + $counter + '="X"),L' + $counter + '="O"),"X","")' + Set-ExcelRange -Address $sheet.Cells["U" + $counter] -Formula $CompleteFormula + } + Write-Progress -ID 1 -Completed + $datarange = "B3:U" + (2+$vmcount) + Add-ConditionalFormatting -Worksheet $sheet -Address $datarange -RuleType Expression -ConditionValue '=INDIRECT("VMData[@Disk Format]")="Thick"' -BackgroundColor Red -ForegroundColor White + Add-ConditionalFormatting -Worksheet $sheet -Address $datarange -RuleType Expression -ConditionValue '=INDIRECT("VMData[@Disk Format]")="O"' -BackgroundColor Orange + Add-ConditionalFormatting -Worksheet $sheet -Address $datarange -RuleType Expression -ConditionValue '=INDIRECT("VMData[@In Prog]")="X"' -BackgroundColor Yellow + Add-ConditionalFormatting -Worksheet $sheet -Address $datarange -RuleType Expression -ConditionValue '=INDIRECT("VMData[@Complete]")="X"' -BackgroundColor Green + $sheet.Cells["V4"].Value = "Total VMs:" + $sheet.Cells["V5"].Value = "Thick Provisioned:" + $sheet.Cells["V6"].Value = "Converting to Thin:" + $sheet.Cells["V7"].Value = "Partially Migrated:" + $sheet.Cells["V8"].Value = "In Progress:" + $sheet.Cells["V9"].Value = "Completed:" + $sheet.Cells["V10"].Value = "Remaining:" + $sheet.Cells["V12"].Value = "Original Space Usage:" + $sheet.Cells["V13"].Value = "New Space Usage:" + $sheet.Cells["V14"].Value = "Space Saved:" + $sheet.Cells["V15"].Value = "Savings:" + $sheet.Cells["V4:V15"].Style.HorizontalAlignment = "Right" + $sheet.Column(22).Width = 22 + $sheet.Column(23).Width = 18 + Set-ExcelRange -Address $sheet.Cells["W4"] -Formula '=COUNTA(VMData[Name])' + Set-ExcelRange -Address $sheet.Cells["W5"] -Formula '=COUNTIF(VMData[Disk Format], "<>Thin")' + Set-ExcelRange -Address $sheet.Cells["W6"] -Formula '=COUNTIF(VMData[Disk Format], "O")' + Set-ExcelRange -Address $sheet.Cells["W7"] -Formula '=COUNTIF(VMData[Migrating],"X")' + Set-ExcelRange -Address $sheet.Cells["W8"] -Formula '=COUNTIF(VMData[In Prog], "X")' + Set-ExcelRange -Address $sheet.Cells["W9"] -Formula '=COUNTIF(VMData[Complete], "X")' + Set-ExcelRange -Address $sheet.Cells["W10"] -Formula '=W4-W9' + $formula = '=SUM(H3:H' + (2+$vmcount) +')' + Set-ExcelRange -Address $sheet.Cells["W12"] -Formula $formula + Set-ExcelRange -Address $sheet.Cells["W12"] -NFormat '#,##0.00 " GB"' + Set-ExcelRange -Address $sheet.Cells["W13"] -Formula '=SUM(VMData[Used Space Now])' + Set-ExcelRange -Address $sheet.Cells["W13"] -NFormat '#,##0.00 " GB"' + Set-ExcelRange -Address $sheet.Cells["W14"] -Formula '=W12-W13' + Set-ExcelRange -Address $sheet.Cells["W14"] -NFormat '#,##0.00 " GB"' + Set-ExcelRange -Address $sheet.Cells["W15"] -Formula '=IF(W12>0,W14/W12,"")' + Set-ExcelRange -Address $sheet.Cells["W15"] -NFormat '#.00%' + $sheet.Column(11).Hidden = $true +} catch { + Write-Host + Write-Host "ERROR! Unable to create file $filename" -ForegroundColor Yellow + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red -BackgroundColor Yellow + try { + Close-ExcelPackage $exceltable + if (Test-Path -Path $filename) { + Remove-Item $filename + } + exit + } catch { + if (Test-Path -Path $filename) { + Remove-Item $filename + } + } +} + +$exceltable.Workbook.WorkSheets[1].Tables["VMData"].ShowFilter = $false +Close-ExcelPackage $exceltable +Write-Host " Success" -ForegroundColor Green +