Public/Set-DMLif.ps1

function Set-DMLif {
    <#
    .SYNOPSIS
        Modifies an OceanStor logical interface port.
 
    .DESCRIPTION
        The target is addressed by -Id or -Name and the modify is always issued as
        PUT lif/{id}, with the ID carried in the URL path. The array's lif modify
        interface rejects a body that repeats the object's own identity (ID+NAME)
        with error 1077948993 "The object name already exists", so identity is kept
        out of the body entirely -- the ID in the path is the sole target reference.
        When the target is addressed by -Name, the name is first resolved to its ID
        through the documented exact NAME filter.
 
        To rename an interface, pass -NewName; it is the only value that populates the
        body NAME field. The addressing -Name never enters the body.
 
        A single interface is modified per call. Piping more than one interface
        (e.g. Get-DMLif 'nas*' | Set-DMLif ...) is rejected before any request is
        sent, because one call carries a single set of changes (for example, one IP
        address) that cannot sensibly be fanned out across several interfaces.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [pscustomobject]$WebSession,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$Id,

        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias('LIF Name')]
        [ValidateLength(1, 255)]
        [ValidatePattern('^[A-Za-z0-9_.-]+$')]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                $session = if ($fakeBoundParameters.ContainsKey('WebSession')) {
                    $fakeBoundParameters.WebSession
                }
                else {
                    $script:CurrentOceanstorSession
                }
                (Get-DMLif -WebSession $session).'LIF Name' | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" }
            })]
        [string]$Name,

        [ValidateLength(1, 255)]
        [ValidatePattern('^[A-Za-z0-9_.-]+$')]
        [string]$NewName,

        [string]$IPv4Address,
        [string]$IPv4Mask,
        [string]$IPv4Gateway,
        [string]$IPv6Address,
        [string]$IPv6Mask,
        [string]$IPv6Gateway,
        [ValidateSet(1, 7, 8, 25, 26)]
        [int]$HomePortType,
        [string]$HomePortId,
        [string]$HomePortName,
        [string]$CurrentPortId,
        [bool]$OperationalStatus,
        [ValidateSet(0, 1)]
        [int]$AddressFamily,
        [bool]$IsPrivate,
        [string]$FailoverGroupId,
        [bool]$CanFailover,
        [ValidateSet(1, 2)]
        [int]$FailbackMode,
        [ValidateSet(0, 1, 2)]
        [int]$DdnsStatus,
        [string]$DnsZoneName,
        [ValidateSet(0, 1)]
        [int]$ListenDnsQueryEnabled,
        [string]$HomeControllerId,
        [string]$HomeSiteWwn,
        [ValidateSet(1)]
        [int]$RemoveFromDnsZone
    )

    begin {
        # Targets are collected across the pipeline and acted on in end{} so a
        # multi-object pipe can be rejected before any modify is sent -- see the
        # count guard below.
        $targets = [System.Collections.Generic.List[object]]::new()
    }

    process {
        try {
            if (-not $PSBoundParameters.ContainsKey('Id') -and -not $PSBoundParameters.ContainsKey('Name')) {
                throw 'Specify -Id or -Name to identify the logical interface to modify.'
            }
            # Snapshot this pipeline item's identity; the actual modify happens in end{}.
            $targets.Add([pscustomobject]@{
                    Id   = if ($PSBoundParameters.ContainsKey('Id')) { $Id } else { $null }
                    Name = if ($PSBoundParameters.ContainsKey('Name')) { $Name } else { $null }
                })
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
    }

    end {
        try {
            if ($targets.Count -eq 0) {
                # Nothing bound and nothing piped (or an empty pipeline): no-op.
                return
            }
            if ($targets.Count -gt 1) {
                throw "Set-DMLif received $($targets.Count) logical interfaces from the pipeline. A modify call carries a single set of changes (for example, one IP address), so it cannot be fanned out across multiple interfaces -- this is almost certainly a mistake. Pipe a single interface, or call Set-DMLif once per interface."
            }
            $target = $targets[0]

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

            # Identity (Id/Name) is deliberately absent from this map: the ID is carried
            # in the URL path (PUT lif/{id}) and echoing it -- or the current NAME -- in
            # the body makes the array reject the payload with 1077948993. Only -NewName
            # populates the body NAME field, and only when a rename is requested.
            $body = ConvertTo-DMRequestBody -BoundParameters $PSBoundParameters -Map @{
                NewName               = 'NAME'
                IPv4Address           = 'IPV4ADDR'
                IPv4Mask              = 'IPV4MASK'
                IPv4Gateway           = 'IPV4GATEWAY'
                IPv6Address           = 'IPV6ADDR'
                IPv6Mask              = 'IPV6MASK'
                IPv6Gateway           = 'IPV6GATEWAY'
                HomePortType          = 'HOMEPORTTYPE'
                HomePortId            = 'HOMEPORTID'
                HomePortName          = 'HOMEPORTNAME'
                CurrentPortId         = 'CURRENTPORTID'
                OperationalStatus     = 'OPERATIONALSTATUS'
                AddressFamily         = 'ADDRESSFAMILY'
                IsPrivate             = 'ISPRIVATE'
                FailoverGroupId       = 'FAILOVERGROUPID'
                CanFailover           = 'CANFAILOVER'
                FailbackMode          = 'FAILBACKMODE'
                DdnsStatus            = 'ddnsStatus'
                DnsZoneName           = 'dnsZoneName'
                ListenDnsQueryEnabled = 'listenDnsQueryEnabled'
                HomeControllerId      = 'HOMECONTROLLERID'
                HomeSiteWwn           = 'HOMESITEWWN'
                RemoveFromDnsZone     = 'removeFromDnsZone'
            }

            # The REST modify interface treats ADDRESSFAMILY as a mandatory body field
            # whenever the addressing changes: an IPv4 edit must carry ADDRESSFAMILY=0 and
            # an IPv6 edit must carry ADDRESSFAMILY=1, or the array rejects the payload.
            # Derive it from the address being changed unless the caller pinned
            # -AddressFamily explicitly (in which case their value is authoritative).
            if (-not $PSBoundParameters.ContainsKey('AddressFamily')) {
                $changingIPv4 = $PSBoundParameters.ContainsKey('IPv4Address')
                $changingIPv6 = $PSBoundParameters.ContainsKey('IPv6Address')
                if ($changingIPv4 -and $changingIPv6) {
                    throw 'Cannot change both -IPv4Address and -IPv6Address in one request; the mandatory ADDRESSFAMILY body field is single-valued. Specify -AddressFamily (0 for IPv4, 1 for IPv6) to disambiguate.'
                }
                elseif ($changingIPv4) {
                    $body['ADDRESSFAMILY'] = 0
                }
                elseif ($changingIPv6) {
                    $body['ADDRESSFAMILY'] = 1
                }
            }

            $displayTarget = if ($target.Name) { $target.Name } else { $target.Id }
            if ($PSCmdlet.ShouldProcess($displayTarget, 'Modify logical interface')) {
                # Resolve the target to an ID for the URL path. Prefer an explicit -Id;
                # otherwise resolve -Name through the documented exact NAME filter. The
                # ID becomes the sole target reference (PUT lif/{id}); it never enters
                # the body.
                if ($target.Id) {
                    $targetId = $target.Id
                }
                else {
                    $lookup = "lif?filter=NAME::$([uri]::EscapeDataString($target.Name))"
                    $found = @(Invoke-DeviceManager -WebSession $session -Method 'GET' -Resource $lookup | Select-DMResponseData)
                    if ($found.Count -eq 0 -or -not $found[0].ID) {
                        throw "No logical interface named '$($target.Name)' was found; the modify request was not sent."
                    }
                    if ($found.Count -gt 1) {
                        throw "Multiple logical interfaces named '$($target.Name)' were found; specify -Id to disambiguate."
                    }
                    $targetId = [string]$found[0].ID
                }

                $response = Invoke-DeviceManager -WebSession $session -Method 'PUT' -Resource "lif/$([uri]::EscapeDataString($targetId))" -BodyData $body
                $response = $response | Assert-DMApiSuccess
                return $response.error
            }
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
    }
}