src/vm.ps1

# SPDX-License-Identifier: Apache-2.0

$script:XO_VM_FIELDS = "uuid,name_label,name_description,power_state,addresses,tags,memory,VIFs,snapshots,current_operations,auto_poweron,os_version,startTime,VCPUs_at_startup,CPUs,VCPUs_number,`$VBDs"

function ConvertTo-XoVmObject {
    <#
    .SYNOPSIS
        Convert a VM object from the API to a PowerShell object.
    .DESCRIPTION
        Convert a VM object from the API to a PowerShell object with proper properties and types.
    .PARAMETER InputObject
        The VM object from the API.
    #>

    [CmdletBinding()]
    [OutputType("XoPowershell.Vm")]
    param(
        [Parameter(Mandatory, ValueFromPipeline, Position = 0)]
        [PSObject] $InputObject
    )

    process {
        $props = @{
            VmUuid      = $InputObject.uuid
            Name        = $InputObject.name_label
            Description = $InputObject.name_description
            PowerState  = $InputObject.power_state
            OsVersion   = $InputObject.os_version
            Parent      = $InputObject.parent
            HostUuid    = $InputObject.$container
        }

        if ($null -ne $InputObject.CPUs) {
            if ($InputObject.CPUs.PSObject.Properties.Name -contains 'number') {
                $props | Add-Member -MemberType NoteProperty -Name CPUs -Value $InputObject.CPUs.number
            }
            elseif ($InputObject.CPUs.PSObject.Properties.Name -contains 'max') {
                $props | Add-Member -MemberType NoteProperty -Name CPUs -Value $InputObject.CPUs.max
            }
        }

        Set-XoObject $InputObject -TypeName XoPowershell.Vm -Properties $props
    }
}

function Get-XoSingleVmById {
    param (
        [string]$VmUuid
    )

    try {
        $uri = "$script:XoHost/rest/v0/vms/$VmUuid"
        $params = @{ fields = $script:XO_VM_FIELDS }
        $vmData = Invoke-RestMethod -Uri $uri @script:XoRestParameters -Body $params

        if ($vmData) {
            return ConvertTo-XoVmObject -InputObject $vmData
        }
    }
    catch {
        throw ("Failed to retrieve VM with UUID {0}: {1}" -f $VmUuid, $_)
    }
    return $null
}

