Public/Add-DMPortToPortGroup.ps1

<#
.SYNOPSIS
    Associates an OceanStor port with a port group.
 
.DESCRIPTION
    Adds an existing front-end port or logical port to an existing port group.
    The cmdlet resolves candidate ports by PortType, validates the selected port and group, then calls the OceanStor API. It supports -WhatIf and -Confirm.
 
    Accepts multiple ports from the pipeline by property name (all piped ports must share the same
    PortType). Each port is resolved and associated independently: a failure (e.g. an invalid/ambiguous
    name, or a REST error) is reported as a non-terminating error and does not stop the remaining ports
    from being processed.
 
.PARAMETER WebSession
    Optional session object returned by Connect-deviceManager. When omitted, the module's cached $script:CurrentOceanstorSession session is used.
 
.PARAMETER PortGroupName
    Name of the port group that will receive the port.
 
.PARAMETER PortType
    Type of port to add to the port group. Valid values are FibreChannel, Ethernet, and LogicalPort.
 
.PARAMETER PortName
    Name of the port to add to the port group. Valid values are resolved from the selected PortType.
 
.PARAMETER VstoreId
    Optional vStore ID used to scope the association operation.
 
.INPUTS
    System.Management.Automation.PSCustomObject
 
.OUTPUTS
    System.Management.Automation.PSCustomObject
    Returns the OceanStor API error object.
 
.EXAMPLE
    PS> Add-DMPortToPortGroup -PortGroupName 'fc-front-end' -PortType FibreChannel -PortName 'CTE0.A.IOM0.P0' -WhatIf
 
    Shows what would happen if the Fibre Channel port were added to the fc-front-end port group.
 
.NOTES
    Filename: Add-DMPortToPortGroup.ps1
#>

function Add-DMPortToPortGroup {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [pscustomobject]$WebSession,

        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                $session = if ($fakeBoundParameters.ContainsKey('WebSession')) {
                    $fakeBoundParameters.WebSession
                }
                else {
                    $script:CurrentOceanstorSession
                }
                (Get-DMPortGroup -WebSession $session).Name | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" }
            })]
        [string]$PortGroupName,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateSet('FibreChannel', 'Ethernet', 'LogicalPort')]
        [string]$PortType,

        [Parameter(Mandatory = $true, Position = 2, ValueFromPipelineByPropertyName = $true)]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                if (-not $fakeBoundParameters.ContainsKey('PortType')) {
                    return
                }
                $session = if ($fakeBoundParameters.ContainsKey('WebSession')) {
                    $fakeBoundParameters.WebSession
                }
                else {
                    $script:CurrentOceanstorSession
                }
                # Get-DMPortGroupCandidate is private and would not resolve here -- confirmed
                # empirically that an ArgumentCompleter scriptblock invoked by the real completion
                # engine can only reliably call already-public commands -- so its per-PortType
                # lookup is inlined directly instead of exposing it as a public command.
                $names = switch ($fakeBoundParameters.PortType) {
                    'FibreChannel' { (Get-DMPortFc -WebSession $session).Name }
                    'Ethernet' { (Get-DMPortETH -WebSession $session).Name }
                    'LogicalPort' { (Get-DMLif -WebSession $session).'LIF Name' }
                }
                $names | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" }
            })]
        [string]$PortName,

        [string]$VstoreId
    )

    process {
        try {
            $session = if ($WebSession) {
                $WebSession
            }
            else {
                $script:CurrentOceanstorSession
            }

            $groups = @(Get-DMPortGroup -WebSession $session)
            $matchingGroups = @($groups | Where-Object Name -EQ $PortGroupName)
            if ($matchingGroups.Count -eq 0) {
                throw "Invalid PortGroupName. Valid values are: $($groups.Name -join ', ')"
            }
            if ($matchingGroups.Count -gt 1) {
                throw "PortGroupName is ambiguous because more than one port group is named '$PortGroupName'."
            }
            $group = $matchingGroups[0]

            $ports = @(Get-DMPortGroupCandidate -WebSession $session -PortType $PortType)
            $matchingPorts = @($ports | Where-Object Name -EQ $PortName)
            if ($matchingPorts.Count -eq 0) {
                throw "Invalid PortName for $PortType. Valid values are: $($ports.Name -join ', ')"
            }
            if ($matchingPorts.Count -gt 1) {
                throw "PortName is ambiguous because more than one $PortType port is named '$PortName'."
            }
            $port = $matchingPorts[0]

            $body = @{
                ID               = $group.Id
                ASSOCIATEOBJTYPE = $port.ObjectType
                ASSOCIATEOBJID   = $port.Id
            }
            if ($VstoreId) {
                $body.vstoreId = $VstoreId
            }

            if ($PSCmdlet.ShouldProcess("$PortName -> $PortGroupName", 'Associate port with port group')) {
                $response = Invoke-DeviceManager -WebSession $session -Method 'POST' -Resource 'port/associate/portgroup' -BodyData $body
                $response = $response | Assert-DMApiSuccess
                return $response.error
            }
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
    }
}