Public/New-DMLunSnapshot.ps1

<#
.SYNOPSIS
    Creates an OceanStor LUN snapshot.
 
.DESCRIPTION
    Creates a LUN snapshot through the OceanStor snapshot REST resource.
    When SnapshotName is omitted or empty, the cmdlet generates a name from the source LUN name and a compact UTC tick serial.
    The source LUN can be identified by Name, Id, or a piped LUN object. SourceLunName and SourceLunId
    are mutually exclusive. SourceLunName is validated at parameter-binding time with tab completion;
    SourceLunId is validated too, but has no tab completion.
 
    Accepts multiple source LUNs from the pipeline.
    Each is snapshotted 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 LUNs from being processed.
 
.PARAMETER WebSession
    Optional session object returned by Connect-deviceManager. When omitted, the module's cached $script:CurrentOceanstorSession session is used.
 
.PARAMETER SnapshotName
    Optional name of the snapshot to create.
 
.PARAMETER SourceLunName
    Name of the source LUN. Valid values are checked against Get-DMlun and support tab completion. Mutually exclusive with SourceLunId.
 
.PARAMETER SourceLunId
    Id of the source LUN. Valid values are checked against Get-DMlun, no tab completion. Mutually exclusive with SourceLunName.
 
.PARAMETER Description
    Optional description of the snapshot.
 
.PARAMETER ReadOnly
    Optional switch to request a read-only snapshot.
 
.INPUTS
    System.Management.Automation.PSCustomObject
 
.OUTPUTS
    OceanstorLunSnapshot
    Returns the created LUN snapshot object on success, or the API error object on failure.
 
.EXAMPLE
    PS> New-DMLunSnapshot -SnapshotName 'db-before-patch' -SourceLunName 'production-db'
 
    Creates a named snapshot of the production-db LUN.
 
.EXAMPLE
    PS> New-DMLunSnapshot -WebSession $session -SnapshotName 'db-readonly' -SourceLunName 'production-db' -ReadOnly
 
    Creates a read-only snapshot using the supplied session.
 
.EXAMPLE
    PS> Get-DMLun 'production-db' | New-DMLunSnapshot
 
    Creates a snapshot of production-db via the pipeline.
 
.NOTES
    Filename: New-DMLunSnapshot.ps1
#>

function New-DMLunSnapshot {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]

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

        [AllowEmptyString()]
        [string]$SnapshotName,

        [Parameter(ValueFromPipeline = $true)]
        [psobject]$InputObject,

        [Parameter(Position = 0)]
        [Alias('Name')]
        [ValidateScript({
                if ($WebSession) {
                    $session = $WebSession
                }
                else {
                    $session = $script:CurrentOceanstorSession
                }

                $matchingLuns = @(Get-DMlun -WebSession $session -Name $_ | Where-Object Name -EQ $_)

                if ($matchingLuns.Count -eq 1) {
                    $true
                }
                elseif ($matchingLuns.Count -gt 1) {
                    throw "SourceLunName is ambiguous because more than one LUN is named '$_'."
                }
                else {
                    throw 'Invalid SourceLunName.'
                }
            })]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

                if ($fakeBoundParameters.ContainsKey('WebSession')) {
                    $session = $fakeBoundParameters.WebSession
                }
                else {
                    $session = $script:CurrentOceanstorSession
                }

                (Get-DMlun -WebSession $session).Name |
                    Sort-Object -Unique |
                    Where-Object { $_ -like "$wordToComplete*" }
            })]
        [string]$SourceLunName,

        [Parameter()]
        [Alias('Id')]
        [ValidateScript({
                $session = if ($WebSession) {
                    $WebSession
                }
                else {
                    $script:CurrentOceanstorSession
                }
                $matchingItems = @(Get-DMlun -WebSession $session -Id $_)
                if ($matchingItems.Count -eq 1) {
                    return $true
                }
                throw 'Invalid SourceLunId.'
            })]
        [string]$SourceLunId,

        [Parameter(Position = 1)]
        [string]$Description,

        [Parameter(Position = 2)]
        [switch]$ReadOnly
    )

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

            if ($InputObject -and ($PSBoundParameters.ContainsKey('SourceLunName') -or $PSBoundParameters.ContainsKey('SourceLunId'))) {
                throw 'Use either pipeline input or SourceLunName/SourceLunId, not both.'
            }
            if ($PSBoundParameters.ContainsKey('SourceLunName') -and $PSBoundParameters.ContainsKey('SourceLunId')) {
                throw 'SourceLunName and SourceLunId cannot be used together.'
            }

            if ($InputObject) {
                if ($InputObject.Id) {
                    $sourceLun = @(Get-DMlun -WebSession $session -Id $InputObject.Id)[0]
                    if ($null -eq $sourceLun) { throw "Could not resolve piped LUN Id '$($InputObject.Id)' - the object may have been removed." }
                }
                elseif ($InputObject.Name) {
                    $matchingLuns = @(Get-DMlun -WebSession $session -Name $InputObject.Name | Where-Object Name -EQ $InputObject.Name)
                    if ($matchingLuns.Count -eq 1) {
                        $sourceLun = $matchingLuns[0]
                    }
                    elseif ($matchingLuns.Count -gt 1) {
                        throw "Piped LUN name is ambiguous because more than one LUN is named '$($InputObject.Name)'."
                    }
                    else {
                        throw "Could not resolve piped LUN Name '$($InputObject.Name)' - the object may have been removed."
                    }
                }
                else {
                    throw 'Piped input must include an Id or Name property.'
                }
            }
            elseif ($PSBoundParameters.ContainsKey('SourceLunId')) {
                $sourceLun = @(Get-DMlun -WebSession $session -Id $SourceLunId)[0]
                if ($null -eq $sourceLun) { throw "Could not resolve 'SourceLunId' - the object may have been removed since parameter validation." }
            }
            elseif ($PSBoundParameters.ContainsKey('SourceLunName')) {
                $sourceLun = @(Get-DMlun -WebSession $session -Name $SourceLunName | Where-Object Name -EQ $SourceLunName)[0]
                if ($null -eq $sourceLun) { throw "Could not resolve 'SourceLunName' - the object may have been removed since parameter validation." }
            }
            else {
                throw 'SourceLunName, SourceLunId, or a piped LUN object is required.'
            }

            $body = @{
                TYPE       = 27
                PARENTTYPE = 11
                PARENTID   = $sourceLun.Id
            }

            if (-not [string]::IsNullOrWhiteSpace($SnapshotName)) {
                $body.Add('NAME', $SnapshotName)
            }
            else {
                $ticks = [DateTime]::UtcNow.Ticks
                $characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                $serial = ''
                do {
                    $remainder = [long]0
                    $ticks = [math]::DivRem($ticks, 36, [ref]$remainder)
                    $serial = $characters[[int]$remainder] + $serial
                } while ($ticks -gt 0)
                $body.Add('NAME', "$($sourceLun.Name)_SNAP_$serial")
            }

            if ($Description) {
                $body.Add('DESCRIPTION', $Description)
            }

            if ($ReadOnly) {
                $body.Add('isReadOnly', $true)
            }

            if ($PSCmdlet.ShouldProcess($sourceLun.Name, 'Create LUN snapshot')) {
                $response = Invoke-DeviceManager -WebSession $session -Method 'POST' -Resource 'snapshot' -BodyData $body
                $response = $response | Assert-DMApiSuccess

                if ($response.error.Code -eq 0) {
                    return [OceanstorLunSnapshot]::new($response.data, $session)
                }

                return $response.error
            }
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
    }
}