Compare commits

..
19 Commits
Author SHA1 Message Date
ackitsme 368d8318fd Merge branch 'main' of https://gitea.ackcentuate.com/ackitsme/PowerShell 2026-10-18 12:24:44 -07:00
ackitsme 93c4a12927 Update RunESXiCommand.ps1 2026-10-18 12:24:37 -07:00
ackitsme 07be3af454 Update KISHorizon.psm1 2026-10-02 12:25:04 -07:00
ackitsme 3c95ec52e7 Create newlog.txt 2026-09-28 18:26:24 -07:00
ackitsme c43a68cf6f Merge branch 'main' of https://gitea.ackcentuate.com/ackitsme/PowerShell 2026-09-16 14:45:48 -07:00
ackitsme 400273c2ee Update KISHorizon.psm1 2026-09-16 14:45:35 -07:00
ackitsme 201e579d85 Create VMFolderTools.ps1 2026-09-15 11:50:36 -07:00
ackitsme 3fdc643eb6 Update KISHorizon.psm1 2026-09-11 17:21:48 -07:00
ackitsme 551287a628 Update KISHorizon.psm1 2026-09-11 17:14:25 -07:00
ackitsme 5ddb5da886 Update KISHorizon.psm1 2026-09-11 17:14:05 -07:00
ackitsme 13d901b45f Update KISHorizon.psm1 2026-09-11 17:12:43 -07:00
ackitsme e6bb5f2e14 Update KISHorizon.psm1 2026-09-11 17:06:39 -07:00
ackitsme 205d434afc Update KISHorizon.psm1 2026-09-11 16:44:01 -07:00
ackitsme 08c3411bfb Update KISHorizon.psm1 2026-09-11 16:28:32 -07:00
ackitsme 85db0f75bb Update KISHorizon.psm1 2026-09-11 16:26:17 -07:00
ackitsme d4800a02df Update KISHorizon.psm1 2026-09-11 16:22:48 -07:00
ackitsme 7e942d9819 updates 2026-09-11 16:16:12 -07:00
ackitsme 8ca2a1bd4b Update KISHorizon.psm1 2026-09-11 15:34:49 -07:00
ackitsme a3badc4f9e Fixed CheckedModuleInstalled function
Fixed response display if module is not installed.
2026-08-05 09:13:00 -07:00
6 changed files with 45118 additions and 112 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'KISHorizon.psm1'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '7.0'
# Module version number
ModuleVersion = '1.0.0'
# ... other manifest settings ...
}
+239 -99
View File
@@ -1,3 +1,6 @@
$KISHorizon_version="2.0.0"
$KISHorizon_release="2026-10-02"
$global:request_header = @{ $global:request_header = @{
"accept"="*/*" "accept"="*/*"
"content-type"="application/json" "content-type"="application/json"
@@ -124,12 +127,16 @@ function Get-HorizonMachine {
) )
$token = $Horizon.access_token $token = $Horizon.access_token
$server = $Horizon.server_name $server = $Horizon.server_name
$request_header = $global:request_header $request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"} $request_header += @{"authorization"="Bearer $token"}
$machineUri = "https://$server/rest/inventory/v5/machines?sort_by=name&order_by=ASC" $machineUri = "https://$server/rest/inventory/v5/machines?sort_by=name&order_by=ASC"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try { try {
$machines = Invoke-RestMethod -Method Get -Uri $machineUri -Headers $request_header -SkipCertificateCheck $machines = Invoke-RestMethod -Method Get -Uri $machineUri -Headers $request_header -SkipCertificateCheck
} catch { } catch {
@@ -406,18 +413,23 @@ function Get-HorizonDesktopPool {
$pools | Add-Member -MemberType NoteProperty -Name cluster -Value "" $pools | Add-Member -MemberType NoteProperty -Name cluster -Value ""
$pools | Add-Member -MemberType NoteProperty -Name GpuEnabled -Value 0 $pools | Add-Member -MemberType NoteProperty -Name GpuEnabled -Value 0
$pools | Add-Member -MemberType NoteProperty -Name Entitlements -Value @() $pools | Add-Member -MemberType NoteProperty -Name Entitlements -Value @()
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name base_vm_name -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name snapshot_path -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name datacenter_name -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name resource_pool_name -Value ""
$pools.provisioning_settings | Add-Member -MemberType NoteProperty -Name base_folder_path -Value ""
$pools.storage_settings.datastores | Add-Member -MemberType NoteProperty -Name name -Value ""
$machines = $Horizon | Get-HorizonMachine $machines = $Horizon | Get-HorizonMachine
$sessions = $Horizon | Get-HorizonSession $sessions = $Horizon | Get-HorizonSession
$counter = 1 $counter = 1
foreach ($pool in $pools) { foreach ($pool in $pools) {
if ($null -ne $pool.provisioing_settings) {
$pool.provisioning_settings | Add-Member -MemberType NoteProperty -Name base_vm_name -Value ""
$pool.provisioning_settings | Add-Member -MemberType NoteProperty -Name snapshot_path -Value ""
$pool.provisioning_settings | Add-Member -MemberType NoteProperty -Name datacenter_name -Value ""
$pool.provisioning_settings | Add-Member -MemberType NoteProperty -Name resource_pool_name -Value ""
$pool.provisioning_settings | Add-Member -MemberType NoteProperty -Name base_folder_path -Value ""
}
if ($null -ne $pool.storage_settings.datastores) {
$pool.storage_settings.datastores | Add-Member -MemberType NoteProperty -Name name -Value ""
}
Write-Progress -Activity "Processing Pools" -Status "Processing $counter of $($pools.length) - $($pool.name)" -PercentComplete (($counter / $pools.length) *100) -Id 1 Write-Progress -Activity "Processing Pools" -Status "Processing $counter of $($pools.length) - $($pool.name)" -PercentComplete (($counter / $pools.length) *100) -Id 1
$mymachines = $machines | where {$_.desktop_pool_id -eq $pool.id} $mymachines = $machines | where {$_.desktop_pool_id -eq $pool.id}
$pool.machines += $mymachines $pool.machines += $mymachines
@@ -429,52 +441,54 @@ function Get-HorizonDesktopPool {
$session | Add-Member -MemberType NoteProperty -Name machine_name -Value $machineName $session | Add-Member -MemberType NoteProperty -Name machine_name -Value $machineName
$session | Add-Member -MemberType NoteProperty -Name machine_dns_name -Value $machineDNSName $session | Add-Member -MemberType NoteProperty -Name machine_dns_name -Value $machineDNSName
} }
$pool.vCenter = ($hzconn | Get-HorizonVcenter | where-object {$_.id -eq $pool.vcenter_id}).server_name $pool.vCenter = (Get-HorizonVcenter -Horizon $Horizon | where-object {$_.id -eq $pool.vcenter_id}).server_name
if (-not ($global:DefaultVIServer.Name -contains $pool.vCenter)) { if (-not ($global:DefaultVIServer.Name -contains $pool.vCenter)) {
throw "Not Connected to vCenter $($pool.vCenter)" throw "Not Connected to vCenter $($pool.vCenter)"
} }
$pool.cluster = (Get-Cluster | where {$_.id -eq "ClusterComputeResource-$($pool.provisioning_settings.host_or_cluster_id)"}).name $pool.cluster = (Get-Cluster | where {$_.id -eq "ClusterComputeResource-$($pool.provisioning_settings.host_or_cluster_id)"}).name
$pool.provisioning_settings.base_vm_name = (Get-View -Id "VirtualMachine-$($pool.provisioning_settings.parent_vm_id)").Name if ($null -ne $pool.provisioning_settings.base_vm_name) {
$snappath = "" $pool.provisioning_settings.base_vm_name = (Get-View -Id "VirtualMachine-$($pool.provisioning_settings.parent_vm_id)").Name
$goldVM = Get-VM $pool.provisioning_settings.base_vm_name $snappath = ""
$thissnap = $goldVM | Get-Snapshot | Where-Object {$_.Id -eq "VirtualMachineSnapshot-$($pool.provisioning_settings.base_snapshot_id)"} $goldVM = Get-VM $pool.provisioning_settings.base_vm_name
while ($thissnap.parentsnapshotid.length -gt 0) { $thissnap = $goldVM | Get-Snapshot | Where-Object {$_.Id -eq "VirtualMachineSnapshot-$($pool.provisioning_settings.base_snapshot_id)"}
while ($thissnap.parentsnapshotid.length -gt 0) {
if ($snappath -eq "") {
$snappath = $thissnap.Name
} else {
$snappath = $thissnap.Name + "\" + $snappath
}
$thissnap = Get-VM $pool.provisioning_settings.base_vm_name | Get-Snapshot | Where-Object {$_.Id -eq $thissnap.ParentSnapshotId}
}
if ($snappath -eq "") { if ($snappath -eq "") {
$snappath = $thissnap.Name $snappath = $thissnap.Name
} else { } else {
$snappath = $thissnap.Name + "\" + $snappath $snappath = $thissnap.Name + "\" + $snappath
} }
$thissnap = Get-VM $pool.provisioning_settings.base_vm_name | Get-Snapshot | Where-Object {$_.Id -eq $thissnap.ParentSnapshotId} $pool.provisioning_settings.snapshot_path = $snappath
} $pool.provisioning_settings.resource_pool_name = $goldVM.ResourcePool
if ($snappath -eq "") { $pool.provisioning_settings.datacenter_name = (Get-View "Datacenter-$($pool.provisioning_settings.datacenter_id)").Name
$snappath = $thissnap.Name $basefolderpath = ""
} else { $thisfolder = Get-View "Folder-$($pool.provisioning_settings.vm_folder_id)"
$snappath = $thissnap.Name + "\" + $snappath while ($thisfolder.parentsnapshotid.length -gt 0) {
} if ($basefolderpath -eq "") {
$pool.provisioning_settings.snapshot_path = $snappath $basefolderpath = $thisfolder.Name
$pool.provisioning_settings.resource_pool_name = $goldVM.ResourcePool } else {
$pool.provisioning_settings.datacenter_name = (Get-View "Datacenter-$($pool.provisioning_settings.datacenter_id)").Name $basefolderpath = $thisfolder.Name + "\" + $basefolderpath
$basefolderpath = "" }
$thisfolder = Get-View "Folder-$($pool.provisioning_settings.vm_folder_id)" $thisfolder = Get-View "Folder-$($thisfolder.Parent)"
while ($thisfolder.parentsnapshotid.length -gt 0) { }
if ($basefolderpath -eq "") { if ($basefolderpath -eq "") {
$basefolderpath = $thisfolder.Name $basefolderpath = $thisfolder.Name
} else { } else {
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath $basefolderpath = $thisfolder.Name + "\" + $basefolderpath
} }
$thisfolder = Get-View "Folder-$($thisfolder.Parent)" $pool.provisioning_settings.base_folder_path = $basefolderpath
} $datastores = @()
if ($basefolderpath -eq "") { foreach ($datastore in $pool.storage_settings.datastores) {
$basefolderpath = $thisfolder.Name $thisds = Get-View -Id "Datastore-$($datastore.datastore_id)"
} else { $datastore.name = $thisds.Name
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath $datastores += $thisds.Name
} }
$pool.provisioning_settings.base_folder_path = $basefolderpath
$datastores = @()
foreach ($datastore in $pool.storage_settings.datastores) {
$thisds = Get-View -Id "Datastore-$($datastore.datastore_id)"
$datastore.name = $thisds.Name
$datastores += $thisds.Name
} }
$pool.GpuEnabled = ($pool.display_protocol_settings.vgpu_grid_profile.Length -gt 0); $pool.GpuEnabled = ($pool.display_protocol_settings.vgpu_grid_profile.Length -gt 0);
@@ -523,7 +537,8 @@ Function Get-HorizonADUser {
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon, [parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[string]$Domain = "", [string]$Domain = "",
[string]$Id = "", [string]$Id = "",
[string]$Name = "", [string]$UserName = "",
[string]$UserUPN = "",
[int]$Page = 0, [int]$Page = 0,
[int]$PageSize = 0 [int]$PageSize = 0
) )
@@ -546,81 +561,47 @@ Function Get-HorizonADUser {
} }
} }
} }
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
if ($Id -ne "") { if ($Id -ne "") {
$extension = "/$($Id)?group_only=false" $extension = "/$($Id)?group_only=false"
} }
if ($Name -ne "") { if ($UserName -ne "") {
$extension = "?group_only=false&filter={`"type`":`"Equals`",`"name`":`"user_principal_name`",`"value`":`"$Name`"}" $extension = "?group_only=false&filter={`"type`":`"Equals`",`"name`":`"login_name`",`"value`":`"$UserName`"}"
if ($global:kishorizondebug) {
Write-Host UserName: $UserName
}
} }
if ($UserUPN -ne "") {
$extension = "?group_only=false&filter={`"type`":`"Equals`",`"name`":`"user_principal_name`",`"value`":`"$UserUPN`"}"
if ($global:kishorizondebug) {
Write-Host UserUPN: $UserUPN
}
}
$userlookupURI += $extension $userlookupURI += $extension
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try { try {
if ($global:kishorizondebug) {
Write-Host URI: $userlookupURI
}
$users = Invoke-RestMethod -Method Get -Uri $userlookupURI -Headers $request_header -SkipCertificateCheck $users = Invoke-RestMethod -Method Get -Uri $userlookupURI -Headers $request_header -SkipCertificateCheck
if ($global:kishorizondebug) {
Write-Host $users
}
} catch { } catch {
throw "ERROR accessing $userlookupURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)" throw "ERROR accessing $userlookupURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
} }
return $users return $users
} }
<# Function Get-HorizonADGroup {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[string]$Domain = "",
[string]$Id = "",
[string]$Name = "",
[int]$Page = 0,
[int]$PageSize = 0
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$grouplookupURI = "https://$server/rest/external/v1/ad-users-or-groups"
$extension = "";
if ($domain -ne "") {
$extension = "?group_only=true&filter={`"type`":`"Equals`",`"name`":`"domain`",`"value`":`"$domain`"}"
if ($Page -ne 0) {
$extension += "&page=$Page"
if ($PageSize -ne 0) {
$extension += "&size=$PageSize"
}
}
}
if ($Id -ne "") {
$extension = "/$($Id)?group_only=true"
}
if ($Name -ne "") {
$extension = "?group_only=true&filter={`"type`":`"Equals`",`"name`":`"name`",`"value`":`"$Name`"}"
}
$grouplookupURI += $extension
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
try {
$users = Invoke-RestMethod -Method Get -Uri $grouplookupURI -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $grouplookupURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $users
} #>
function Invoke-SSHCommand { function Invoke-SSHCommand {
param ( param (
@@ -671,6 +652,163 @@ function Get-HorizonSessionConnectedLength {
return $retval; return $retval;
} }
function Add-HorizonMachineToPool {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[parameter(Mandatory = $true)][string[]] $MachineName,
[parameter(Mandatory = $true)] $PoolName
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
$PoolID = (Get-HorizonDesktopPool -Horizon $Horizon -PoolName $PoolName).id
$machineAddURI = "https://$server/rest/inventory/v1/desktop-pools/$PoolID/action/add-machines-by-name"
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
$body = @{
machine_names = $MachineName
} | ConvertTo-Json
try {
$result = Invoke-RestMethod -Method Post -Uri $machineAddURI -body $body -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $machineAddURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $result
}
function Grant-HorizonDesktop {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[parameter(Mandatory = $true)] $MachineName,
[string] $UserUPN = "",
[string] $UserName = ""
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
if ($UserUPN -eq "" -and $UserName -eq "") {
return [PSCustomObject]@{
Success = $false
Message = "Neither UserName nor UserUPN were provided."
status_code = 9999
}
}
$mymachine = Get-HorizonMachine -Horizon $Horizon | where {$_.name -eq $MachineName}
$MachineID = $mymachine.id
if ($global:kishorizondebug) {
Write-Host MachineID: $MachineID
}
if ($MachineID -eq $null) {
return [PSCustomObject]@{
Success = $false
Message = "Machine $MachneName does not exist in any desktop pool."
status_code = 9999
}
}
if ($UserName -ne "") {
$User = Get-HorizonAdUser -Horizon $Horizon -UserName $UserName
} else {
$User = Get-HorizonAdUser -Horizon $Horizon -UPN $UserUPN
}
if ($global:kishorizondebug) {
Write-Host UserID: $UserName
Write-Host UserUPD: $UserUPN
}
if ($UserID -eq $null) {
return [PSCustomObject]@{
Success = $false
Message = User $UserUPN cannot be found.
status_code = 9999
}
}
if ($mymachine.PSObject.Properties['user_ids']) {
if ($User.id -in $mymachine.user_ids) {
if ($User
return [PSCustomObject]@{
Success = $false
Message = User $UserUPN already assigned to computer $MachineName.
status_code = 9999
}
}
}
$desktopAssignURI = "https://$server/rest/inventory/v1/machines/$MachineID/action/assign-users"
$body = "[ ""$UserID"" ]"
if ($global:kishorizondebug) {
Write-Host body: $body
}
try {
$result = Invoke-RestMethod -Method Post -Uri $desktopAssignURI -body $body -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $desktopAssignURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $result
}
function Revoke-HorizonDesktop {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
[parameter(Mandatory = $true)] $MachineName
)
$token = $Horizon.access_token
$server = $Horizon.server_name
$request_header = $global:request_header
$request_header += @{"authorization"="Bearer $token"}
if ($global:kishorizondebug) {
Write-Host $MyInvocation.MyCommand.Name Starting
}
$MachineID = (Get-HorizonMachine -Horizon $Horizon | where {$_.name -eq $MachineName}).id
if ($global:kishorizondebug) {
Write-Host MachineID: $MachineID
}
if ($MachineID -eq $null) {
return [PSCustomObject]@{
Success = $false
Message = Machine $MachneName does not exist in any desktop pool.
}
}
$desktopRevokeURI = "https://$server/rest/inventory/v1/machines/$MachineID/action/assign-users"
$body = "[ ""$UserID"" ]"
if ($global:kishorizondebug) {
Write-Host body: $body
}
try {
$result = Invoke-RestMethod -Method Post -Uri $desktopAssignURI -body $body -Headers $request_header -SkipCertificateCheck
} catch {
throw "ERROR accessing $desktopAssignURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
}
return $result
}
Export-ModuleMember -Function Connect-HorizonServer Export-ModuleMember -Function Connect-HorizonServer
Export-ModuleMember -Function Get-HorizonVcenter Export-ModuleMember -Function Get-HorizonVcenter
@@ -685,4 +823,6 @@ Export-ModuleMember -Function Get-HorizonSessionLength
Export-ModuleMember -Function Get-HorizonKeyAgreement Export-ModuleMember -Function Get-HorizonKeyAgreement
Export-ModuleMember -Function Get-HorizonEntitlements Export-ModuleMember -Function Get-HorizonEntitlements
Export-ModuleMember -Function Get-HorizonEntitlementDetail Export-ModuleMember -Function Get-HorizonEntitlementDetail
Export-ModuleMember -Function Add-HorizonMachineToPool
Export-ModuleMember -Function Grant-HorizonDesktop
+44524
View File
File diff suppressed because it is too large. Load diff
+10 -10
View File
@@ -8,8 +8,8 @@ param (
) )
# Variables # Variables
$fileVersion = "1.3.3" $fileVersion = "1.3.5"
$fileDate = "2026-08-04" $fileDate = "2026-08-05"
$scriptName = Split-Path -Path $PSCommandPath -Leaf $scriptName = Split-Path -Path $PSCommandPath -Leaf
# Display Script Banner # Display Script Banner
@@ -26,7 +26,7 @@ function CheckModuleInstalled($moduleName, $command) {
if (-not (Get-Module -ListAvailable -Name $moduleName)) { if (-not (Get-Module -ListAvailable -Name $moduleName)) {
$response = "" $response = ""
while (($response -ne "yes") -and ($response -ne "no")) { while (($response -ne "yes") -and ($response -ne "no")) {
$response = (Read-Host "ImportExcel module not instaled. Install now? (yes/no)").ToLower() $response = (Read-Host "$moduleName module not instaled. Install now? (yes/no)").ToLower()
} }
if ($response -eq "no") { if ($response -eq "no") {
Write-Host "Operation Cancelled." -ForegroundColor Yellow Write-Host "Operation Cancelled." -ForegroundColor Yellow
@@ -44,13 +44,6 @@ function CheckModuleInstalled($moduleName, $command) {
} }
} }
# 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) { if ($Help) {
Write-Host Write-Host
Write-Host "Purpose: Generate a migration spreadsheet from vCenter VMs." Write-Host "Purpose: Generate a migration spreadsheet from vCenter VMs."
@@ -76,6 +69,13 @@ if ($Help) {
exit exit
} }
# 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
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false $quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
+30 -3
View File
@@ -1,4 +1,4 @@
#requires -Modules posh-ssh ##requires -Modules posh-ssh
param ( param (
[string]$vCenter = "", [string]$vCenter = "",
@@ -9,8 +9,8 @@ param (
) )
# Variables # Variables
$FileVersion = "1.0.1" $FileVersion = "1.1.0"
$FileDate = "2026-04-18" $FileDate = "2026-10-06"
# Display Script Banner # Display Script Banner
Write-Host "--------------------------------------------------" -ForegroundColor White Write-Host "--------------------------------------------------" -ForegroundColor White
@@ -19,6 +19,31 @@ Write-Host "Version $($FileVersion) ($($FileDate))" -ForegroundColor Green
Write-Host "Written by Charlie Silverman" -ForegroundColor Green Write-Host "Written by Charlie Silverman" -ForegroundColor Green
Write-Host "--------------------------------------------------" -ForegroundColor White 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 "$moduleName 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
}
}
if ($Help) { if ($Help) {
Write-Host Write-Host
Write-Host "Purpose: Run a CLI command on all hosts in a cluster." Write-Host "Purpose: Run a CLI command on all hosts in a cluster."
@@ -44,6 +69,8 @@ if ($Help) {
exit exit
} }
CheckModuleInstalled -moduleName "posh-ssh" -command "New-SSHSession"
$quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false $quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
if ($vCenter -eq "") { if ($vCenter -eq "") {
+303
View File
@@ -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
}
}