function Get-XoVm {
    <#
    .SYNOPSIS
        Get VMs from Xen Orchestra.
    .DESCRIPTION
        Retrieves VMs from Xen Orchestra. Can retrieve specific VMs by their UUID
        or filter VMs by power state, tags, or custom filters.
    .PARAMETER VmUuid
        The UUID(s) of the VM(s) to retrieve.
    .PARAMETER PowerState
        Filter VMs by power state. Valid values: Running, Halted, Suspended.
    .PARAMETER Tag
        Filter VMs by tag.
    .PARAMETER Filter
        Custom filter to apply to the VM query.
    .PARAMETER Limit
        Maximum number of results to return. Default is 25 if not specified.
    .EXAMPLE
        Get-XoVm
        Returns up to 25 VMs.
    .EXAMPLE
        Get-XoVm -Limit 0
        Returns all VMs without limit.
    .EXAMPLE
        Get-XoVm -VmUuid "12345678-abcd-1234-abcd-1234567890ab"
        Returns the VM with the specified UUID.
    .EXAMPLE
        Get-XoVm -PowerState Running
        Returns running VMs (up to default limit).
    .EXAMPLE
        Get-XoVm -Tag "Production"
        Returns VMs tagged with "Production" (up to default limit).
    .EXAMPLE
        Get-XoVm -Filter "name_label:test*"
        Returns VMs with names starting with "test" (up to default limit).
    #>

    [CmdletBinding(DefaultParameterSetName = "Filter")]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = "VmUuid")]
        [ValidatePattern("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")]
        [Alias("VmId")]
        [string[]]$VmUuid,

        [Parameter(ParameterSetName = "Filter")]
        [ValidateSet("Running", "Halted", "Suspended")]
        [string[]]$PowerState,

        [Parameter(ParameterSetName = "Filter")]
        [string[]]$Tag,

        [Parameter(ParameterSetName = "Filter")]
        [string]$Filter,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Filter")]
        [ValidatePattern("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")]
        [string]$PoolUuid,

        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Filter")]
        [ValidatePattern("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")]
        [string]$HostUuid,

        [Parameter(ParameterSetName = "Filter")]
        [int]$Limit = $script:XoSessionLimit
    )

    begin {
        if (-not $script:XoHost -or -not $script:XoRestParameters) {
            throw ("Not connected to Xen Orchestra. Call Connect-XoSession first.")
        }

        $params = @{ fields = $script:XO_VM_FIELDS }
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq "VmUuid") {
            foreach ($id in $VmUuid) {
                Get-XoSingleVmById -VmUuid $id
            }
        }
    }

    end {
        if ($PSCmdlet.ParameterSetName -eq "Filter") {
            $AllFilters = $Filter

            if ($PowerState) {
                $AllFilters = "$AllFilters power_state:($($PowerState -join '|'))"
            }

            if ($Tag) {
                $AllFilters = "$AllFilters tags:($($Tag -join '&'))"
            }

            if ($PoolUuid) {
                $AllFilters = "$AllFilters `$pool:$PoolUuid"
            }

            if ($HostUuid) {
                $AllFilters = "$AllFilters `$container:$HostUuid"
            }

            if ($AllFilters) {
                Write-Verbose "Filter: $AllFilters"
                $params["filter"] = $AllFilters
            }

            if ($Limit) {
                $params['limit'] = $Limit
            }

            try {
                $uri = "$script:XoHost/rest/v0/vms"
                Write-Verbose "Getting VMs from $uri with parameters: $($params | ConvertTo-Json -Compress)"

                $response = Invoke-RestMethod -Uri $uri @script:XoRestParameters -Body $params

                if (!$response -or $response.Count -eq 0) {
                    Write-Verbose "No VMs found matching criteria"
                    return
                }

                Write-Verbose "Found $($response.Count) VMs"

                foreach ($vmItem in $response) {
                    ConvertTo-XoVmObject -InputObject $vmItem
                }
            }
            catch {
                throw ("Failed to list VMs. Error: {0}" -f $_)
            }
        }
    }
}

function Set-XoVm {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)]
        [Alias("VmId")]
        [string]$VmUuid,

        [Parameter()]
        [string]$Name,

        [Parameter()]
        [string]$Description,

        [Parameter()]
        [string[]]$Tags
    )

    $params = @{}

    if ($PSBoundParameters.ContainsKey("Name")) {
        $params["name_label"] = $Name
    }
    if ($PSBoundParameters.ContainsKey("Description")) {
        $params["name_description"] = $Description
    }
    if ($PSBoundParameters.ContainsKey("Tags")) {
        $params["tags"] = $Tags
    }

    if ($params.Count -gt 0) {
        $body = [System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json $params))
        Invoke-RestMethod -Uri "$script:XoHost/rest/v0/vms/$VmUuid" @script:XoRestParameters -Method Patch -ContentType "application/json" -Body $body
    }
}

function Get-XoVmVdi {
    <#
    .SYNOPSIS
        Get virtual disks attached to a VM.
    .DESCRIPTION
        Retrieves all virtual disk images (VDIs) attached to a specified VM.
    .PARAMETER VmUuid
        The UUID of the VM to get VDIs for.
    .EXAMPLE
        Get-XoVmVdi -VmUuid "12345678-abcd-1234-abcd-1234567890ab"
        Returns all virtual disks attached to the specified VM.
    .EXAMPLE
        Get-XoVm -PowerState Running | Get-XoVmVdi
        Returns all virtual disks attached to running VMs.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]$VmUuid
    )

    begin {
        $params = @{
            fields = $script:XO_VDI_FIELDS
        }
    }

    process {
        foreach ($id in $VmUuid) {
            (Invoke-RestMethod -Uri "$script:XoHost/rest/v0/vms/$id/vdis" @script:XoRestParameters -Body $params) | ConvertTo-XoVdiObject
        }
    }
}

