Compare commits
19
Commits
37adc4f178
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
368d8318fd | ||
|
|
93c4a12927 | ||
|
|
07be3af454 | ||
|
|
3c95ec52e7 | ||
|
|
c43a68cf6f | ||
|
|
400273c2ee | ||
|
|
201e579d85 | ||
|
|
3fdc643eb6 | ||
|
|
551287a628 | ||
|
|
5ddb5da886 | ||
|
|
13d901b45f | ||
|
|
e6bb5f2e14 | ||
|
|
205d434afc | ||
|
|
08c3411bfb | ||
|
|
85db0f75bb | ||
|
|
d4800a02df | ||
|
|
7e942d9819 | ||
|
|
8ca2a1bd4b | ||
|
|
a3badc4f9e |
No files matched your search
@@ -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 ...
|
||||
}
|
||||
+241
-101
@@ -1,3 +1,6 @@
|
||||
$KISHorizon_version="2.0.0"
|
||||
$KISHorizon_release="2026-10-02"
|
||||
|
||||
$global:request_header = @{
|
||||
"accept"="*/*"
|
||||
"content-type"="application/json"
|
||||
@@ -130,6 +133,10 @@ function Get-HorizonMachine {
|
||||
|
||||
$machineUri = "https://$server/rest/inventory/v5/machines?sort_by=name&order_by=ASC"
|
||||
|
||||
if ($global:kishorizondebug) {
|
||||
Write-Host $MyInvocation.MyCommand.Name Starting
|
||||
}
|
||||
|
||||
try {
|
||||
$machines = Invoke-RestMethod -Method Get -Uri $machineUri -Headers $request_header -SkipCertificateCheck
|
||||
} catch {
|
||||
@@ -406,18 +413,23 @@ function Get-HorizonDesktopPool {
|
||||
$pools | Add-Member -MemberType NoteProperty -Name cluster -Value ""
|
||||
$pools | Add-Member -MemberType NoteProperty -Name GpuEnabled -Value 0
|
||||
$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
|
||||
$sessions = $Horizon | Get-HorizonSession
|
||||
|
||||
$counter = 1
|
||||
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
|
||||
$mymachines = $machines | where {$_.desktop_pool_id -eq $pool.id}
|
||||
$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_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)) {
|
||||
throw "Not Connected to vCenter $($pool.vCenter)"
|
||||
}
|
||||
$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
|
||||
$snappath = ""
|
||||
$goldVM = Get-VM $pool.provisioning_settings.base_vm_name
|
||||
$thissnap = $goldVM | Get-Snapshot | Where-Object {$_.Id -eq "VirtualMachineSnapshot-$($pool.provisioning_settings.base_snapshot_id)"}
|
||||
while ($thissnap.parentsnapshotid.length -gt 0) {
|
||||
if ($null -ne $pool.provisioning_settings.base_vm_name) {
|
||||
$pool.provisioning_settings.base_vm_name = (Get-View -Id "VirtualMachine-$($pool.provisioning_settings.parent_vm_id)").Name
|
||||
$snappath = ""
|
||||
$goldVM = Get-VM $pool.provisioning_settings.base_vm_name
|
||||
$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 "") {
|
||||
$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 "") {
|
||||
$snappath = $thissnap.Name
|
||||
} else {
|
||||
$snappath = $thissnap.Name + "\" + $snappath
|
||||
}
|
||||
$pool.provisioning_settings.snapshot_path = $snappath
|
||||
$pool.provisioning_settings.resource_pool_name = $goldVM.ResourcePool
|
||||
$pool.provisioning_settings.datacenter_name = (Get-View "Datacenter-$($pool.provisioning_settings.datacenter_id)").Name
|
||||
$basefolderpath = ""
|
||||
$thisfolder = Get-View "Folder-$($pool.provisioning_settings.vm_folder_id)"
|
||||
while ($thisfolder.parentsnapshotid.length -gt 0) {
|
||||
$pool.provisioning_settings.snapshot_path = $snappath
|
||||
$pool.provisioning_settings.resource_pool_name = $goldVM.ResourcePool
|
||||
$pool.provisioning_settings.datacenter_name = (Get-View "Datacenter-$($pool.provisioning_settings.datacenter_id)").Name
|
||||
$basefolderpath = ""
|
||||
$thisfolder = Get-View "Folder-$($pool.provisioning_settings.vm_folder_id)"
|
||||
while ($thisfolder.parentsnapshotid.length -gt 0) {
|
||||
if ($basefolderpath -eq "") {
|
||||
$basefolderpath = $thisfolder.Name
|
||||
} else {
|
||||
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath
|
||||
}
|
||||
$thisfolder = Get-View "Folder-$($thisfolder.Parent)"
|
||||
}
|
||||
if ($basefolderpath -eq "") {
|
||||
$basefolderpath = $thisfolder.Name
|
||||
} else {
|
||||
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath
|
||||
}
|
||||
$thisfolder = Get-View "Folder-$($thisfolder.Parent)"
|
||||
}
|
||||
if ($basefolderpath -eq "") {
|
||||
$basefolderpath = $thisfolder.Name
|
||||
} else {
|
||||
$basefolderpath = $thisfolder.Name + "\" + $basefolderpath
|
||||
}
|
||||
$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.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);
|
||||
@@ -523,7 +537,8 @@ Function Get-HorizonADUser {
|
||||
[parameter(Mandatory = $true, ValueFromPipeline = $true)] $Horizon,
|
||||
[string]$Domain = "",
|
||||
[string]$Id = "",
|
||||
[string]$Name = "",
|
||||
[string]$UserName = "",
|
||||
[string]$UserUPN = "",
|
||||
[int]$Page = 0,
|
||||
[int]$PageSize = 0
|
||||
)
|
||||
@@ -547,22 +562,40 @@ Function Get-HorizonADUser {
|
||||
}
|
||||
}
|
||||
|
||||
if ($Id -ne "") {
|
||||
$extension = "/$($Id)?group_only=false"
|
||||
}
|
||||
|
||||
if ($Name -ne "") {
|
||||
$extension = "?group_only=false&filter={`"type`":`"Equals`",`"name`":`"user_principal_name`",`"value`":`"$Name`"}"
|
||||
}
|
||||
|
||||
$userlookupURI += $extension
|
||||
|
||||
if ($global:kishorizondebug) {
|
||||
Write-Host $MyInvocation.MyCommand.Name Starting
|
||||
}
|
||||
|
||||
|
||||
if ($Id -ne "") {
|
||||
$extension = "/$($Id)?group_only=false"
|
||||
}
|
||||
|
||||
if ($UserName -ne "") {
|
||||
$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
|
||||
|
||||
|
||||
try {
|
||||
if ($global:kishorizondebug) {
|
||||
Write-Host URI: $userlookupURI
|
||||
}
|
||||
$users = Invoke-RestMethod -Method Get -Uri $userlookupURI -Headers $request_header -SkipCertificateCheck
|
||||
if ($global:kishorizondebug) {
|
||||
Write-Host $users
|
||||
}
|
||||
} catch {
|
||||
throw "ERROR accessing $userlookupURI`nStatusCode: $($_.Exception.Response.StatusCode.value__)`nStatusDescription: $($_.Exception.Response.StatusDescription)"
|
||||
}
|
||||
@@ -570,58 +603,6 @@ Function Get-HorizonADUser {
|
||||
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 {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
@@ -671,6 +652,163 @@ function Get-HorizonSessionConnectedLength {
|
||||
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 Get-HorizonVcenter
|
||||
@@ -685,4 +823,6 @@ Export-ModuleMember -Function Get-HorizonSessionLength
|
||||
Export-ModuleMember -Function Get-HorizonKeyAgreement
|
||||
Export-ModuleMember -Function Get-HorizonEntitlements
|
||||
Export-ModuleMember -Function Get-HorizonEntitlementDetail
|
||||
Export-ModuleMember -Function Add-HorizonMachineToPool
|
||||
Export-ModuleMember -Function Grant-HorizonDesktop
|
||||
|
||||
+44524
File diff suppressed because it is too large.
Load diff
@@ -8,8 +8,8 @@ param (
|
||||
)
|
||||
|
||||
# Variables
|
||||
$fileVersion = "1.3.3"
|
||||
$fileDate = "2026-08-04"
|
||||
$fileVersion = "1.3.5"
|
||||
$fileDate = "2026-08-05"
|
||||
$scriptName = Split-Path -Path $PSCommandPath -Leaf
|
||||
|
||||
# Display Script Banner
|
||||
@@ -26,7 +26,7 @@ function CheckModuleInstalled($moduleName, $command) {
|
||||
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()
|
||||
$response = (Read-Host "$moduleName module not instaled. Install now? (yes/no)").ToLower()
|
||||
}
|
||||
if ($response -eq "no") {
|
||||
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) {
|
||||
Write-Host
|
||||
Write-Host "Purpose: Generate a migration spreadsheet from vCenter VMs."
|
||||
@@ -76,6 +69,13 @@ if ($Help) {
|
||||
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"
|
||||
|
||||
$quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#requires -Modules posh-ssh
|
||||
##requires -Modules posh-ssh
|
||||
|
||||
param (
|
||||
[string]$vCenter = "",
|
||||
@@ -9,8 +9,8 @@ param (
|
||||
)
|
||||
|
||||
# Variables
|
||||
$FileVersion = "1.0.1"
|
||||
$FileDate = "2026-04-18"
|
||||
$FileVersion = "1.1.0"
|
||||
$FileDate = "2026-10-06"
|
||||
|
||||
# Display Script Banner
|
||||
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 "--------------------------------------------------" -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) {
|
||||
Write-Host
|
||||
Write-Host "Purpose: Run a CLI command on all hosts in a cluster."
|
||||
@@ -44,6 +69,8 @@ if ($Help) {
|
||||
exit
|
||||
}
|
||||
|
||||
CheckModuleInstalled -moduleName "posh-ssh" -command "New-SSHSession"
|
||||
|
||||
$quiet = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
|
||||
|
||||
if ($vCenter -eq "") {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user