Public/Save-PVEApplianceTemplate.ps1

function Save-PVEApplianceTemplate {
  <#
  .SYNOPSIS
  Downloads container templates from the PVE appliance catalogue onto a storage.
  .DESCRIPTION
  Starts each download with POST /nodes/{node}/aplinfo, waits for the tasks, and writes each stored
  template as a PVEPS.StorageContent object, ready for New-PVEContainer -OSTemplate.
  .PARAMETER Template
  Template file name(s), as Get-PVEApplianceTemplate lists them. Binds from piped objects.
  .PARAMETER Storage
  Storage to save the templates on. It must allow container templates (vztmpl).
  .PARAMETER Node
  Node to download on. Binds from piped objects; can be omitted on a single-node server.
  .PARAMETER RunAsync
  Return the task(s) at once instead of waiting.
  .PARAMETER Server
  Connection(s) to use. Defaults to the default connection; binds from piped objects.
  .EXAMPLE
  Save-PVEApplianceTemplate -Template debian-13-standard_13.1-2_amd64.tar.zst -Storage local
  .EXAMPLE
  $t = Get-PVEApplianceTemplate 'debian-13-standard*' | Save-PVEApplianceTemplate -Storage local
  New-PVEContainer -Name ct01 -OSTemplate $t.Volume -Storage local-lvm
  #>

  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
  [OutputType('PVEPS.StorageContent', 'PVEPS.Task')]
  param(
    [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [string[]]$Template,

    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$Storage,

    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateNotNullOrEmpty()]
    [string]$Node,

    [switch]$RunAsync,

    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Connection')]
    [object[]]$Server
  )

  begin {
    $operations = [System.Collections.Generic.List[psobject]]::new()
  }

  process {
    try {
      $connections = @(Resolve-PVEConnection -Server $Server)
    }
    catch {
      $PSCmdlet.WriteError($_)
      return
    }
    foreach ($connection in $connections) {
      try {
        $nodeName = Resolve-PVENodeName -Connection $connection -Node $Node
      }
      catch {
        $PSCmdlet.WriteError($_)
        continue
      }
      foreach ($name in $Template) {
        if (-not $PSCmdlet.ShouldProcess("$Storage on $nodeName", "Download appliance template $name")) {
          continue
        }
        $path = Format-PVEPath '/nodes/{node}/aplinfo' @{ node = $nodeName }
        try {
          $upid = [string](Invoke-PVERequest -Connection $connection -Method POST -Path $path -Parameters @{ storage = $Storage; template = $name })
        }
        catch {
          $PSCmdlet.WriteError($_)
          continue
        }
        $operations.Add([pscustomobject]@{ Upid = $upid; Connection = $connection; Node = $nodeName; Template = $name })
      }
    }
  }

  end {
    if ($RunAsync) {
      foreach ($op in $operations) {
        if ($op.Upid -match '^UPID:') {
          Get-PVETask -Upid $op.Upid -Server $op.Connection
        }
      }
      return
    }
    foreach ($op in @(Wait-PVEOperation -Operation $operations)) {
      try {
        Find-PVEStoredFile -Connection $op.Connection -Node $op.Node -Storage $Storage -Content vztmpl -FileName $op.Template
      }
      catch {
        $PSCmdlet.WriteError($_)
      }
    }
  }
}