function Start-XoVm {
    <#
    .SYNOPSIS
        Start one or more VMs.
    .DESCRIPTION
        Starts the specified VMs. Returns a task object that can be used to monitor
        the startup operation.
    .PARAMETER VmUuid
        The UUID(s) of the VM(s) to start.
    .EXAMPLE
        Start-XoVm -VmUuid "12345678-abcd-1234-abcd-1234567890ab"
        Starts the VM with the specified UUID.
    .EXAMPLE
        Get-XoVm -PowerState Halted | Start-XoVm
        Starts all halted VMs.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]$VmUuid
    )

    process {
        foreach ($id in $VmUuid) {
            if ($PSCmdlet.ShouldProcess($id, "start")) {
                Invoke-RestMethod -Uri "$script:XoHost/rest/v0/vms/$id/actions/start" -Method Post @script:XoRestParameters | ForEach-Object {
                    ConvertFrom-XoTaskHref $_
                }
            }
        }
    }
}

function Stop-XoVm {
    <#
    .SYNOPSIS
        Stop one or more VMs.
    .DESCRIPTION
        Stops the specified VMs. By default, performs a clean shutdown.
        Use -Force to perform a hard shutdown.
    .PARAMETER VmUuid
        The UUID(s) of the VM(s) to stop.
    .PARAMETER Force
        If specified, performs a hard shutdown instead of a clean shutdown.
    .EXAMPLE
        Stop-XoVm -VmUuid "12345678-abcd-1234-abcd-1234567890ab"
        Performs a clean shutdown of the VM with the specified UUID.
    .EXAMPLE
        Get-XoVm -PowerState Running | Stop-XoVm -Force
        Performs a hard shutdown of all running VMs.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]$VmUuid,
        [Parameter()][switch]$Force
    )

    begin {
        $action = if ($Force) { "hard_shutdown" } else { "clean_shutdown" }
    }

    process {
        foreach ($id in $VmUuid) {
            if ($PSCmdlet.ShouldProcess($id, $action)) {
                Invoke-RestMethod -Uri "$script:XoHost/rest/v0/vms/$id/actions/$action" -Method Post @script:XoRestParameters | ForEach-Object {
                    ConvertFrom-XoTaskHref $_
                }
            }
        }
    }
}

function Restart-XoVm {
    <#
    .SYNOPSIS
        Restart one or more VMs.
    .DESCRIPTION
        Restarts the specified VMs. By default, performs a clean reboot.
        Use -Force to perform a hard reboot.
    .PARAMETER VmUuid
        The UUID(s) of the VM(s) to restart.
    .PARAMETER Force
        If specified, performs a hard reboot instead of a clean reboot.
    .EXAMPLE
        Restart-XoVm -VmUuid "12345678-abcd-1234-abcd-1234567890ab"
        Performs a clean reboot of the VM with the specified UUID.
    .EXAMPLE
        Get-XoVm -PowerState Running | Restart-XoVm -Force
        Performs a hard reboot of all running VMs.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]$VmUuid,
        [Parameter()][switch]$Force
    )

    begin {
        $action = if ($Force) { "hard_reboot" } else { "clean_reboot" }
    }

    process {
        foreach ($id in $VmUuid) {
            if ($PSCmdlet.ShouldProcess($id, $action)) {
                Invoke-RestMethod -Uri "$script:XoHost/rest/v0/vms/$id/actions/$action" -Method Post @script:XoRestParameters | ForEach-Object {
                    ConvertFrom-XoTaskHref $_
                }
            }
        }
    }
}

function Suspend-XoVm {
    <#
    .SYNOPSIS
        Suspend one or more VMs.
    .DESCRIPTION
        Suspends the specified VMs.
    .PARAMETER VmUuid
        The UUID(s) of the VM(s) to suspend.
    .EXAMPLE
        Suspend-XoVm -VmUuid "12345678-abcd-1234-abcd-1234567890ab"
        Suspends the VM with the specified UUID.
    .EXAMPLE
        Get-XoVm -PowerState Running | Suspend-XoVm
        Suspends all running VMs.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]$VmUuid
    )

    process {
        foreach ($id in $VmUuid) {
            if ($PSCmdlet.ShouldProcess($id, "suspend")) {
                Invoke-RestMethod -Uri "$script:XoHost/rest/v0/vms/$id/actions/suspend" -Method Post @script:XoRestParameters | ForEach-Object {
                    ConvertFrom-XoTaskHref $_
                }
            }
        }
    }
}