StorageGRID-Webscale.psm1

$SGW_PROFILE_PATH = "$HOME/.sgw/"
$SGW_CREDENTIALS_FILE = $SGW_PROFILE_PATH + "credentials"

# workarounds for PowerShell issues
if ($PSVersionTable.PSVersion.Major -lt 6) {
    Add-Type @"
        using System.Net;
        using System.Security.Cryptography.X509Certificates;
        public class TrustAllCertsPolicy : ICertificatePolicy {
           public bool CheckValidationResult(
                ServicePoint srvPoint, X509Certificate certificate,
                WebRequest request, int certificateProblem) {
                return true;
            }
        }
"@


    # StorageGRID supports TLS 1.2 and PowerShell does not auto negotiate it, thus enforcing TLS 1.2
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    # Using .NET JSON Serializer as JSON serialization included in Invoke-RestMethod has a length restriction for JSON content
    Add-Type -AssemblyName System.Web.Extensions
    $global:javaScriptSerializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
    $global:javaScriptSerializer.MaxJsonLength = [System.Int32]::MaxValue
    $global:javaScriptSerializer.RecursionLimit = 99

    # Functions necessary to parse JSON output from .NET serializer to PowerShell Objects
    function ParseItem($jsonItem) {
        if ($jsonItem.PSObject.TypeNames -match "Array") {
            return ParseJsonArray($jsonItem)
        }
        elseif ($jsonItem.PSObject.TypeNames -match "Dictionary") {
            return ParseJsonObject([HashTable]$jsonItem)
        }
        else {
            return $jsonItem
        }
    }

    function ParseJsonObject($jsonObj) {
        $Response = New-Object -TypeName PSCustomObject
        foreach ($key in $jsonObj.Keys) {
            $item = $jsonObj[$key]
            if ($item) {
                $parsedItem = ParseItem $item
            }
            else {
                $parsedItem = $null
            }
            $Response | Add-Member -MemberType NoteProperty -Name $key -Value $parsedItem
        }
        return $Response
    }

    function ParseJsonArray($jsonArray) {
        $Response = @()
        $jsonArray | ForEach-Object {
            $Response += ,(ParseItem $_)
        }
        return $Response
    }

    function ParseJsonString($json) {
        $config = $javaScriptSerializer.DeserializeObject($json)
        if ($config -is [Array]) {
            return ParseJsonArray($config)
        }
        else {
            return ParseJsonObject($config)
        }
    }
}

### Helper Functions ###

function ParseErrorForResponseBody($Error) {
    if ($PSVersionTable.PSVersion.Major -lt 6) {
        if ($Error.Exception.Response) {
            $Reader = New-Object System.IO.StreamReader($Error.Exception.Response.GetResponseStream())
            $Reader.BaseStream.Position = 0
            $Reader.DiscardBufferedData()
            $ResponseBody = $Reader.ReadToEnd()
            if ( $ResponseBody.StartsWith('{')) {
                $ResponseBody = $ResponseBody | ConvertFrom-Json | ConvertTo-Json
            }
            return $ResponseBody
        }
    }
    else {
        return $Error.ErrorDetails.Message
    }
}

# helper function to convert datetime to unix timestamp
function ConvertTo-UnixTimestamp {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $True,
                Position = 0,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Date to be converted.")][DateTime[]]$Date,
        [parameter(Mandatory = $True,
                Position = 1,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Unit of timestamp.")][ValidateSet("Seconds", "Milliseconds")][String]$Unit = "Milliseconds"
    )

    BEGIN {
        $epoch = Get-Date -Year 1970 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0
    }

    PROCESS {
        if ($Unit = "Seconds") {
            Write-Output ([math]::truncate($Date.ToUniversalTime().Subtract($epoch).TotalSeconds))
        }
        else {
            Write-Output ([math]::truncate($Date.ToUniversalTime().Subtract($epoch).TotalMilliSeconds))
        }
    }
}

# helper function to convert unix timestamp to datetime
function ConvertFrom-UnixTimestamp {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $True,
                Position = 0,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Timestamp to be converted.")][String]$Timestamp,
        [parameter(Mandatory = $True,
                Position = 0,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Unit of timestamp.")][ValidateSet("Seconds", "Milliseconds")][String]$Unit = "Milliseconds",
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "Optional Timezone to be used as basis for Timestamp. Default is system Timezone.")][System.TimeZoneInfo]$Timezone = [System.TimeZoneInfo]::Local
    )

    PROCESS {
        $Timestamp = @($Timestamp)
        foreach ($Timestamp in $Timestamp) {
            if ($Unit -eq "Seconds") {
                $Date = [System.TimeZoneInfo]::ConvertTimeFromUtc(([datetime]'1/1/1970').AddSeconds($Timestamp), $Timezone)
            }
            else {
                $Date = [System.TimeZoneInfo]::ConvertTimeFromUtc(([datetime]'1/1/1970').AddMilliseconds($Timestamp), $Timezone)
            }
            Write-Output $Date
        }
    }
}


function ConvertFrom-SgwConfigFile {
    #private
    [CmdletBinding()]

    PARAM (
        [parameter(
            Mandatory=$True,
            Position=0,
            HelpMessage="StorageGRID Config File")][String]$SgwConfigFile
    )

    Process {
        # TODO: Verify that folder is only readable by current user

        if (!(Test-Path $SgwConfigFile))
        {
            throw "Config file $SgwConfigFile does not exist!"
        }

        Write-Verbose "Reading StorageGRID Configuration from $SgwConfigFile"

        $Content = Get-Content -Path $SgwConfigFile -Raw
        # convert to JSON structure
        # replace all carriage returns
        $Content = $Content -replace "\r",""
        # remove empty lines
        $Content = $Content -replace "(\n$)*", ""
        # remove profile string from profile section
        $Content = $Content -replace "profile ", ""

        # replace key value pairs with quoted key value pairs and replace = with :
        $Content = $Content -replace "\n\s*([^=^\s^`"]+)\s*=\s*([^\s^\n]*)","`n`"`$1`":`"`$2`","

        # make sure that Profile is a Key Value inside the JSON Object
        $Content = $Content -replace "\[([^\]]+)\]([^\[]+)","{`"ProfileName`":`"`$1`",`$2},`n"

        # remove additional , before a closing curly bracket
        $Content = $Content -replace "\s*,\s*\n?}","}"

        # ensure that the complete output is an array consisting of multiple JSON objects
        $Content = $Content -replace "\A","["
        $Content = $Content -replace "},?\s*\n?\s*\z","}]"

        $Config = ConvertFrom-Json -InputObject $Content
        Write-Output $Config
    }
}

function ConvertTo-SgwConfigFile {
    #private
    [CmdletBinding()]

    PARAM (
        [parameter(
            Mandatory=$True,
            Position=0,
            HelpMessage="Configs to store in config file")][PSCustomObject]$Configs,
        [parameter(
            Mandatory=$True,
            Position=1,
            HelpMessage="StorageGRID Config File")][String]$SgwConfigFile
    )

    Process {
        # TODO: Verify that folder is only readable by current user

        if (!(Test-Path $SgwConfigFile)) {
            New-Item -Path $SgwConfigFile -ItemType File -Force
        }

        $SgwConfigDirectory = ([System.IO.DirectoryInfo]$SgwConfigFile).Parent.FullName

        # make sure that parent folder is only accessible by current user
        try {
            if ([environment]::OSVersion.Platform -match "win") {
                $Acl = Get-Acl -Path $SgwConfigDirectory
                # remove inheritance
                $Acl.SetAccessRuleProtection($true,$false)
                $AcessRule = [System.Security.AccessControl.FileSystemAccessRule]::new(
                    $env:USERNAME,"FullControl",
                    ([System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit),
                    [System.Security.AccessControl.PropagationFlags]::None,
                    [System.Security.AccessControl.AccessControlType]::Allow)
                $Acl.AddAccessRule($AcessRule)
                Set-Acl -Path $SgwConfigDirectory -AclRule
            }
            else {
                Invoke-Expression "chmod 700 $SgwConfigDirectory"
            }
        }
        catch {
            Write-Warning "Couldn't restrict access to directory $SgwConfigDirectory"
        }

        Write-Verbose "Writing StorageGRID Configuration to $SgwConfigFile"

        if ($SgwConfigFile -match "credentials$") {
            foreach ($Config in $Configs) {
                $Output += "[$( $Config.ProfileName )]`n"
                $Output += "username = $($Config.username)`n"
                $Output += "password = $($Config.password)`n"
            }
        }
        else {
            foreach ($Config in $Configs) {
                if ($Config.ProfileName -eq "default") {
                    $Output += "[$( $Config.ProfileName )]`n"
                }
                else {
                    $Output += "[profile $( $Config.ProfileName )]`n"
                }
                $Properties = $Config.PSObject.Members | Where-Object { $_.MemberType -eq "NoteProperty" -and $_.Name -ne "ProfileName" -and $_.Value -isnot [PSCustomObject] }
                $Sections = $Config.PSObject.Members | Where-Object { $_.MemberType -eq "NoteProperty" -and $_.Name -ne "ProfileName" -and $_.Value -is [PSCustomObject]}
                foreach ($Property in $Properties) {
                    $Output += "$($Property.Name) = $($Property.Value)`n"
                }
                foreach ($Section in $Sections) {
                    $Output += "$($Section.Name) =`n"
                    $Properties = $Section.Value.PSObject.Members | Where-Object { $_.MemberType -eq "NoteProperty" }
                    foreach ($Property in $Properties) {
                        $Output += " $($Property.Name) = $($Property.Value)`n"
                    }
                }
            }
        }
        Write-Debug "Output:`n$Output"
        $Output | Out-File -FilePath $SgwConfigFile -NoNewline
    }
}

## function to trigger request to StorageGRID Webscale Server ##

function Invoke-SgwRequest {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $True,
                Position = 0,
                HelpMessage = "Uri")][Uri]$Uri,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "WebSession")][Microsoft.PowerShell.Commands.WebRequestSession]$WebSession,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "HTTP Method")][ValidateSet("Default", "Get", "Head", "Post", "Put", "Delete", "Trace", "Options", "Merge", "Patch")][String]$Method = "Get",
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "Headers")][Hashtable]$Headers,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "Body")][Object]$Body,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "Content Type")][String]$ContentType = "application/json",
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "Variable to store session details in")][String]$SessionVariable,
        [parameter(Mandatory = $False,
                Position = 7,
                HelpMessage = "Timeout in seconds")][Int]$TimeoutSec = 60,
        [parameter(Mandatory = $False,
                Position = 8,
                HelpMessage = "Skip certificate checks")][Switch]$SkipCertificateCheck,
        [parameter(
                Mandatory=$False,
                Position=9,
                HelpMessage="File to output result to")][System.IO.DirectoryInfo]$OutFile
    )

    Process {
        # if $OutFile is a directory, create a temporary file and save content to temporary file
        # and later rename the file according to the Content-Disposition header or
        $OutPath = $null
        if ($OutFile -and (Test-Path -PathType Container -Path $OutFile)) {
            $OutPath = $OutFile.PSObject.Copy()
            $OutFile = (New-TemporaryFile).ToString()
        }

        try {
            if ($PSVersionTable.PSVersion.Major -lt 6) {
                if ($SkipCertificateCheck.isPresent) {
                    $CurrentCertificatePolicy = [System.Net.ServicePointManager]::CertificatePolicy
                    [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
                }
                if ($Body) {
                    Write-Verbose "Body:`n$Body"
                    if ($SessionVariable) {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -SessionVariable $SessionVariable -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -SessionVariable $SessionVariable
                        }
                        $Response | Add-Member  -MemberType NoteProperty -Name $SessionVariable -Value (Get-Variable -Name $SessionVariable -ValueOnly)
                    }
                    else {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -WebSession $WebSession -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -WebSession $WebSession
                        }
                    }
                }
                else {
                    if ($SessionVariable) {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -SessionVariable $SessionVariable -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -SessionVariable $SessionVariable
                        }
                        $Response | Add-Member  -MemberType NoteProperty -Name $SessionVariable -Value (Get-Variable -Name $SessionVariable -ValueOnly) -PassThru
                    }
                    else {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -WebSession $WebSession -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -TimeoutSec $TimeoutSec -WebSession $WebSession
                        }
                    }
                }
                if ($SkipCertificateCheck.isPresent) {
                    [System.Net.ServicePointManager]::CertificatePolicy = $CurrentCertificatePolicy
                }
            }
            else {
                if ($Body) {
                    Write-Verbose "Body:`n$Body"
                    if ($SessionVariable) {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -SessionVariable $SessionVariable -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -SessionVariable $SessionVariable
                        }
                        $Response | Add-Member  -MemberType NoteProperty -Name $SessionVariable -Value (Get-Variable -Name $SessionVariable -ValueOnly) -PassThru
                    }
                    else {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -WebSession $WebSession -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -Body ([System.Text.Encoding]::UTF8.GetBytes($Body)) -ContentType $ContentType -WebSession $WebSession
                        }
                    }
                }
                else {
                    if ($SessionVariable) {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -SessionVariable $SessionVariable -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -SessionVariable $SessionVariable
                        }
                        $Response | Add-Member  -MemberType NoteProperty -Name $SessionVariable -Value (Get-Variable -Name $SessionVariable -ValueOnly)
                    }
                    else {
                        if ($OutFile) {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -WebSession $WebSession -OutFile $OutFile -PassThru
                        }
                        else {
                            $Response = Invoke-WebRequest -Method $Method -Uri $Uri -Headers $Headers -SkipCertificateCheck:$SkipCertificateCheck -SkipHeaderValidation -TimeoutSec $TimeoutSec -WebSession $WebSession
                        }
                    }
                }
            }

            switch ($Response.Headers.'Content-Type') {
                'application/json' {
                    $Response | Add-Member -MemberType NoteProperty -Name Json -Value (ConvertFrom-Json -InputObject $Response.Content)
                }
            }

            if ($OutPath) {
                if ($Response.Headers.'Content-Disposition' -match "filename") {
                    $FileName = ($Response.Headers.'Content-Disposition' | Select-Object -Last 1) -replace '.*filename="*(.*)"*','$1'
                }
                else {
                    # use last part of Request URI as filename
                    $FileName = $Response.BaseResponse.RequestMessage.RequestUri.Segments | Select-Object -Last 1
                    if ($FileName -eq "/") {
                        $FileName = "index.html"
                    }
                }
                $Destination = Join-Path -Path $OutPath -ChildPath $FileName
                Move-Item -Path $OutFile -Destination $Destination -ErrorAction Stop
                Write-Host "Output written to $Destination"
            }

            return $Response
        }
        catch {
            # TODO: handle errors
            Throw $_
        }
        finally {
            if ($OutFile) {
                Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
            }
        }
    }
}

### Cmdlets ###

## profile ##

# TODO: Implement separate Update Cmdlet which does not overwrite values with defaults

Set-Alias -Name Set-SgwProfile -Value Add-SgwProfile
Set-Alias -Name New-SgwProfile -Value Add-SgwProfile
Set-Alias -Name Update-SgwProfile -Value Add-SgwProfile
Set-Alias -Name Set-SgwCredential -Value Add-SgwProfile
Set-Alias -Name New-SgwCredential -Value Add-SgwProfile
Set-Alias -Name Add-SgwCredential -Value Add-SgwProfile
Set-Alias -Name Update-SgwCredential -Value Add-SgwProfile
<#
    .SYNOPSIS
    Add StorageGRID Credentials
    .DESCRIPTION
    Add StorageGRID Credentials
    .PARAMETER ProfileName
    StorageGRID Profile to use which contains StorageGRID sredentials and settings
    .PARAMETER ProfileLocation
    StorageGRID Profile location if different than .aws/credentials
    .PARAMETER Name
    The name of the StorageGRID Webscale Management Server. This value may also be a string representation of an IP address. If not an address, the name must be resolvable to an address.
    .PARAMETER Credential
    A System.Management.Automation.PSCredential object containing the credentials needed to log into the StorageGRID Webscale Management Server.
    .PARAMETER SkipCertificateCheck
    If the StorageGRID Webscale Management Server certificate cannot be verified, the connection will fail. Specify -SkipCertificateCheck to skip the validation of the StorageGRID Webscale Management Server certificate.
    .PARAMETER AccountId
    Account ID of the StorageGRID Webscale tenant to connect to.
    .PARAMETER DisableAutomaticAccessKeyGeneration
    By default StorageGRID automatically generates S3 Access Keys if required to carry out S3 operations. With this switch, automatic S3 Access Key generation will not be done.
    .PARAMETER TemporaryAccessKeyExpirationTime
    Time in seconds until automatically generated temporary S3 Access Keys expire (default 3600 seconds).
    .PARAMETER S3EndpointUrl
    S3 Endpoint URL to be used.
    .PARAMETER SwiftEndpointUrl
    Swift Endpoint URL to be used.
#>

function Global:Add-SgwProfile {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory=$False,
                Position=0,
                ValueFromPipelineByPropertyName=$True,
                HelpMessage="StorageGRID Profile to use which contains StorageGRID sredentials and settings")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory=$False,
                Position=1,
                ValueFromPipelineByPropertyName=$True,
                HelpMessage="StorageGRID Profile location if different than .aws/credentials")][String]$ProfileLocation=$SGW_CREDENTIALS_FILE,
        [parameter(Mandatory = $False,
                Position = 2,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "The name of the StorageGRID Webscale Management Server. This value may also be a string representation of an IP address. If not an address, the name must be resolvable to an address.")][String]$Name,
        [parameter(Mandatory = $True,
                Position = 3,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "A System.Management.Automation.PSCredential object containing the credentials needed to log into the StorageGRID Webscale Management Server.")][System.Management.Automation.PSCredential]$Credential,
        [parameter(Mandatory = $False,
                Position = 4,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "If the StorageGRID Webscale Management Server certificate cannot be verified, the connection will fail. Specify -SkipCertificateCheck to skip the validation of the StorageGRID Webscale Management Server certificate.")][Alias("Insecure")][Switch]$SkipCertificateCheck,
        [parameter(Position = 5,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Account ID of the StorageGRID Webscale tenant to connect to.")][String]$AccountId,
        [parameter(Position = 6,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "By default StorageGRID automatically generates S3 Access Keys if required to carry out S3 operations. With this switch, automatic S3 Access Key generation will not be done.")][Switch]$DisableAutomaticAccessKeyGeneration,
        [parameter(Position = 7,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Time in seconds until automatically generated temporary S3 Access Keys expire (default 3600 seconds).")][Int]$TemporaryAccessKeyExpirationTime = 3600,
        [parameter(Position = 8,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "S3 Endpoint URL to be used.")][System.UriBuilder]$S3EndpointUrl,
        [parameter(Position = 9,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Swift Endpoint URL to be used.")][System.UriBuilder]$SwiftEndpointUrl
    )

    Process {
        $ConfigLocation = $ProfileLocation -replace "/[^/]+$", '/config'

        if ($Credential) {
            try {
                $Credentials = ConvertFrom-SgwConfigFile -SgwConfigFile $ProfileLocation
            }
            catch {
                Write-Verbose "Retrieving credentials from $ProfileLocation failed"
            }

            if (($Credentials | Where-Object { $_.ProfileName -eq $ProfileName })) {
                $CredentialEntry = $Credentials | Where-Object { $_.ProfileName -eq $ProfileName }
            }
            else {
                $CredentialEntry = [PSCustomObject]@{ ProfileName = $ProfileName }
            }

            $CredentialEntry | Add-Member -MemberType NoteProperty -Name username -Value $Credential.UserName -Force
            $CredentialEntry | Add-Member -MemberType NoteProperty -Name password -Value $Credential.GetNetworkCredential().Password -Force

            Write-Debug $CredentialEntry

            $Credentials = @($Credentials | Where-Object { $_.ProfileName -ne $ProfileName }) + $CredentialEntry
            ConvertTo-SgwConfigFile -Config $Credentials -SgwConfigFile $ProfileLocation
        }

        try {
            $Configs = ConvertFrom-SgwConfigFile -SgwConfigFile $ConfigLocation
        }
        catch {
            Write-Verbose "Retrieving config from $ConfigLocation failed"
        }

        $Config = $Configs | Where-Object { $_.ProfileName -eq $ProfileName }
        if (!$Config) {
            $Config = [PSCustomObject]@{ ProfileName = $ProfileName}
        }

        if ($Name) {
            $Config | Add-Member -MemberType NoteProperty -Name name -Value $Name -Force
        }

        if ($AccountId) {
            $Config | Add-Member -MemberType NoteProperty -Name account_id -Value $AccountId -Force
        }

        if ($DisableAutomaticAccessKeyGeneration) {
            $Config | Add-Member -MemberType NoteProperty -Name disable_automatic_access_key_generation -Value $DisableAutomaticAccessKeyGeneration -Force
        }

        if ($TemporaryAccessKeyExpirationTime -and $TemporaryAccessKeyExpirationTime -ne 3600) {
            $Config | Add-Member -MemberType NoteProperty -Name temporary_access_key_expiration_time -Value $TemporaryAccessKeyExpirationTime -Force
        }

        if ($S3EndpointUrl) {
            $S3EndpointUrlString = $S3EndpointUrl -replace "(http://.*:80)",'$1' -replace "(https://.*):443",'$1' -replace "/$",""
            $Config | Add-Member -MemberType NoteProperty -Name s3_endpoint_url -Value $S3EndpointUrlString -Force
        }

        if ($SwiftEndpointUrl) {
            $SwiftEndpointUrlString = $SwiftEndpointUrl -replace "(http://.*:80)",'$1' -replace "(https://.*):443",'$1' -replace "/$",""
            $Config | Add-Member -MemberType NoteProperty -Name swift_endpoint_url -Value $SwiftEndpointUrlString -Force
        }

        if ($SkipCertificateCheck -ne $null) {
            $Config | Add-Member -MemberType NoteProperty -Name skip_certificate_check -Value $SkipCertificateCheck -Force
        }

        $Configs = @($Configs | Where-Object { $_.ProfileName -ne $ProfileName}) + $Config
        ConvertTo-SgwConfigFile -Config $Configs -SgwConfigFile $ConfigLocation
    }
}

Set-Alias -Name Get-SgwCredentials -Value Get-SgwProfiles
<#
    .SYNOPSIS
    Get the StorageGRID config for all profiles and if there is a connection to a StorageGRID, it includes the StorageGRID config of the connected tenant
    .DESCRIPTION
    Get the StorageGRID config for all profiles and if there is a connection to a StorageGRID, it includes the StorageGRID config of the connected tenant
    .PARAMETER ProfileLocation
    StorageGRID Profile location if different than .aws/credentials
#>

function Global:Get-SgwProfiles {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory=$False,
                Position=0,
                ValueFromPipelineByPropertyName=$True,
                HelpMessage="StorageGRID Profile location if different than .aws/credentials")][String]$ProfileLocation=$SGW_CREDENTIALS_FILE
    )

    Process {
        if (!$ProfileLocation) {
            $ProfileLocation = $StorageGRID_CREDENTIALS_FILE
        }
        $ConfigLocation = $ProfileLocation -replace "/[^/]+$",'/config'

        if (!(Test-Path $ProfileLocation)) {
            Write-Warning "Profile location $ProfileLocation does not exist!"
            break
        }

        $Credentials = @()
        $Config = @()
        try {
            $Credentials = ConvertFrom-SgwConfigFile -SgwConfigFile $ProfileLocation
        }
        catch {
            Write-Verbose "Retrieving credentials from $ProfileLocation failed"
        }
        try {
            $Configs = ConvertFrom-SgwConfigFile -SgwConfigFile $ConfigLocation
        }
        catch {
            Write-Verbose "Retrieving credentials from $ConfigLocation failed"
        }

        foreach ($Credential in $Credentials) {
            $Config = $Configs | Where-Object { $_.ProfileName -eq $Credential.ProfileName } | Select-Object -First 1
            if (!$Config) {
                $Config = [PSCustomObject]@{ProfileName=$Credential.ProfileName}
                $Configs = @($Configs) + $Config
            }
            if ($Credential.username -and $Credential.password) {
                $Config | Add-Member -MemberType NoteProperty -Name Credential -Value ([PSCredential]::new($Credential.username,($Credential.password | ConvertTo-SecureString -AsPlainText -Force))) -Force
            }
        }

        foreach ($Config in $Configs) {
            $Output = [PSCustomObject]@{ProfileName = $Config.ProfileName;Credential = $Config.Credential}

            if ($Config.Name) {
                $Output | Add-Member -MemberType NoteProperty -Name Name -Value $Config.Name
            }
            else {
                Write-Warning "No StorageGRID name specified for Profile $($Config.ProfileName)"
            }

            if ($Config.account_id) {
                $Output | Add-Member -MemberType NoteProperty -Name AccountId -Value $Config.account_id
            }

            if ($Config.disalble_automatic_access_key_generation) {
                $Output | Add-Member -MemberType NoteProperty -Name DisableAutomaticAccessKeyGeneration -Value ([System.Convert]::ToBoolean($Config.disalble_automatic_access_key_generation))
            }
            else {
                $Output | Add-Member -MemberType NoteProperty -Name DisableAutomaticAccessKeyGeneration -Value $False
            }

            if ($Config.temporary_access_key_expiration_time) {
                $Output | Add-Member -MemberType NoteProperty -Name TemporaryAccessKeyExpirationTime -Value $Config.temporary_access_key_expiration_time
            }
            else {
                $Output | Add-Member -MemberType NoteProperty -Name TemporaryAccessKeyExpirationTime -Value 3600
            }

            if ($Config.s3_endpoint_url) {
                $Output | Add-Member -MemberType NoteProperty -Name S3EndpointUrl -Value $Config.s3_endpoint_url
            }

            if ($Config.swift_endpoint_url) {
                $Output | Add-Member -MemberType NoteProperty -Name SwiftEndpointUrl -Value $Config.swift_endpoint_url
            }

            if ($Config.skip_certificate_check) {
                $Output | Add-Member -MemberType NoteProperty -Name SkipCertificateCheck -Value ([System.Convert]::ToBoolean($Config.skip_certificate_check))
            }
            else {
                $Output | Add-Member -MemberType NoteProperty -Name SkipCertificateCheck -Value $False
            }
            Write-Output $Output
        }
    }
}

Set-Alias -Name Get-SgwCredential -Value Get-SgwProfile
<#
    .SYNOPSIS
    Add StorageGRID Credentials
    .DESCRIPTION
    Add StorageGRID Credentials
    .PARAMETER ProfileName
    StorageGRID Profile to use which contains StorageGRID sredentials and settings
    .PARAMETER ProfileLocation
    StorageGRID Profile location if different than .aws/credentials
    .PARAMETER Name
    The name of the StorageGRID Webscale Management Server. This value may also be a string representation of an IP address. If not an address, the name must be resolvable to an address.
    .PARAMETER Credential
    A System.Management.Automation.PSCredential object containing the credentials needed to log into the StorageGRID Webscale Management Server.
    .PARAMETER SkipCertificateCheck
    If the StorageGRID Webscale Management Server certificate cannot be verified, the connection will fail. Specify -SkipCertificateCheck to skip the validation of the StorageGRID Webscale Management Server certificate.
    .PARAMETER AccountId
    Account ID of the StorageGRID Webscale tenant to connect to.
    .PARAMETER DisableAutomaticAccessKeyGeneration
    By default StorageGRID automatically generates S3 Access Keys if required to carry out S3 operations. With this switch, automatic S3 Access Key generation will not be done.
    .PARAMETER TemporaryAccessKeyExpirationTime
    Time in seconds until automatically generated temporary S3 Access Keys expire (default 3600 seconds).
    .PARAMETER S3EndpointUrl
    S3 Endpoint URL to be used.
    .PARAMETER SwiftEndpointUrl
    Swift Endpoint URL to be used.
#>

function Global:Get-SgwProfile {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory=$False,
                Position=0,
                ValueFromPipelineByPropertyName=$True,
                HelpMessage="StorageGRID Profile to use which contains StorageGRID sredentials and settings")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory=$False,
                Position=1,
                ValueFromPipelineByPropertyName=$True,
                HelpMessage="StorageGRID Profile location if different than .aws/credentials")][String]$ProfileLocation=$SGW_CREDENTIALS_FILE,
        [parameter(Mandatory = $False,
                Position = 2,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "The name of the StorageGRID Webscale Management Server. This value may also be a string representation of an IP address. If not an address, the name must be resolvable to an address.")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 3,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "A System.Management.Automation.PSCredential object containing the credentials needed to log into the StorageGRID Webscale Management Server.")][System.Management.Automation.PSCredential]$Credential,
        [parameter(Mandatory = $False,
                Position = 4,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "If the StorageGRID Webscale Management Server certificate cannot be verified, the connection will fail. Specify -SkipCertificateCheck to skip the validation of the StorageGRID Webscale Management Server certificate.")][Alias("Insecure")][Switch]$SkipCertificateCheck,
        [parameter(Position = 5,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Account ID of the StorageGRID Webscale tenant to connect to.")][String]$AccountId,
        [parameter(Position = 6,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "By default StorageGRID automatically generates S3 Access Keys if required to carry out S3 operations. With this switch, automatic S3 Access Key generation will not be done.")][Switch]$DisableAutomaticAccessKeyGeneration,
        [parameter(Position = 7,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Time in seconds until automatically generated temporary S3 Access Keys expire (default 3600 seconds).")][Int]$TemporaryAccessKeyExpirationTime,
        [parameter(Position = 8,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "S3 Endpoint URL to be used.")][System.UriBuilder]$S3EndpointUrl,
        [parameter(Position = 9,
                Mandatory = $False,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Swift Endpoint URL to be used.")][System.UriBuilder]$SwiftEndpointUrl
    )

    Begin {
        if (!$Server -and $CurrentSgwServer) {
            $Server = $CurrentSgwServer.PSObject.Copy()
        }
    }

    Process {
        if (!$ProfileName) {
            $ProfileName = "default"
        }

        $Configs = Get-SgwProfiles

        $Config = $Configs | Where-Object { $_.ProfileName -eq $ProfileName }

        if (!$Config) {
            $Config = [PSCustomObject]@{ProfileName = $ProfileName;
                Name = $Name;
                Credential = $Credential;
                SkipCertificateCheck = $SkipCertificateCheck;
                AccountId = $AccountId;
                DisableAutomaticAccessKeyGeneration = $DisableAutomaticAccessKeyGeneration;
                TemporaryAccessKeyExpirationTime = $TemporaryAccessKeyExpirationTime;
                S3EndpointUrl = $S3EndpointUrl;
                SwiftEndpointUrl = $SwiftEndpointUrl}
        }

        if ($Name) {
            $Config.Name = $Name
        }

        if ($Credential) {
            $Config.Credential = $Credential
        }

        if ($SkipCertificateCheck -ne $null) {
            $Config.SkipCertificateCheck = $SkipCertificateCheck
        }
        elseif (!$Config.SkipCertificateCheck) {
            $Config.SkipCertificateCheck = $False
        }

        if ($AccountId) {
            $Config.AccountId = $AccountId
        }

        if ($DisableAutomaticAccessKeyGeneration) {
            $Config.DisableAutomaticAccessKeyGeneration = $DisableAutomaticAccessKeyGeneration
        }
        elseif (!$Config.DisableAutomaticAccessKeyGeneration) {
            $Config.DisableAutomaticAccessKeyGeneration = $False
        }

        if ($TemporaryAccessKeyExpirationTime) {
            $Config.TemporaryAccessKeyExpirationTime = $TemporaryAccessKeyExpirationTime
        }
        elseif (!$Config.TemporaryAccessKeyExpirationTime) {
            $Config.TemporaryAccessKeyExpirationTime = 3600
        }

        if ($S3EndpointUrl) {
            $Config.S3EndpointUrl = $S3EndpointUrl
        }

        if ($SwiftEndpointUrl) {
            $Config.SwiftEndpointUrl = $SwiftEndpointUrl
        }

        Write-Output $Config
    }
}

Set-Alias -Name Remove-SgwCredential -Value Remove-SgwConfig
<#
    .SYNOPSIS
    Remove StorageGRID Config
    .DESCRIPTION
    Remove StorageGRID Config
    .PARAMETER ProfileName
    StorageGRID Profile to remove which contains StorageGRID sredentials and settings
    .PARAMETER ProfileLocation
    StorageGRID Profile location if different than .aws/credentials
#>

function Global:Remove-SgwProfile {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory=$True,
                Position=0,
                HelpMessage="StorageGRID Profile where config should be removed")][Alias("Profile")][String]$ProfileName,
        [parameter(
                Mandatory=$False,
                Position=1,
                ValueFromPipelineByPropertyName=$True,
                HelpMessage="StorageGRID Profile location if different than .aws/credentials")][String]$ProfileLocation=$SGW_CREDENTIALS_FILE
    )

    Process {
        $ConfigLocation = $ProfileLocation -replace "/[^/]+$",'/config'

        $Credentials = ConvertFrom-SgwConfigFile -SgwConfigFile $ProfileLocation
        $Credentials = $Credentials | Where-Object { $_.ProfileName -ne $ProfileName }
        ConvertTo-SgwConfigFile -Config $Credentials -SgwConfigFile $ProfileLocation

        $Configs = ConvertFrom-SgwConfigFile -SgwConfigFile $ConfigLocation
        $Configs = $Configs | Where-Object { $_.ProfileName -ne $ProfileName }
        ConvertTo-SgwConfigFile -Config $Configs -SgwConfigFile $ConfigLocation
    }
}

## accounts ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieve all StorageGRID Webscale Accounts
    .DESCRIPTION
    Retrieve all StorageGRID Webscale Accounts
#>

function Global:Get-SgwAccounts {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Maximum number of results.")][Int]$Limit = 0,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "Pagination offset (value is Account's id).")][String]$Marker,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "if set, the marker element is also returned.")][Switch]$IncludeMarker,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "pagination order (desc requires marker).")][ValidateSet("asc", "desc")][String]$Order = "asc",
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "Comma separated list of capabilities of the accounts to return. Can be swift, S3 and management (e.g. swift,s3 or s3,management ...).")][ValidateSet("swift", "s3", "management")][String[]]$Capabilities
    )

    Begin {
        if (!$Server -and !$CurrentSgwServer.Name) {
            $Profile = Get-SgwProfile -ProfileName $ProfileName
            $Server = Connect-SgwServer -Name $Profile.Name -Credential $Profile.Credential -AccountId $Profile.AccountId -SkipCertificateCheck:$Profile.SkipCertificateCheck -DisableAutomaticAccessKeyGeneration:$Profile.disalble_automatic_access_key_generation -TemporaryAccessKeyExpirationTime $Profile.temporary_access_key_expiration_time -S3EndpointUrl $Profile.S3EndpointUrl -SwiftEndpointUrl $Profile.SwiftEndpointUrl -Transient
        }

        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {

        $Uri = $Server.BaseURI + '/grid/accounts'
        $Method = "GET"

        if ($Limit -eq 0) {
            $Query = "?limit=25"
        }
        else {
            $Query = "?limit=$Limit"
        }
        if ($Marker) {
            $Query += "&marker=$Marker"
        }
        if ($IncludeMarker) {
            $Query += "&includeMarker=true"
        }
        if ($Order) {
            $Query += "&order=$Order"
        }

        $Uri += $Query

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $( $responseBody.message )"
            return
        }

        $Accounts = $Response.Json.data

        if ($Capabilities) {
            $Accounts = $Accounts | Where-Object { ($_.capabilities -join ",") -match ($Capabilities -join "|") }
        }

        foreach ($Account in $Accounts) {
            $Account | Add-Member -MemberType AliasProperty -Name accountId -Value id
            $Account | Add-Member -MemberType AliasProperty -Name tenant -Value name
            $Account | Add-Member -MemberType NoteProperty -Name tenantPortal -Value "https://$( $Server.Name )/?accountId=$( $Account.id )"
        }

        Write-Output $Accounts

        if ($Limit -eq 0 -and $Response.Json.data.count -eq 25) {
            if ($Capabilities) {
                Get-SgwAccounts -Server $Server -Limit $Limit -Marker ($Response.Json.data | select -last 1 -ExpandProperty id) -IncludeMarker:$IncludeMarker -Order $Order -Capabilities $Capabilities
            }
            else {
                Get-SgwAccounts -Server $Server -Limit $Limit -Marker ($Response.Json.data | select -last 1 -ExpandProperty id) -IncludeMarker:$IncludeMarker -Order $Order
            }
        }
    }
}

<#
    .SYNOPSIS
    Create a StorageGRID Webscale Account
    .DESCRIPTION
    Create a StorageGRID Webscale Account
    .PARAMETER Server
    StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.
    .PARAMETER Name
    Name of the StorageGRID Webscale Account to be created.
    .PARAMETER Capabilities
    Comma separated list of capabilities of the account. Can be swift, S3 and management (e.g. swift,s3 or s3,management).
    .PARAMETER UseAccountIdentitySource
    Comma separated list of capabilities of the account. Can be swift, S3 and management (e.g. swift,s3 or s3,management).
    .PARAMETER AllowPlatformServices
    Allow platform services to be used (default: true - supported since StorageGRID 11.0).
    .PARAMETER Quota
    Quota for tenant in bytes.
    .PARAMETER Password
    Tenant root password (must be at least 8 characters).
    .EXAMPLE
    Create new account with S3 and Management capabilities
 
    New-SgwAccount -Name MyAccount -Capabilities s3,management -Password t9eM66Y2
    .EXAMPLE
    Create new account with Swift and Management capabilities and do not allow own Identity Federation configuration
 
    New-SgwAccount -Name MyAccount -Capabilities swift,management -UseAccountIdentitySource $false -Password t9eM66Y2
    .EXAMPLE
    Create new account with S3 and Management capabilities and set Quota
 
    New-SgwAccount -Name MyAccount -Capabilities swift,management -UseAccountIdentitySource $false -Quota 1TB -Password t9eM66Y2
#>

function Global:New-SgwAccount {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Name of the StorageGRID Webscale Account to be created.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Name,
        [parameter(
                Mandatory = $True,
                Position = 3,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Comma separated list of capabilities of the account. Can be swift, S3 and management (e.g. swift,s3 or s3,management).")][ValidateSet("swift", "s3", "management")][String[]]$Capabilities,
        [parameter(
                Mandatory = $False,
                Position = 4,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Use account identity source (default: true - supported since StorageGRID 10.4).")][Boolean]$UseAccountIdentitySource = $true,
        [parameter(
                Mandatory = $False,
                Position = 5,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Allow platform services to be used (default: true - supported since StorageGRID 11.0).")][Boolean]$AllowPlatformServices = $true,
        [parameter(
                Mandatory = $False,
                Position = 6,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Quota for tenant in bytes.")][Long]$Quota,
        [parameter(
                Mandatory = $False,
                Position = 7,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Policy object containing information on identity source, platform service and quota")][PSCustomObject]$Policy = @{ },
        [parameter(
                Mandatory = $False,
                Position = 8,
                HelpMessage = "Tenant root password (must be at least 8 characters).")][ValidateLength(8, 256)][String]$Password
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -ge 2 -and !$Password) {
            Throw "Password required"
        }
        if ($Server.APIVersion -lt 2 -and ($Quota -or $Password)) {
            Write-Warning "Quota and password will be ignored in API Version $( $Server.APIVersion )"
        }
        if ($Server.APIVersion -lt 2 -and $UseAccountIdentitySource.isPresent) {
            Write-Warning "Use Account Services is only Supported from StorageGRID 10.4"
        }
        if ($Server.APIVersion -lt 2.1 -and $AllowPlatformServices.isPresent) {
            Write-Warning "Use Account Services is only Supported from StorageGRID 11.0"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
        if ($Password) {
            if ($Password.length -lt 8) {
                Throw "Password does not meet minimum length requirement of 8 characters"
            }
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/accounts"
        $Method = "POST"

        $Body = @{ }
        $Body.name = $Name
        $Body.capabilities = $Capabilities

        if ($Server.APIVersion -ge 2) {
            $Body.password = $Password
            $Body.policy = $Policy
            if ($UseAccountIdentitySource) {
                $Body.policy.useAccountIdentitySource = $UseAccountIdentitySource
            }
            if ($Server.APIVersion -ge 2.1) {
                if ($AllowPlatformServices) {
                    $Body.policy.allowPlatformServices = $AllowPlatformServices
                }
            }
            if ($Quota) {
                $Body.policy.quotaObjectBytes = $Quota
            }
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Account = $Response.Json.data

        $Account | Add-Member -MemberType AliasProperty -Name accountId -Value id
        $Account | Add-Member -MemberType AliasProperty -Name tenant -Value name
        $Account | Add-Member -MemberType NoteProperty -Name tenantPortal -Value "https://$( $Server.Name )/?accountId=$( $Account.id )"

        Write-Output $Account
    }
}

<#
    .SYNOPSIS
    Delete a StorageGRID Webscale Account
    .DESCRIPTION
    Delete a StorageGRID Webscale Account
#>

function Global:Remove-SgwAccount {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Account to delete.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $False,
                Position = 2,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/accounts/$id"
        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
            Write-Verbose "Successfully deleted account with ID $id"
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }
    }
}

<#
    .SYNOPSIS
    Retrieve a StorageGRID Webscale Account
    .DESCRIPTION
    Retrieve a StorageGRID Webscale Account
#>

function Global:Get-SgwAccount {
    [CmdletBinding(DefaultParameterSetName = "id")]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 2,
                ParameterSetName = "id",
                HelpMessage = "ID of a StorageGRID Webscale Account to get information for.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Id")][String]$AccountId,
        [parameter(
                Mandatory = $True,
                Position = 2,
                ParameterSetName = "name",
                HelpMessage = "Name of a StorageGRID Webscale Account to get information for.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        if ($Name) {
            # this is a convenience method for retrieving an account by name
            $Account = Get-SgwAccounts | ? { $_.Name -eq $Name }
            Write-Output $Account
        }
        else {
            $Uri = $Server.BaseURI + "/grid/accounts/$AccountId"
            $Method = "GET"

            try {
                $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
            }
            catch {
                $ResponseBody = ParseErrorForResponseBody $_
                Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
            }

            $Account = $Response.Json.data
            $Account | Add-Member -MemberType AliasProperty -Name accountId -Value id
            $Account | Add-Member -MemberType AliasProperty -Name tenant -Value name
            $Account | Add-Member -MemberType NoteProperty -Name tenantPortal -Value "https://$( $Server.Name )/?accountId=$( $Account.AccountId )"

            Write-Output $Account
        }
    }
}

<#
    .SYNOPSIS
    Update a StorageGRID Webscale Account
    .DESCRIPTION
    Update a StorageGRID Webscale Account
#>

function Global:Update-SgwAccount {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "ID of a StorageGRID Webscale Account to update.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Comma separated list of capabilities of the account. Can be swift, S3 and management (e.g. swift,s3 or s3,management ...).")][String[]]$Capabilities,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "New name of the StorageGRID Webscale Account.")][String]$Name,
        [parameter(
                Mandatory = $False,
                Position = 5,
                HelpMessage = "Use account identity source (supported since StorageGRID 10.4).")][Boolean]$UseAccountIdentitySource = $true,
        [parameter(
                Mandatory = $False,
                Position = 6,
                HelpMessage = "Allow platform services to be used (supported since StorageGRID 11.0).")][Boolean]$AllowPlatformServices = $true,
        [parameter(
                Mandatory = $False,
                Position = 7,
                HelpMessage = "Quota for tenant in bytes.")][Long]$Quota
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }

        if ($Server.APIVersion -lt 2 -and ($Quota -or $Password)) {
            Write-Warning "Quota and password will be ignored in API Version $( $Server.APIVersion )"
        }
        if ($Server.APIVersion -lt 2 -and $UseAccountIdentitySource.isPresent) {
            Write-Warning "Use Account Services is only Supported from StorageGRID 10.4"
        }
        if ($Server.APIVersion -lt 2.1 -and $AllowPlatformServices.isPresent) {
            Write-Warning "Use Account Services is only Supported from StorageGRID 11.0"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/accounts/$id"
        $Method = "PATCH"

        $Body = @{ }
        if ($Name) {
            $Body.name = $Name
        }
        if ($Capabilities) {
            $Body.capabilities = $Capabilities
        }

        if ($Server.APIVersion -ge 2) {
            $Body.policy = @{ "useAccountIdentitySource" = $UseAccountIdentitySource }
            if ($Server.APIVersion -ge 2.1) {
                $Body.policy["allowPlatformServices"] = $AllowPlatformServices
            }
            if ($Quota) {
                $Body.policy.quotaObjectBytes = $Quota
            }
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Account = $Response.Json.data
        $Account | Add-Member -MemberType AliasProperty -Name accountId -Value id
        $Account | Add-Member -MemberType AliasProperty -Name tenant -Value name
        $Account | Add-Member -MemberType NoteProperty -Name tenantPortal -Value "https://$( $Server.Name )/?accountId=$( $Account.id )"

        Write-Output $Account
    }
}

<#
    .SYNOPSIS
    Replace a StorageGRID Webscale Account
    .DESCRIPTION
    Replace a StorageGRID Webscale Account
#>

function Global:Replace-SgwAccount {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "ID of a StorageGRID Webscale Account to update.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Comma separated list of capabilities of the account. Can be swift, S3 and management (e.g. swift,s3 or s3,management ...).")][String[]]$Capabilities,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "New name of the StorageGRID Webscale Account.")][String]$Name,
        [parameter(
                Mandatory = $False,
                Position = 5,
                HelpMessage = "Use account identity source (supported since StorageGRID 10.4).")][Boolean]$UseAccountIdentitySource = $true,
        [parameter(
                Mandatory = $False,
                Position = 6,
                HelpMessage = "Allow platform services to be used (supported since StorageGRID 11.0).")][Boolean]$AllowPlatformServices = $true,
        [parameter(
                Mandatory = $False,
                Position = 7,
                HelpMessage = "Quota for tenant in bytes.")][Long]$Quota
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }

        if ($Server.APIVersion -lt 2 -and ($Quota -or $Password)) {
            Write-Warning "Quota and password will be ignored in API Version $( $Server.APIVersion )"
        }
        if ($Server.APIVersion -lt 2 -and $UseAccountIdentitySource.isPresent) {
            Write-Warning "Use Account Services is only Supported from StorageGRID 10.4"
        }
        if ($Server.APIVersion -lt 2.1 -and $AllowPlatformServices.isPresent) {
            Write-Warning "Use Account Services is only Supported from StorageGRID 11.0"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Body = @{ }
        if ($Name) {
            $Body.name = $Name
        }
        if ($Capabilities) {
            $Body.capabilities = $Capabilities
        }

        if ($Server.APIVersion -ge 2) {
            $Body.policy = @{ "useAccountIdentitySource" = $UseAccountIdentitySource }
            if ($Server.APIVersion -ge 2.1) {
                $Body.policy["allowPlatformServices"] = $AllowPlatformServices
            }
            if ($Quota) {
                $Body.policy.quotaObjectBytes = $Quota
            }
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Account = $Response.Json.data
        $Account | Add-Member -MemberType AliasProperty -Name accountId -Value id
        $Account | Add-Member -MemberType AliasProperty -Name tenant -Value name
        $Account | Add-Member -MemberType NoteProperty -Name tenantPortal -Value "https://$( $Server.Name )/?accountId=$( $Account.id )"

        Write-Output $Account
    }
}

<#
    .SYNOPSIS
    Change Swift Admin Password for StorageGRID Webscale Account
    .DESCRIPTION
    Change Swift Admin Password for StorageGRID Webscale Account
#>

function Global:Update-SgwSwiftAdminPassword {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Account to update.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String[]]$Id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Old Password.")][String]$OldPassword,
        [parameter(
                Mandatory = $True,
                Position = 3,
                HelpMessage = "New Password.")][String]$NewPassword,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -gt 1) {
            Write-Error "This Cmdlet is only supported with API Version 1.0. Use the new Update-SgwPassword Cmdlet instead!"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Id = @($Id)
        foreach ($Id in $Id) {
            $Uri = $Server.BaseURI + "/grid/accounts/$id/swift-admin-password"
            $Method = "POST"

            $Body = @"
{
  "password": "$NewPassword",
  "currentPassword": "$OldPassword"
}
"@

            try {
                $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
            }
            catch {
                $ResponseBody = ParseErrorForResponseBody $_
                Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
            }

            Write-Output $Response.Json.data
        }
    }
}

<#
    .SYNOPSIS
    Changes the root user password for the Storage Tenant Account
    .DESCRIPTION
    Changes the root user password for the Storage Tenant Account
#>

function Global:Update-SgwPassword {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Account to update.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Old Password.")][String]$OldPassword,
        [parameter(
                Mandatory = $True,
                Position = 3,
                HelpMessage = "New Password.")][String]$NewPassword,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2) {
            Write-Error "This Cmdlet is only supported with API Version 2.0 and later. Use the old Update-SgwSwiftAdminPassword Cmdlet instead!"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/accounts/$id/change-password"
        $Method = "POST"

        $Body = @"
{
  "password": "$NewPassword",
  "currentPassword": "$OldPassword"
}
"@

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve StorageGRID Webscale Account Usage Report
    .DESCRIPTION
    Retrieve StorageGRID Webscale Account Usage Report
#>

function Global:Get-SgwAccountUsage {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Account to get usage information for.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $False,
                Position = 2,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if (!$Id) {
            if (!$Server.AccountId) {
                throw "No ID specified and not connected as tenant user. Either specify an ID or use Connect-SgwServer with the parameter accountId."
            }
            else {
                $Uri = $Server.BaseURI + "/org/usage"
            }
        }
        else {
            $Uri = $Server.BaseURI + "/grid/accounts/$id/usage"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
            return
        }
        Write-Output $Response.Json.data
    }
}

## auth ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Connect to StorageGRID Webscale Management Server
    .DESCRIPTION
    Connect to StorageGRID Webscale Management Server
    .PARAMETER Name
    The name of the StorageGRID Webscale Management Server. This value may also be a string representation of an IP address. If not an address, the name must be resolvable to an address.
    .PARAMETER Credential
    A System.Management.Automation.PSCredential object containing the credentials needed to log into the StorageGRID Webscale Management Server.
    .PARAMETER SkipCertificateCheck
    If the StorageGRID Webscale Management Server certificate cannot be verified, the connection will fail. Specify -SkipCertificateCheck to skip the validation of the StorageGRID Webscale Management Server certificate.
    .PARAMETER Transient
    If set the global variable `$CurrentOciServer will not be set and the Server must be explicitly specified for all Cmdlets.
    .PARAMETER AccountId
    Account ID of the StorageGRID Webscale tenant to connect to.
    .PARAMETER DisableAutomaticAccessKeyGeneration
    By default StorageGRID automatically generates S3 Access Keys if required to carry out S3 operations. With this switch, automatic S3 Access Key generation will not be done.
    .PARAMETER TemporaryAccessKeyExpirationTime
    Time in seconds until automatically generated temporary S3 Access Keys expire.
    .PARAMETER S3EndpointUrl
    S3 Endpoint URL to be used.
    .PARAMETER SwiftEndpointUrl
    Swift Endpoint URL to be used.
    .EXAMPLE
    Minimum required information to connect with a StorageGRID Webscale Admin Node
 
    $Name = "admin-node.example.org"
    $Credential = Get-Credential
    Connect-SgwServer -Name $Name -Credential $Credential
    .EXAMPLE
    Skip certificate validation
 
    $Name = "admin-node.example.org"
    $Credential = Get-Credential
    Connect-SgwServer -Name $Name -Credential $Credential -SkipCertificateCheck
    .EXAMPLE
    Do not store server in global variable
 
    $Name = "admin-node.example.org"
    $Credential = Get-Credential"
    Connect-SgwServer -Name $Name -Credential $Credential -Transient
    .EXAMPLE
    Connect as StorageGRID Webscale tenant
 
    $Name = "admin-node.example.org"
    $Credential = Get-Credential
    $AccountId = "12345678901234567890"
    Connect-SgwServer -Name $Name -Credential $Credential -AccountId
#>

function global:Connect-SgwServer {
    [CmdletBinding(DefaultParameterSetName="profile")]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                ParameterSetName="profile",
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(
                Mandatory = $True,
                Position = 1,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                ParameterSetName="name",
                HelpMessage = "The name of the StorageGRID Webscale Management Server. This value may also be a string representation of an IP address. If not an address, the name must be resolvable to an address.")][String]$Name,
        [parameter(
                Mandatory = $True,
                Position = 2,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                ParameterSetName="name",
                HelpMessage = "A System.Management.Automation.PSCredential object containing the credentials needed to log into the StorageGRID Webscale Management Server.")][System.Management.Automation.PSCredential]$Credential,
        [parameter(
                Mandatory = $False,
                Position = 3,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "If the StorageGRID Webscale Management Server certificate cannot be verified, the connection will fail. Specify -SkipCertificateCheck to skip the validation of the StorageGRID Webscale Management Server certificate.")][Alias("Insecure")][Switch]$SkipCertificateCheck,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "Specify -Transient to not set the global variable `$CurrentOciServer.")][Switch]$Transient,
        [parameter(
                Mandatory = $False,
                Position = 5,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Account ID of the StorageGRID Webscale tenant to connect to.")][String]$AccountId,
        [parameter(
                Mandatory = $False,
                Position = 6,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "By default StorageGRID automatically generates S3 Access Keys if required to carry out S3 operations. With this switch, automatic S3 Access Key generation will not be done.")][Switch]$DisableAutomaticAccessKeyGeneration,
        [parameter(
                Mandatory = $False,
                Position = 7,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Time in seconds until automatically generated temporary S3 Access Keys expire (default 3600 seconds).")][Int]$TemporaryAccessKeyExpirationTime = 3600,
        [parameter(
                Mandatory = $False,
                Position = 8,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "S3 Endpoint URL to be used.")][System.UriBuilder]$S3EndpointUrl,
        [parameter(
                Mandatory = $False,
                Position = 9,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Swift Endpoint URL to be used.")][System.UriBuilder]$SwiftEndpointUrl
    )

    Process {
        if (!$Name) {
            $Profile = Get-SgwProfile -ProfileName $ProfileName
            $Server = Connect-SgwServer -Name $Profile.Name -Credential $Profile.Credential -AccountId $Profile.AccountId -SkipCertificateCheck:$Profile.SkipCertificateCheck -DisableAutomaticAccessKeyGeneration:$Profile.disalble_automatic_access_key_generation -TemporaryAccessKeyExpirationTime $Profile.temporary_access_key_expiration_time -S3EndpointUrl $Profile.S3EndpointUrl -SwiftEndpointUrl $Profile.SwiftEndpointUrl -Transient:$Transient
            return $Server
        }

        $Server = [PSCustomObject]@{
            SkipCertificateCheck = $SkipCertificateCheck.IsPresent;
            Name = $Name;
            User = $Credential.UserName;
            Credential = $Credential;
            BaseUri = "https://$Name/api/v2";
            Session = [Microsoft.PowerShell.Commands.WebRequestSession]::new();
            Headers = [Hashtable]::new();
            ApiVersion = 0;
            SupportedApiVersions = @();
            S3EndpointUrl = $null;
            SwiftEndpointUrl = $null;
            DisableAutomaticAccessKeyGeneration = $DisableAutomaticAccessKeyGeneration.isPresent;
            TemporaryAccessKeyExpirationTime = $TemporaryAccessKeyExpirationTime;
            AccessKeyStore = @{ };
            AccountId = "";
            TenantPortal = ""
        }

        if ([environment]::OSVersion.Platform -match "Win") {
            # check if proxy is used
            $ProxyRegistry = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
            $ProxySettings = Get-ItemProperty -Path $ProxyRegistry
            if ($ProxySettings.ProxyEnable) {
                Write-Warning "Proxy Server $( $ProxySettings.ProxyServer ) configured in Internet Explorer may be used to connect to the OCI server!"
            }
            if ($ProxySettings.AutoConfigURL) {
                Write-Warning "Proxy Server defined in automatic proxy configuration script $( $ProxySettings.AutoConfigURL ) configured in Internet Explorer may be used to connect to the OCI server!"
            }
        }

        $Body = @{ }
        $Body.username = $Credential.UserName
        $Body.password = $Credential.GetNetworkCredential().Password
        $Body.cookie = $True
        $Body.csrfToken = $True

        if ($AccountId) {
            $Body.accountId = $AccountId
            $Server.AccountId = $AccountId
            $Server.TenantPortal = "https://$( $Server.Name )/?accountId=$( $Account.id )"
        }

        $Body = ConvertTo-Json -InputObject $Body

        $APIVersion = (Get-SgwVersion -Server $Server -ErrorAction Stop | Sort-Object | select -Last 1) -replace "\..*", ""

        if (!$APIVersion) {
            Throw "API Version could not be retrieved via https://$Name/api/versions"
        }

        $Server.BaseUri = "https://$Name/api/v2"

        Try {
            $Response = Invoke-RestMethod -SessionVariable "Session" -Method POST -Uri "$( $Server.BaseUri )/authorize" -TimeoutSec 10 -ContentType "application/json" -Body $Body -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        Catch {
            $ResponseBody = ParseErrorForResponseBody $_
            if ($_.Exception.Message -match "Unauthorized") {
                Write-Error "Authorization for $BaseURI/authorize with user $( $Credential.UserName ) failed"
                return
            }
            elseif ($_.Exception.Message -match "trust relationship") {
                Write-Error $_.Exception.Message
                Write-Information "Certificate of the server is not trusted. Use -SkipCertificateCheck switch if you want to skip certificate verification."
            }
            else {
                Write-Error "Login to $BaseURI/authorize failed via HTTPS protocol. Exception message: $( $_.Exception.Message )`n $ResponseBody"
                return
            }
        }

        if ($Response.status -ne "success") {
            Throw "Authorization failed with status $( $Response.status )"
        }

        $Server.Headers["Authorization"] = "Bearer $( $Response.data )"

        $Server.Session = $Session
        if (($Server.Session.Cookies.GetCookies($Server.BaseUri) | ? { $_.Name -match "CsrfToken" })) {
            $XCsrfToken = $Server.Session.Cookies.GetCookies($Server.BaseUri) | ? { $_.Name -match "CsrfToken" } | select -ExpandProperty Value
            $Server.Headers["X-Csrf-Token"] = $XCsrfToken
        }

        $Server.ApiVersion = $Response.apiVersion

        $SupportedApiVersions = @(Get-SgwVersions -Server $Server)
        if (!$SupportedApiVersions.Contains(1)) {
            Write-Warning "API Version 1 not supported. API Version 1 is required to autogenerate S3 credentials for Grid Administrators. If you want to run the S3 Cmdlets as Grid Administrator and let the Cmdlets autogenerate S3 credentials, then enable API Version 1 with`nUpdate-SgwConfigManagement -MinApiVersion 1"
        }
        $Server.SupportedApiVersions = $SupportedApiVersions

        if ($S3EndpointUrl) {
            $Server.S3EndpointUrl = $S3EndpointUrl
        }

        if ($SwiftEndpointUrl) {
            $Server.SwiftEndpointUrl = $SwiftEndpointUrl
        }

        if (!$AccountId -and !$Server.S3EndpointUrl) {
            Write-Verbose "Trying to identify S3 and Swift Endpoints"
            # check endpoint urls and try StorageGRID default ports 8082 and 18082 for S3 and 8083 and 18083 for Swift
            $EndpointDomainNames = Get-SgwEndpointDomainNames -Server $Server | % { @("https://$_", "https://${_}:8082", "https://${_}:18082", "https://${_}:8083", "https://${_}:18083") }
            foreach ($EndpointDomainName in $EndpointDomainNames) {
                Write-Verbose "Endpoint domain name: $EndpointDomainName"
                if ($PSVersionTable.PSVersion.Major -lt 6) {
                    $CurrentCertificatePolicy = [System.Net.ServicePointManager]::CertificatePolicy
                    [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
                    try {
                        $Response = Invoke-WebRequest -Method Options -Uri $EndpointDomainName -UseBasicParsing -TimeoutSec 3
                        if ($Response.Headers["x-amz-request-id"]) {
                            $Server.S3EndpointUrl = [System.UriBuilder]::new($EndpointDomainName)
                            [System.Net.ServicePointManager]::CertificatePolicy = $CurrentCertificatePolicy
                            break
                        }
                    }
                    catch {
                    }
                    try {
                        $Response = Invoke-WebRequest -Method Options -Uri "$EndpointDomainName/info" -UseBasicParsing -TimeoutSec 3 -SkipCertificateCheck
                        if ($Response.Headers["X-Trans-Id"]) {
                            $Server.SwiftEndpointUrl = [System.UriBuilder]::new($EndpointDomainName)
                            [System.Net.ServicePointManager]::CertificatePolicy = $CurrentCertificatePolicy
                            break
                        }
                    }
                    catch {
                    }
                    [System.Net.ServicePointManager]::CertificatePolicy = $CurrentCertificatePolicy
                }
                else {
                    try {
                        $Response = Invoke-WebRequest -Method Options -Uri "$EndpointDomainName" -SkipCertificateCheck -UseBasicParsing -TimeoutSec 3
                        if ($Response.Headers["x-amz-request-id"]) {
                            Write-Verbose "Test"
                            $Server.S3EndpointUrl = [System.UriBuilder]::new($EndpointDomainName)
                            break
                        }
                    }
                    catch {
                    }
                    try {
                        $Response = Invoke-WebRequest -Method Options -Uri "$EndpointDomainName/info" -SkipCertificateCheck -UseBasicParsing -TimeoutSec 3
                        if ($Response.Headers["X-Trans-Id"]) {
                            $Server.SwiftEndpointUrl = [System.UriBuilder]::new($EndpointDomainName)
                            break
                        }
                    }
                    catch {
                    }
                }
            }
        }
        elseif (!$Server.S3EndpointUrl -and $CurrentSgwServer.Name -eq $Name) {
            Write-Verbose "Setting S3 and Swift Endpoints to the values from current SGW Server"
            $Server.S3EndpointUrl = $CurrentSgwServer.S3EndpointUrl
            $Server.SwiftEndpointUrl = $CurrentSgwServer.SwiftEndpointUrl
        }

        if (!$Transient) {
            Set-Variable -Name CurrentSgwServer -Value $Server -Scope Global
        }

        return $Server
    }
}

<#
    .SYNOPSIS
    Connect to StorageGRID Webscale Management Server
    .DESCRIPTION
    Connect to StorageGRID Webscale Management Server
#>

function global:Disconnect-SgwServer {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
            Remove-Variable -Name CurrentSgwServer -Scope Global
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/authorize"

        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
            return
        }
    }
}

## alarms ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieve all StorageGRID Webscale Alarms
    .DESCRIPTION
    Retrieve all StorageGRID Webscale Alarms
#>

function Global:Get-SgwAlarms {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "If set, acknowledged alarms are also returned")][Switch]$includeAcknowledged,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Maximum number of results")][int]$limit
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + '/grid/alarms'
        $Method = "GET"

        $Separator = "?"
        if ($includeAcknowledged) {
            $Uri += "$( $Separator )includeAcknowledged=true"
            $Separator = "&"
        }
        if ($limit) {
            $Uri += "$( $Separator )limit=$limit"
        }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## audit ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Gets the audit configuration
    .DESCRIPTION
    Gets the audit configuration
#>

function Global:Get-SgwAudit {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + '/grid/audit'
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Audit = [PSCustomObject]@{
            LevelSystem = $Response.Json.data.levels.system;
            LevelStorage = $Response.Json.data.levels.storage;
            LevelProtocol = $Response.Json.data.levels.protocol;
            LevelManagement = $Response.Json.data.levels.management;
            LoggedHeaders = $Response.Json.data.loggedHeaders
        }

        Write-Output $Audit
    }
}

<#
    .SYNOPSIS
    Replace the audit configuration
    .DESCRIPTION
    Replace the audit configuration
#>

function Global:Replace-SgwAudit {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Audit log level for system.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][ValidateSet("off","error","normal","debug")][String]$LevelSystem,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Audit log level for storage.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][ValidateSet("off","error","normal","debug")][String]$LevelStorage,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Audit log level for protocol.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][ValidateSet("off","error","normal","debug")][String]$LevelProtocol,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Audit log level for management.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][ValidateSet("off","error","normal","debug")][String]$LevelManagement,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Logged headers.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String[]]$LoggedHeaders
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + '/grid/audit'
        $Method = "PUT"

        $Body = @{}
        $Body.levels = @{}
        $Body.levels.system = $LevelSystem
        $Body.levels.storage = $LevelStorage
        $Body.levels.protocol = $LevelProtocol
        $Body.levels.management = $LevelManagement
        $Body.loggedHeaders = $LoggedHeaders

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Body $Body -ContentType "application/json" -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Audit = [PSCustomObject]@{
            LevelSystem = $Response.Json.data.levels.system;
            LevelStorage = $Response.Json.data.levels.storage;
            LevelProtocol = $Response.Json.data.levels.protocol;
            LevelManagement = $Response.Json.data.levels.management;
            LoggedHeaders = $Response.Json.data.loggedHeaders
        }

        Write-Output $Audit
    }
}

## config ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieves global configuration and token information
    .DESCRIPTION
    Retrieves global configuration and token information
#>

function Global:Get-SgwConfig {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/config"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/config"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves the global management API and UI configuration
    .DESCRIPTION
    Retrieves the global management API and UI configuration
#>

function Global:Get-SgwConfigManagement {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2) {
            Throw "Cmdlet not supported on server with API Version less than 2.0"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/config/management"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Changes the global management API and UI configuration
    .DESCRIPTION
    Changes the global management API and UI configuration
#>

function Global:Update-SgwConfigManagement {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $True,
                Position = 0,
                HelpMessage = "Minimum API Version.")][Int][ValidateSet(1, 2)]$MinApiVersion,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )


    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2) {
            Throw "Cmdlet not supported on server with API Version less than 2.0"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/config/management"
        $Method = "PUT"

        $Body = ConvertTo-Json -InputObject @{ minApiVersion = $MinApiVersion }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Server.SupportedApiVersions = @(Get-SgwVersions -Server $Server)

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve StorageGRID Product Version
    .DESCRIPTION
    Retrieve StorageGRID Product Version
#>

function Global:Get-SgwProductVersion {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/config/product-version"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/config/product-version"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data.productVersion
    }
}

<#
    .SYNOPSIS
    Retrieves the current API version of the management API
    .DESCRIPTION
    Retrieves the current API version of the management API
#>

function Global:Get-SgwVersion {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/versions"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
            $ApiVersion = $Response.Json.APIVersion
        }
        Catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Host "$ResponseBody"
            if ($ResponseBody -match "apiVersion") {
                $ApiVersion = ($ResponseBody | ConvertFrom-Json).APIVersion
            }
            else {
                Write-Warning "Certificate of the server may not be trusted. Use -SkipCertificateCheck switch if you want to skip certificate verification."
                Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
            }
        }

        Write-Output $ApiVersion
    }
}

<#
    .SYNOPSIS
    Retrieves the major versions of the management API supported by the product release
    .DESCRIPTION
    Retrieves the major versions of the management API supported by the product release
#>

function Global:Get-SgwVersions {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/versions"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## containers ##

Set-Alias -Name Get-SgwBucketOwner -Value Get-SgwContainerOwner
<#
    .SYNOPSIS
    Retrieves the Owner of an S3 bucket or Swift container
    .DESCRIPTION
    Retrieves the Owner of an S3 bucket or Swift container
#>

function Global:Get-SgwContainerOwner {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        foreach ($Account in (Get-SgwAccounts -Server $Server)) {
            if ($Account | Get-SgwAccountUsage | select -ExpandProperty buckets | ? { $_.name -eq $Name }) {
                Write-Output $Account
                break
            }
        }
    }
}

Set-Alias -Name Get-SgwBuckets -Value Get-SgwContainers
<#
    .SYNOPSIS
    Lists the S3 buckets or Swift containers for a tenant account
    .DESCRIPTION
    Lists the S3 buckets or Swift containers for a tenant account
#>

function Global:Get-SgwContainers {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Get-SgwBucketConsistency -Value Get-SgwContainerConsistency
<#
    .SYNOPSIS
    Gets the consistency level for an S3 bucket or Swift container
    .DESCRIPTION
    Gets the consistency level for an S3 bucket or Swift container
#>

function Global:Get-SgwContainerConsistency {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/consistency"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Update-SgwBucketConsistency -Value Update-SgwContainerConsistency
<#
    .SYNOPSIS
    Gets the consistency level for an S3 bucket or Swift container
    .DESCRIPTION
    Gets the consistency level for an S3 bucket or Swift container
#>

function Global:Update-SgwContainerConsistency {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Consistency level.")][ValidateSet("all", "strong-global", "strong-site", "default", "available", "weak")][String]$Consistency
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/consistency"
        $Method = "PUT"

        $Body = @{ consistency = $Consistency }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Get-SgwBucketLastAccessTime -Value Get-SgwContainerLastAccessTime
<#
    .SYNOPSIS
    Determines if last access time is enabled for an S3 bucket
    .DESCRIPTION
    Determines if last access time is enabled for an S3 bucket
#>

function Global:Get-SgwContainerLastAccessTime {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/last-access-time"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Enable-SgwBucketLastAccessTime -Value Enable-SgwContainerLastAccessTime
<#
    .SYNOPSIS
    Enables last access time updates for an S3 bucket
    .DESCRIPTION
    Enables last access time updates for an S3 bucket
#>

function Global:Enable-SgwContainerLastAccessTime {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/last-access-time"
        $Method = "PUT"

        $Body = @{ lastAccessTime = "enabled" }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Disable-SgwBucketLastAccessTime -Value Disable-SgwContainerLastAccessTime
<#
    .SYNOPSIS
    Disables last access time updates for an S3 bucket
    .DESCRIPTION
    Disables last access time updates for an S3 bucket
#>

function Global:Disable-SgwContainerLastAccessTime {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/last-access-time"
        $Method = "PUT"

        $Body = @{ lastAccessTime = "disabled" }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Get-SgwBucketMetadataNotification -Value Get-SgwContainerMetadataNotification
Set-Alias -Name Get-SgwBucketMetadataNotificationRules -Value Get-SgwContainerMetadataNotification
Set-Alias -Name Get-SgwContainerMetadataNotificationRules -Value Get-SgwContainerMetadataNotification
<#
    .SYNOPSIS
    Gets the metadata notification (search) configuration for an S3 bucket
    .DESCRIPTION
    Gets the metadata notification (search) configuration for an S3 bucket
#>

function Global:Get-SgwContainerMetadataNotification {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/metadata-notification"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.metadataNotification) {
            $Xml = [xml]$Response.Json.data.metadataNotification
            foreach ($Rule in $Xml.MetadataNotificationConfiguration.Rule) {
                $Rule = [PSCustomObject]@{ Bucket = $Name; Id = $Rule.ID; Status = $Rule.Status; Prefix = $Rule.Prefix; DestinationUrn = $Rule.Destination.Urn }
                Write-Output $Rule
            }
        }
    }
}

Set-Alias -Name Remove-SgwBucketMetadataNotification -Value Remove-SgwContainerMetadataNotification
<#
    .SYNOPSIS
    Romoves the metadata notification (search) configuration for an S3 bucket
    .DESCRIPTION
    Removes the metadata notification (search) configuration for an S3 bucket
#>

function Global:Remove-SgwContainerMetadataNotification {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/metadata-notification"
        $Method = "PUT"

        $Body = @{ }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }
    }
}

Set-Alias -Name Update-SgwBucketMetadataNotificationRule -Value Add-SgwContainerMetadataNotificationRule
Set-Alias -Name Update-SgwContainerMetadataNotificationRule -Value Add-SgwContainerMetadataNotificationRule
Set-Alias -Name Add-SgwBucketMetadataNotificationRule -Value Add-SgwContainerMetadataNotificationRule
<#
    .SYNOPSIS
    Adds a new rule for metadata notification (search) configuration for an S3 bucket
    .DESCRIPTION
    Adds a new rule for metadata notification (search) configuration for an S3 bucket
#>

function Global:Add-SgwContainerMetadataNotificationRule {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Rule ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Rule Status.")][ValidateSet("Enabled", "Disabled")][String]$Status = "Enabled",
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "S3 Key Prefix.")][String]$Prefix = "",
        [parameter(Mandatory = $True,
                Position = 4,
                HelpMessage = "URN of the Destination.")][Alias("Urn")][System.UriBuilder]$DestinationUrn
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/metadata-notification"
        $Method = "PUT"

        $MetadataNotificationRules = [System.Collections.ArrayList]::new()
        $MetadataNotification = Get-SgwContainerMetadataNotification -Server $Server -Name $Name
        foreach ($MetadataNotificationRule in $MetadataNotification) {
            $Null = $MetadataNotificationRules.Add($MetadataNotificationRule)
        }

        if (($MetadataNotificationRules | Where-Object { $_.Id -eq $Id })) {
            $MetadataNotificationRule = $MetadataNotificationRules | Where-Object { $_.Id -eq $Id } | Select -first 1
            if ($Status) {
                $MetadataNotificationRule.Status = $Status
            }
            if ($Prefix) {
                $MetadataNotificationRule.Prefix = $Prefix
            }
            if ($DestinationUrn) {
                $MetadataNotificationRule.DestinationUrn = $DestinationUrn
            }
        }
        else {
            $MetadataNotificationRule = [PSCustomObject]@{ Id = $ID; Status = $Status; Prefix = $Prefix; DestinationUrn = $DestinationUrn }
            $Null = $MetadataNotificationRules.Add($MetadataNotificationRule)
        }

        $MetadataNotificationConfiguration = "<MetadataNotificationConfiguration>"
        foreach ($MetadataNotificationRule in $MetadataNotificationRules) {
            $MetadataNotificationConfiguration += "<Rule><ID>$( $MetadataNotificationRule.Id )</ID><Status>$( $MetadataNotificationRule.Status )</Status><Prefix>$( $MetadataNotificationRule.Prefix )</Prefix><Destination><Urn>$( $MetadataNotificationRule.DestinationUrn )</Urn></Destination></Rule>"
        }
        $MetadataNotificationConfiguration += "</MetadataNotificationConfiguration>"

        $Body = @{ metadataNotification = $MetadataNotificationConfiguration }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.metadataNotification) {
            $Xml = [xml]$Response.Json.data.metadataNotification
            foreach ($Rule in $Xml.MetadataNotificationConfiguration.Rule) {
                $Rule = [PSCustomObject]@{ Bucket = $Name; Id = $Rule.ID; Status = $Rule.Status; Prefix = $Rule.Prefix; DestinationUrn = $Rule.Destination.Urn }
                Write-Output $Rule
            }
        }
    }
}

Set-Alias -Name Remove-SgwBucketMetadataNotificationRule -Value Remove-SgwContainerMetadataNotificationRule
<#
    .SYNOPSIS
    Removes a rule for metadata notification (search) configuration for an S3 bucket
    .DESCRIPTION
    Removes a rule for metadata notification (search) configuration for an S3 bucket
#>

function Global:Remove-SgwContainerMetadataNotificationRule {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Rule ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/metadata-notification"
        $Method = "PUT"

        $MetadataNotificationRules = [System.Collections.ArrayList]::new()
        $MetadataNotification = Get-SgwContainerMetadataNotification -Server $Server -Name $Name
        foreach ($MetadataNotificationRule in $MetadataNotification) {
            if ($MetadataNotificationRule.id -ne $Id) {
                $Null = $MetadataNotificationRules.Add($MetadataNotificationRule)
            }
        }

        $MetadataNotification = "<MetadataNotificationConfiguration>"
        foreach ($MetadataNotificationRule in $MetadataNotificationRules) {
            $MetadataNotification += "<Rule><ID>$( $MetadataNotificationRule.Id )</ID><Status>$( $MetadataNotificationRule.Status )</Status><Prefix>$( $MetadataNotificationRule.Prefix )</Prefix><Destination><Urn>$( $MetadataNotificationRule.DestinationUrn )</Urn></Destination></Rule>"
        }
        $MetadataNotification += "</MetadataNotificationConfiguration>"

        $Body = @{ metadataNotification = $MetadataNotification }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.metadataNotification) {
            $Xml = [xml]$Response.Json.data.metadataNotification
            foreach ($Rule in $Xml.MetadataNotificationConfiguration.Rule) {
                $Rule = [PSCustomObject]@{ Bucket = $Name; Id = $Rule.ID; Status = $Rule.Status; Prefix = $Rule.Prefix; DestinationUrn = $Rule.Destination.Urn }
                Write-Output $Rule
            }
        }
    }
}

Set-Alias -Name Get-SgwBucketNotification -Value Get-SgwContainerNotification
Set-Alias -Name Get-SgwBucketNotificationRules -Value Get-SgwContainerNotification
Set-Alias -Name Get-SgwBucketNotificationTopics -Value Get-SgwContainerNotification
Set-Alias -Name Get-SgwContainerNotificationRules -Value Get-SgwContainerNotification
Set-Alias -Name Get-SgwContainerNotificationTopics -Value Get-SgwContainerNotification
<#
    .SYNOPSIS
    Gets the notification configuration for an S3 bucket
    .DESCRIPTION
    Gets the notification configuration for an S3 bucket
#>

function Global:Get-SgwContainerNotification {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/notification"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.notification) {
            Write-Verbose $Response.Json.data.notification
            $Xml = [xml]$Response.Json.data.notification
            foreach ($Topic in $Xml.NotificationConfiguration.TopicConfiguration) {
                $Topic = [PSCustomObject]@{ Bucket = $Name; Id = $Topic.Id; Topic = $Topic.Topic; Event = $Topic.Event; Prefix = ($Topic.Filter.S3Key.FilterRule | ? { $_.Name -eq "prefix" } | Select -ExpandProperty Value -First 1); Suffix = ($Topic.Filter.S3Key.FilterRule | ? { $_.Name -eq "suffix" } | Select -ExpandProperty Value -First 1) }
                Write-Output $Topic
            }
        }
    }
}

Set-Alias -Name Remove-SgwBucketNotification -Value Remove-SgwContainerNotification
<#
    .SYNOPSIS
    Remove the notification configuration for an S3 bucket
    .DESCRIPTION
    Remove the notification configuration for an S3 bucket
#>

function Global:Remove-SgwContainerNotification {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/notification"
        $Method = "PUT"

        $Body = @{ }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }
    }
}

Set-Alias -Name Add-SgwBucketNotificationRule -Value Add-SgwContainerNotificationTopic
Set-Alias -Name Add-SgwBucketNotificationTopic -Value Add-SgwContainerNotificationTopic
Set-Alias -Name Add-SgwContainerNotificationRule -Value Add-SgwContainerNotificationTopic
<#
    .SYNOPSIS
    Add a topic to the notification configuration for an S3 bucket
    .DESCRIPTION
    Add a topic to the notification configuration for an S3 bucket
#>

function Global:Add-SgwContainerNotificationTopic {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Topic ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(Mandatory = $True,
                Position = 3,
                HelpMessage = "URN of the Topic.")][Alias("TopicUrn", "Urn")][System.UriBuilder]$Topic,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "Event to trigger notifications for.")][Alias("Event")][String[]]$Events,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "Prefix filter.")][String]$Prefix,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "Suffix filter.")][String]$Suffix

    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/notification"
        $Method = "PUT"

        $NotificationTopics = [System.Collections.ArrayList]::new()
        $Notifications = Get-SgwContainerNotification -Server $Server -Name $Name
        foreach ($NotificationTopic in $Notifications) {
            $Null = $NotificationTopics.Add($NotificationTopic)
        }

        if (($NotificationTopics | Where-Object { $_.Id -eq $Id })) {
            $NotificationTopic = $NotificationTopics | Where-Object { $_.Id -eq $Id } | Select -first 1
            if ($Topic) {
                $NotificationTopic.Topic = $Topic
            }
            if ($Events) {
                $NotificationTopic.Events = $Events
            }
            if ($Prefix) {
                $NotificationTopic.Prefix = $Prefix
            }
            if ($Suffix) {
                $NotificationTopic.Suffix = $Suffix
            }
        }
        else {
            $NotificationTopic = [PSCustomObject]@{ Id = $ID; Topic = $Topic; Events = $Events; Prefix = $Prefix; Suffix = $Suffix }
            $Null = $NotificationTopics.Add($NotificationTopic)
        }

        $NotificationConfiguration = "<NotificationConfiguration>"
        foreach ($NotificationTopic in $NotificationTopics) {
            $NotificationConfiguration += "<TopicConfiguration><Id>$( $NotificationTopic.Id )</Id><Topic>$( $NotificationTopic.Topic )</Topic>"
            foreach ($Event in $Events) {
                $NotificationConfiguration += "<Event>$Event</Event>"
            }
            $NotificationConfiguration += "<Filter><S3Key><FilterRule><Name>prefix</Name><Value>$( $NotificationTopic.Prefix )</Value></FilterRule><FilterRule><Name>suffix</Name><Value>$( $NotificationTopic.Suffix )</Value></FilterRule></S3Key></Filter></TopicConfiguration>"
        }
        $NotificationConfiguration += "</NotificationConfiguration>"

        $Body = @{ notification = $NotificationConfiguration }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.notification) {
            Write-Verbose $Response.Json.data.notification
            $Xml = [xml]$Response.Json.data.notification
            foreach ($Topic in $Xml.NotificationConfiguration.TopicConfiguration) {
                $Topic = [PSCustomObject]@{ Bucket = $Name; Id = $Topic.Id; Topic = $Topic.Topic; Event = $Topic.Event; Prefix = ($Topic.Filter.S3Key.FilterRule | ? { $_.Name -eq "prefix" } | Select -ExpandProperty Value -First 1); Suffix = ($Topic.Filter.S3Key.FilterRule | ? { $_.Name -eq "suffix" } | Select -ExpandProperty Value -First 1) }
                Write-Output $Topic
            }
        }
    }
}

Set-Alias -Name Remove-SgwBucketNotificationRule -Value Remove-SgwContainerNotificationTopic
Set-Alias -Name Remove-SgwBucketNotificationTopic -Value Remove-SgwContainerNotificationTopic
Set-Alias -Name Remove-SgwContainerNotificationRule -Value Remove-SgwContainerNotificationTopic
<#
    .SYNOPSIS
    Remove a topic from the notification configuration for an S3 bucket
    .DESCRIPTION
    Remove a topic from the notification configuration for an S3 bucket
#>

function Global:Remove-SgwContainerNotificationTopic {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Topic ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/notification"
        $Method = "PUT"

        $NotificationTopics = [System.Collections.ArrayList]::new()
        $Notifications = Get-SgwContainerNotification -Server $Server -Name $Name
        foreach ($NotificationTopic in $Notifications) {
            if ($NotificationTopic.id -ne $Id) {
                $Null = $NotificationTopics.Add($NotificationTopic)
            }
        }

        if (($NotificationTopics | Where-Object { $_.Id -eq $Id })) {
            $NotificationTopic = $NotificationTopics | Where-Object { $_.Id -eq $Id } | Select -first 1
            if ($Topic) {
                $NotificationTopic.Topic = $Topic
            }
            if ($Events) {
                $NotificationTopic.Events = $Events
            }
            if ($Prefix) {
                $NotificationTopic.Prefix = $Prefix
            }
            if ($Suffix) {
                $NotificationTopic.Suffix = $Suffix
            }
        }
        else {
            $NotificationTopic = [PSCustomObject]@{ Id = $ID; Topic = $Topic; Events = $Events; Prefix = $Prefix; Suffix = $Suffix }
            $Null = $NotificationTopics.Add($NotificationTopic)
        }

        $NotificationConfiguration = "<NotificationConfiguration>"
        foreach ($NotificationTopic in $NotificationTopics) {
            $NotificationConfiguration += "<TopicConfiguration><Id>$( $NotificationTopic.Id )</Id><Topic>$( $NotificationTopic.Topic )</Topic>"
            foreach ($Event in $Events) {
                $NotificationConfiguration += "<Event>$Event</Event>"
            }
            $NotificationConfiguration += "<Filter><S3Key><FilterRule><Name>prefix</Name><Value>$( $NotificationTopic.Prefix )</Value></FilterRule><FilterRule><Name>suffix</Name><Value>$( $NotificationTopic.Suffix )</Value></FilterRule></S3Key></Filter></TopicConfiguration>"
        }
        $NotificationConfiguration += "</NotificationConfiguration>"

        $Body = @{ metadataNotification = $MetadataNotification }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.notification) {
            Write-Verbose $Response.Json.data.notification
            $Xml = [xml]$Response.Json.data.notification
            foreach ($Topic in $Xml.NotificationConfiguration.TopicConfiguration) {
                $Topic = [PSCustomObject]@{ Bucket = $Name; Id = $Topic.Id; Topic = $Topic.Topic; Event = $Topic.Event; Prefix = ($Topic.Filter.S3Key.FilterRule | ? { $_.Name -eq "prefix" } | Select -ExpandProperty Value -First 1); Suffix = ($Topic.Filter.S3Key.FilterRule | ? { $_.Name -eq "suffix" } | Select -ExpandProperty Value -First 1) }
                Write-Output $Topic
            }
        }
    }
}

Set-Alias -Name Get-SgwBucketReplication -Value Get-SgwContainerReplication
Set-Alias -Name Get-SgwBucketReplicationRules -Value Get-SgwContainerReplication
Set-Alias -Name Get-SgwContainerReplicationRules -Value Get-SgwContainerReplication
<#
    .SYNOPSIS
    Gets the replication configuration for an S3 bucket or Swift container
    .DESCRIPTION
    Gets the replication configuration for an S3 bucket or Swift container
#>

function Global:Get-SgwContainerReplication {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/replication"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.replication) {
            $Xml = [xml]$Response.Json.data.replication
            foreach ($Rule in $Xml.ReplicationConfiguration.Rule) {
                $Rule = [PSCustomObject]@{ Bucket = $Name; Id = $Rule.ID; Status = $Rule.Status; Prefix = $Rule.Prefix; DestinationUrn = $Rule.Destination.Bucket; DestinationStorageClass = $Rule.Destination.StorageClass }
                Write-Output $Rule
            }
        }
    }
}

Set-Alias -Name Remove-SgwBucketReplication -Value Remove-SgwContainerReplication
<#
    .SYNOPSIS
    Removes the replication configuration for an S3 bucket or Swift container
    .DESCRIPTION
    Removes the replication configuration for an S3 bucket or Swift container
#>

function Global:Remove-SgwContainerReplication {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/replication"
        $Method = "PUT"

        $Body = @{ }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }
    }
}

Set-Alias -Name Update-SgwBucketReplicationRule -Value Add-SgwContainerReplicationRule
Set-Alias -Name Update-SgwBucketReplicationRule -Value Add-SgwContainerReplicationRule
Set-Alias -Name Add-SgwBucketReplicationRule -Value Add-SgwContainerReplicationRule
<#
    .SYNOPSIS
    Adds a replication configuration rule for an S3 bucket or Swift container
    .DESCRIPTION
    Adds a replication configuration rule for an S3 bucket or Swift container
#>

function Global:Add-SgwContainerReplicationRule {
    [CmdletBinding(DefaultParameterSetName = "bucket")]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name to be replicated.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Rule ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Rule Status.")][ValidateSet("Enabled", "Disabled")][String]$Status = "Enabled",
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "S3 Key Prefix.")][String]$Prefix = "",
        [parameter(Mandatory = $True,
                Position = 4,
                ParameterSetName = "bucket",
                HelpMessage = "Destination Bucket name.")][String]$DestinationBucket,
        [parameter(Mandatory = $True,
                Position = 4,
                ParameterSetName = "urn",
                HelpMessage = "Destination Bucket name.")][String]$DestinationUrn,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "Destination Storage Class.")][ValidateSet("STANDARD", "STANDARD_IA", "RRS")][String]$DestinationStorageClass = "STANDARD",
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "IAM Role.")][String]$Role
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/replication"
        $Method = "PUT"

        $ReplicationRules = [System.Collections.ArrayList]::new()
        $Replication = Get-SgwContainerReplication -Server $Server -Name $Name
        foreach ($ReplicationRule in $Replication) {
            $Null = $ReplicationRules.Add($ReplicationRule)
        }

        if ($DestinationBucket) {
            $DestinationUrn = Get-SgwEndpoints | Where-Object { $_.endpointURN -match ":$DestinationBucket$" } | Select-Object -ExpandProperty endpointURN -First 1
        }

        if (($ReplicationRules | Where-Object { $_.Id -eq $Id })) {
            $ReplicationRule = $ReplicationRules | Where-Object { $_.Id -eq $Id } | Select -first 1
            if ($Status) {
                $ReplicationRule.Status = $Status
            }
            if ($Prefix) {
                $ReplicationRule.Prefix = $Prefix
            }
            $ReplicationRule.DestinationUrn = $DestinationUrn
            if ($DestinationStorageClass) {
                $ReplicationRule.DestinationStorageClass = $DestinationStorageClass
            }
            if ($Role) {
                $ReplicationRule.Role = $Role
            }
        }
        else {
            $ReplicationRule = [PSCustomObject]@{ Id = $ID; Status = $Status; Prefix = $Prefix; DestinationUrn = $DestinationUrn; DestinationStorageClass = $DestinationStorageClass; Role = $Role }
            $Null = $ReplicationRules.Add($ReplicationRule)
        }

        $ReplicationConfiguration = "<ReplicationConfiguration>"
        foreach ($ReplicationRule in $ReplicationRules) {
            $ReplicationConfiguration += "<Rule><ID>$( $ReplicationRule.Id )</ID><Status>$( $ReplicationRule.Status )</Status><Prefix>$( $ReplicationRule.Prefix )</Prefix><Destination><Bucket>$( $ReplicationRule.DestinationUrn )</Bucket><StorageClass>$( $ReplicationRule.DestinationStorageClass )</StorageClass></Destination><Role>$( $ReplicationRule.Role )</Role></Rule>"
        }
        $ReplicationConfiguration += "</ReplicationConfiguration>"

        $Body = @{ replication = $ReplicationConfiguration }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.replication) {
            $Xml = [xml]$Response.Json.data.replication
            foreach ($Rule in $Xml.ReplicationConfiguration.Rule) {
                $Rule = [PSCustomObject]@{ Bucket = $Name; Id = $Rule.ID; Status = $Rule.Status; Prefix = $Rule.Prefix; DestinationUrn = $Rule.Destination.Bucket; DestinationStorageClass = $Rule.Destination.StorageClass }
                Write-Output $Rule
            }
        }
    }
}

Set-Alias -Name Remove-SgwBucketReplicationRule -Value Remove-SgwContainerReplicationRule
<#
    .SYNOPSIS
    Removes a replication configuration rule for an S3 bucket or Swift container
    .DESCRIPTION
    Removes a replication configuration rule for an S3 bucket or Swift container
#>

function Global:Remove-SgwContainerReplicationRule {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Swift Container or S3 Bucket name.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("Container", "Bucket")][String]$Name,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Rule ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Containers is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/containers/$Name/replication"
        $Method = "PUT"

        $ReplicationRules = [System.Collections.ArrayList]::new()
        $Replication = Get-SgwContainerReplication -Server $Server -Name $Name
        foreach ($ReplicationRule in $Replication) {
            if ($ReplicationRule.id -ne $Id) {
                $Null = $ReplicationRules.Add($ReplicationRule)
            }
        }

        $ReplicationConfiguration = "<ReplicationConfiguration>"
        foreach ($ReplicationRule in $ReplicationRules) {
            $ReplicationConfiguration += "<Rule><ID>$( $ReplicationRule.Id )</ID><Status>$( $ReplicationRule.Status )</Status><Prefix>$( $ReplicationRule.Prefix )</Prefix><Destination><Bucket>$( $ReplicationRule.DestinationUrn )</Bucket><StorageClass>$( $ReplicationRule.DestinationStorageClass )</StorageClass></Destination><Role>$( $ReplicationRule.Role )</Role></Rule>"
        }
        $ReplicationConfiguration += "</ReplicationConfiguration>"

        $Body = @{ replication = $ReplicationConfiguration }
        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Response.Json.data.replication) {
            $Xml = [xml]$Response.Json.data.replication
            foreach ($Rule in $Xml.ReplicationConfiguration.Rule) {
                $Rule = [PSCustomObject]@{ Bucket = $Name; Id = $Rule.ID; Status = $Rule.Status; Prefix = $Rule.Prefix; DestinationUrn = $Rule.Destination.Bucket; DestinationStorageClass = $Rule.Destination.StorageClass }
                Write-Output $Rule
            }
        }
    }
}

## deactivated-features ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieves the deactivated features configuration
    .DESCRIPTION
    Retrieves the deactivated features configuration
#>

function Global:Get-SgwDeactivatedFeatures {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2) {
            Throw "This Cmdlet is only supported for API Version 2.0 and above"
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/deactivated-features"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/deactivated-features"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Deactivates specific features. If no feature is selected, all features will be enabled again.
    .DESCRIPTION
    Deactivates specific features. If no feature is selected, all features will be enabled again.
#>

function Global:Update-SgwDeactivatedFeatures {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "Deactivate Alarm Acknowledgements.")][Boolean]$AlarmAcknowledgment,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "Deactivate Other Grid Configuration.")][Boolean]$OtherGridConfiguration,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Deactivate Grid Topology Page Configuration.")][Boolean]$GridTopologyPageConfiguration,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "Deactivate Management of Tenant Accounts.")][Boolean]$TenantAccounts,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "Deactivate changing of tenant root passwords.")][Boolean]$ChangeTenantRootPassword,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "Deactivate maintenance.")][Boolean]$Maintenance,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "Deactivates activating features. This cannot be undone!")][Boolean]$ActivateFeatures,
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "Deactivates managing of own S3 Credentials.")][Boolean]$ManageOwnS3Credentials,
        [parameter(Mandatory = $False,
                Position = 7,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2) {
            Throw "This Cmdlet is only supported for API Version 2.0 and above"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/deactivated-features"
        $Method = "PUT"

        $Body = @{ }
        if ($AlarmAcknowledgment -or $OtherGridConfiguration -or $GridTopologyPageConfiguration -or $TenantAccounts -or $ChangeTenantRootPassword -or $Maintenance -or $ActivateFeatures) {
            $Body.grid = @{ }
        }
        if ($AlarmAcknowledgment) {
            $Body.grid.alarmAcknowledgment = $AlarmAcknowledgment
        }
        if ($OtherGridConfiguration) {
            $Body.grid.otherGridConfiguration = $OtherGridConfiguration
        }
        if ($GridTopologyPageConfiguration) {
            $Body.grid.gridTopologyPageConfiguration = $GridTopologyPageConfiguration
        }
        if ($TenantAccounts) {
            $Body.grid.tenantAccounts = $TenantAccounts
        }
        if ($ChangeTenantRootPassword) {
            $Body.grid.changeTenantRootPassword = $ChangeTenantRootPassword
        }
        if ($Maintenance) {
            $Body.grid.maintenance = $Maintenance
        }
        if ($ActivateFeatures) {
            $caption = "Please Confirm"
            $message = "Are you sure you want to proceed with permanently deactivating the activation of features (this can't be undone!):"
            [int]$defaultChoice = 0
            $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Do the job."
            $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Do not do the job."
            $options = [System.Management.Automation.Host.ChoiceDescription[]]($no, $yes)
            $choiceRTN = $host.ui.PromptForChoice($caption, $message, $options, $defaultChoice)
            if ($choiceRTN -eq 1) {
                $Body.grid.activateFeatures = $ActivateFeatures
            }
            else {
                Write-Host "Deactivating of permanent feature activation aborted."
                return
            }
        }
        if ($ManageOwnS3Credentials) {
            $Body.tenant = @{ manageOwnS3Credentials = $ManageOwnS3Credentials }
        }
        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## dns-servers ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieve StorageGRID DNS Servers
    .DESCRIPTION
    Retrieve StorageGRID DNS Servers
#>

function Global:Get-SgwDNSServers {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/dns-servers"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve StorageGRID DNS Servers
    .DESCRIPTION
    Retrieve StorageGRID DNS Servers
#>

function Global:Replace-SgwDNSServers {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "List of IP addresses of the external DNS servers.")][String[]]$DNSServers
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/dns-servers"
        $Method = "PUT"

        $Body = '["' + ($DNSServers -join '","') + '"]'

        Write-Verbose $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## endpoints ##

<#
    .SYNOPSIS
    Gets the list of endpoints
    .DESCRIPTION
    Gets the list of endpoints
#>

function Global:Get-SgwEndpoints {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default"
    )

    Begin {
        if (!$Server -and !$CurrentSgwServer.Name) {
            $Profile = Get-SgwProfile -ProfileName $ProfileName
            $Server = Connect-SgwServer -Name $Profile.Name -Credential $Profile.Credential -AccountId $Profile.AccountId -SkipCertificateCheck:$Profile.SkipCertificateCheck -DisableAutomaticAccessKeyGeneration:$Profile.disalble_automatic_access_key_generation -TemporaryAccessKeyExpirationTime $Profile.temporary_access_key_expiration_time -S3EndpointUrl $Profile.S3EndpointUrl -SwiftEndpointUrl $Profile.SwiftEndpointUrl -Transient
        }

        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Endpoints is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/endpoints"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name New-SgwEndpoint -Value Add-SgwEndpoint
<#
    .SYNOPSIS
    Creates a new endpoint
    .DESCRIPTION
    Creates a new endpoint
#>

function Global:Add-SgwEndpoint {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Display Name of Endpoint.")][String]$DisplayName,
        [parameter(Mandatory = $True,
                Position = 3,
                HelpMessage = "URI of the Endpoint.")][Alias("Uri")][System.UriBuilder]$EndpointUri,
        [parameter(Mandatory = $True,
                Position = 4,
                HelpMessage = "URN of the Endpoint.")][Alias("Urn")][System.UriBuilder]$EndpointUrn,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "CA Certificate String.")][String]$CaCert,
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "Skip endpoint certificate check.")][Alias("insecureTLS")][Switch]$SkipCertificateCheck,
        [parameter(Mandatory = $False,
                Position = 7,
                HelpMessage = "S3 Access Key authorized to use the endpoint.")][String]$AccessKey,
        [parameter(Mandatory = $False,
                Position = 8,
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")][String]$SecretAccessKey,
        [parameter(Mandatory = $False,
                Position = 9,
                HelpMessage = "Test the validity of the endpoint but do not save it.")][Switch]$Test,
        [parameter(Mandatory = $False,
                Position = 10,
                HelpMessage = "Force saving without endpoint validation.")][Alias("ForceSave")][Switch]$Force
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }

        if (!$Server) {
            $Server = Connect-SgwServer -ProfileName $ProfileName -Transient
        }

        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Endpoints is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/endpoints"
        $Method = "POST"

        if ($Test.isPresent) {
            $Uri += "?test=true"
        }
        elseif ($Force.isPresent) {
            $Uri += "?forceSave=true"
        }

        $EndpointBucketName = $EndpointUrn.Uri.ToString() -replace ".*:.*:.*:.*:.*:(.*)",'$1'
        # Convert Destination Bucket Name to IDN mapping to support Unicode Names
        $EndpointBucketName = [System.Globalization.IdnMapping]::new().GetAscii($EndpointBucketName).ToLower()
        $EndpointUrnPrefix = $EndpointUrn.Uri.ToString() -replace "(.*:.*:.*:.*:.*:).*",'$1'
        $EndpointUrn = [System.UriBuilder]"$EndpointUrnPrefix$EndpointBucketName"

        $Body = @{ }
        $Body.displayName = $DisplayName
        $Body.endpointURI = $EndpointUri.Uri
        $Body.endpointURN = $EndpointUrn.Uri
        $Body.caCert = $CaCert
        $Body.insecureTLS = $SkipCertificateCheck.isPresent
        $Body.credentials = @{ }
        $Body.credentials.accessKeyId = $AccessKey
        $Body.credentials.secretAccessKey = $SecretAccessKey

        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Remove-SgwS3Endpoint -Value Remove-SgwEndpoint
Set-Alias -Name Remove-SgwSnsEndpoint -Value Remove-SgwEndpoint
Set-Alias -Name Remove-SgwEsEndpoint -Value Remove-SgwEndpoint
<#
    .SYNOPSIS
    Deletes a single endpoint
    .DESCRIPTION
    Deletes a single endpoint
#>

function Global:Remove-SgwEndpoint {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Profile to use for connection.")][Alias("Profile")][String]$ProfileName="default",
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Endpoint ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }

        if (!$Server) {
            $Server = Connect-SgwServer -ProfileName $ProfileName -Transient
        }

        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Endpoints is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/endpoints/$Id"
        $Method = "DELETE"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Get-SgwS3Endpoint -Value Get-SgwEndpoint
Set-Alias -Name Get-SgwSnsEndpoint -Value Get-SgwEndpoint
Set-Alias -Name Get-SgwEsEndpoint -Value Get-SgwEndpoint
<#
    .SYNOPSIS
    Retrieves a single endpoint
    .DESCRIPTION
    Retrieves a single endpoint
#>

function Global:Get-SgwEndpoint {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Endpoint ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Endpoints is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/endpoints/$Id"
        $Method = "GET"

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Replaces a single endpoint
    .DESCRIPTION
    Replaces a single endpoint
#>

function Global:Update-SgwEndpoint {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Endpoint ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Display Name of Endpoint.")][String]$DisplayName,
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndUrnAndProfile",
                HelpMessage = "URI of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "URI of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndNameAndProfile",
                HelpMessage = "URI of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "URI of the Endpoint.")][Alias("Uri")][System.UriBuilder]$EndpointUri,
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndUrnAndProfile",
                HelpMessage = "URN of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "URN of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndUrnAndProfile",
                HelpMessage = "URN of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "URN of the Endpoint.")][Alias("Urn")][System.UriBuilder]$EndpointUrn,
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndUrnAndProfile",
                HelpMessage = "Region.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "Region.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndNameAndProfile",
                HelpMessage = "Region.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "Region.")][String]$Region,
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndNameAndProfile",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndNameAndProfile",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "NameOnlyAndProfile",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "NameOnlyAndKey",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")][Alias("Bucket", "Topic", "Domain")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "CA Certificate String.")][String]$CaCert,
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "Skip endpoint certificate check.")][Alias("insecureTLS")][Switch]$SkipCertificateCheck,
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndUrnAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndNameAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndUrnAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndNameAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "NameOnlyAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")][String]$Profile = "default",
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "NameOnlyAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")][Alias("AccessKeyId")][String]$AccessKey,
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "NameOnlyAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")][String]$SecretAccessKey,
        [parameter(Mandatory = $False,
                Position = 9,
                HelpMessage = "Test the validity of the endpoint but do not save it.",
                ParameterSetName = "test")][Switch]$Test,
        [parameter(Mandatory = $False,
                Position = 10,
                HelpMessage = "Force saving without endpoint validation.",
                ParameterSetName = "force")][Alias("ForceSave")][Switch]$Force
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Endpoints is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/endpoints/$id"
        $Method = "PUT"

        if ($Test.isPresent) {
            $Uri += "?test=true"
        }
        elseif ($Force.isPresent) {
            $Uri += "?forceSave=true"
        }

        if ($Profile) {
            $Config = Get-AwsCredential -Profile $Profile
            if (!$Config.aws_access_key_id -and !$Config.aws_secret_access_key) {
                throw "No Credentials found for Profile $Profile. Either add credentials using Add-AwsCredential or specify AccessKey and SecretAccessKey"
            }
            $AccessKey = $Config.aws_access_key_id
            $SecretAccessKey = $Config.aws_secret_access_key
            if (!$Region -and $Name -and !$EndpointUri) {
                if ($Config.Region) {
                    $Region = $Config.Region
                    if ($Region -eq "us-east-1" -and !$Config.endpoint_url) {
                        $EndpointUri = "s3.amazonaws.com"
                    }
                    elseif (!$Config.endpoint_url) {
                        $EndpointUri = "s3.$Region.amazonaws.com"
                    }
                    else {
                        $EndpointUri = [System.UriBuilder]$Config.endpoint_url
                    }
                }
                else {
                    Throw "No Endpoint URI and Region specified and Region not included in configuration of profile $Profile"
                }
            }
        }

        if ($Name) {
            if ($EndpointUri -match "amazonaws.com") {
                $EndpointUrn = "arn:aws:s3:::$Name"
            }
            else {
                $EndpointUrn = "urn:sgws:s3:::$Name"
            }
        }

        $Body = @{ }
        $Body.displayName = $DisplayName
        $Body.endpointURI = $EndpointUri.Uri
        $Body.endpointURN = $EndpointUrn.Uri
        $Body.caCert = $CaCert
        $Body.insecureTLS = $SkipCertificateCheck.isPresent
        $Body.credentials = @{ }
        $Body.credentials.accessKeyId = $AccessKey
        $Body.credentials.secretAccessKey = $SecretAccessKey

        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Gets the list of S3 endpoints
    .DESCRIPTION
    Gets the list of S3 endpoints
#>

function Global:Get-SgwS3Endpoints {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Process {
        Get-SgwEndpoints | Where-Object { $_.endpointURN -match "[^:]*:[^:]*:s3:" }
    }
}

Set-Alias -Name New-SgwS3Endpoint -Value Add-SgwS3Endpoint
<#
    .SYNOPSIS
    Creates a new S3 endpoint
    .DESCRIPTION
    Creates a new S3 endpoint
#>

function Global:Add-SgwS3Endpoint {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Display Name of Endpoint.")][String]$DisplayName,
        [parameter(Mandatory = $True,
                Position = 2,
                ParameterSetName = "RegionAndName",
                HelpMessage = "Region.")][String]$Region,
        [parameter(Mandatory = $True,
                Position = 3,
                ParameterSetName = "RegionAndName",
                HelpMessage = "Bucket Name.")]
        [parameter(Mandatory = $True,
                Position = 3,
                ParameterSetName = "NameOnly",
                HelpMessage = "Bucket Name.")][Alias("Bucket")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "CA Certificate String.")][String]$CaCert,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "Skip endpoint certificate check.")][Alias("insecureTLS")][Switch]$SkipCertificateCheck,
        [parameter(Mandatory = $False,
                Position = 6,
                ParameterSetName = "RegionAndName",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 6,
                ParameterSetName = "NameOnly",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")][String]$Profile = "default",
        [parameter(Mandatory = $False,
                Position = 6,
                ParameterSetName = "RegionAndName",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 6,
                ParameterSetName = "NameOnly",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")][String]$AccessKey,
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndName",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "NameOnly",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")][String]$SecretAccessKey,
        [parameter(Mandatory = $False,
                Position = 8,
                HelpMessage = "Test the validity of the endpoint but do not save it.",
                ParameterSetName = "test")][Switch]$Test,
        [parameter(Mandatory = $False,
                Position = 9,
                HelpMessage = "Force saving without endpoint validation.",
                ParameterSetName = "force")][Alias("ForceSave")][Switch]$Force
    )

    Process {
        if ($Profile) {
            $Config = Get-AwsCredential -Profile $Profile
            if (!$Config.aws_access_key_id -and !$Config.aws_secret_access_key) {
                throw "No Credentials found for Profile $Profile. Either add credentials using Add-AwsCredential or specify AccessKey and SecretAccessKey"
            }
            $AccessKey = $Config.aws_access_key_id
            $SecretAccessKey = $Config.aws_secret_access_key
            if (!$Region -and $Config.Region) {
                $Region = $Config.Region
            }
        }

        if (!$Region) {
            $Region = "us-east-1"
        }

        if (!$EndpointUri) {
            if ($Region -eq "us-east-1" -and !$Config.endpoint_url) {
                $EndpointUri = "https://s3.amazonaws.com"
            }
            elseif (!$Config.endpoint_url) {
                $EndpointUri = "https://s3.$Region.amazonaws.com"
            }
            else {
                $EndpointUri = [System.UriBuilder]$Config.endpoint_url
            }
        }

        if ($Name) {
            if ($EndpointUri -match "amazonaws.com") {
                $EndpointUrn = "arn:aws:s3:::$Name"
            }
            else {
                $EndpointUrn = "urn:sgws:s3:::$Name"
            }
        }

        Add-SgwEndpoint -Server $Server -DisplayName $DisplayName -EndpointUri $EndpointUri -EndpointUrn $EndpointUrn -CaCert $CaCert -SkipCertificateCheck:$SkipCertificateCheck -AccessKey $AccessKey -SecretAccessKey $SecretAccessKey -Test:$Test -Force:$Force
    }
}

<#
    .SYNOPSIS
    Replaces a single endpoint
    .DESCRIPTION
    Replaces a single endpoint
#>

function Global:Update-SgwS3Endpoint {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Endpoint ID.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "Display Name of Endpoint.")][String]$DisplayName,
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndUrnAndProfile",
                HelpMessage = "URI of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "URI of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndNameAndProfile",
                HelpMessage = "URI of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "URI of the Endpoint.")][Alias("Uri")][System.UriBuilder]$EndpointUri,
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndUrnAndProfile",
                HelpMessage = "URN of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "URN of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndUrnAndProfile",
                HelpMessage = "URN of the Endpoint.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "URN of the Endpoint.")][Alias("Urn")][System.UriBuilder]$EndpointUrn,
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndUrnAndProfile",
                HelpMessage = "Region.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "Region.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndNameAndProfile",
                HelpMessage = "Region.")]
        [parameter(Mandatory = $False,
                Position = 3,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "Region.")][String]$Region,
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndNameAndProfile",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndNameAndProfile",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "NameOnlyAndProfile",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")]
        [parameter(Mandatory = $False,
                Position = 4,
                ParameterSetName = "NameOnlyAndKey",
                HelpMessage = "Bucket Name for CloudMirror, Topic Name for SNS or Domain-Name/Index-Name/Type-Name for ElasticSearch.")][Alias("Bucket", "Topic", "Domain")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "CA Certificate String.")][String]$CaCert,
        [parameter(Mandatory = $False,
                Position = 6,
                HelpMessage = "Skip endpoint certificate check.")][Alias("insecureTLS")][Switch]$SkipCertificateCheck,
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndUrnAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndNameAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndUrnAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndNameAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "NameOnlyAndProfile",
                HelpMessage = "StorageGRID Profile which has credentials and region to be used for this endpoint.")][String]$Profile = "default",
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 7,
                ParameterSetName = "NameOnlyAndKey",
                HelpMessage = "S3 Access Key authorized to use the endpoint.")][Alias("AccessKeyId")][String]$AccessKey,
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "UriAndUrnAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "UriAndNameAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "RegionAndUrnAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "RegionAndNameAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")]
        [parameter(Mandatory = $False,
                Position = 8,
                ParameterSetName = "NameOnlyAndKey",
                HelpMessage = "S3 Secret Access Key authorized to use the endpoint.")][String]$SecretAccessKey,
        [parameter(Mandatory = $False,
                Position = 9,
                HelpMessage = "Test the validity of the endpoint but do not save it.",
                ParameterSetName = "test")][Switch]$Test,
        [parameter(Mandatory = $False,
                Position = 10,
                HelpMessage = "Force saving without endpoint validation.",
                ParameterSetName = "force")][Alias("ForceSave")][Switch]$Force
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -lt 2.1) {
            Throw "Managing Endpoints is only Supported from StorageGRID 11.0"
        }
        if (!$Server.AccountId) {
            throw "Not connected as tenant user. Use Connect-SgwServer with the parameter accountId to connect to a tenant."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/org/endpoints/$id"
        $Method = "PUT"

        if ($Test.isPresent) {
            $Uri += "?test=true"
        }
        elseif ($Force.isPresent) {
            $Uri += "?forceSave=true"
        }

        if ($Profile) {
            $Config = Get-AwsCredential -Profile $Profile
            if (!$Config.aws_access_key_id -and !$Config.aws_secret_access_key) {
                throw "No Credentials found for Profile $Profile. Either add credentials using Add-AwsCredential or specify AccessKey and SecretAccessKey"
            }
            $AccessKey = $Config.aws_access_key_id
            $SecretAccessKey = $Config.aws_secret_access_key
            if (!$Region -and $Name -and !$EndpointUri) {
                if ($Config.Region) {
                    $Region = $Config.Region
                    if ($Region -eq "us-east-1" -and !$Config.endpoint_url) {
                        $EndpointUri = "s3.amazonaws.com"
                    }
                    elseif (!$Config.endpoint_url) {
                        $EndpointUri = "s3.$Region.amazonaws.com"
                    }
                    else {
                        $EndpointUri = [System.UriBuilder]$Config.endpoint_url
                    }
                }
                else {
                    Throw "No Endpoint URI and Region specified and Region not included in configuration of profile $Profile"
                }
            }
        }

        if ($Name) {
            if ($EndpointUri -match "amazonaws.com") {
                $EndpointUrn = "arn:aws:s3:::$Name"
            }
            else {
                $EndpointUrn = "urn:sgws:s3:::$Name"
            }
        }

        $Body = @{ }
        $Body.displayName = $DisplayName
        $Body.endpointURI = $EndpointUri.Uri
        $Body.endpointURN = $EndpointUrn.Uri
        $Body.caCert = $CaCert
        $Body.insecureTLS = $SkipCertificateCheck.isPresent
        $Body.credentials = @{ }
        $Body.credentials.accessKeyId = $AccessKey
        $Body.credentials.secretAccessKey = $SecretAccessKey

        $Body = ConvertTo-Json -InputObject $Body

        Try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## endpoint-domain-names ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Lists endpoint domain names
    .DESCRIPTION
    Lists endpoint domain names
#>

function Global:Get-SgwEndpointDomainNames {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/domain-names"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Add-SgwEndpointDomainNames -Value Replace-SgwEndpointDomainNames
Set-Alias -Name New-SgwEndpointDomainNames -Value Replace-SgwEndpointDomainNames
<#
    .SYNOPSIS
    Change the endpoint domain names
    .DESCRIPTION
    Change the endpoint domain names
#>

function Global:Replace-SgwEndpointDomainNames {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "List of DNS names to be used as S3/Swift endpoints.")][String[]]$EndpointDomainNames
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/domain-names"
        $Method = "PUT"

        $Body = ConvertTo-Json -InputObject $EndpointDomainNames

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## erasure-coding

# complete as of API 2.1

<#
    .SYNOPSIS
    Lists EC profiles
    .DESCRIPTION
    Lists EC profiles
#>

function Global:Get-SgwEcProfiles {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/ec-profiles"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## erasure-coding

<#
    .SYNOPSIS
    Lists EC profiles
    .DESCRIPTION
    Lists EC profiles
#>

function Global:Get-SgwEcSchemes {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/schemes"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## expansion ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Cancels the expansion procedure and resets all user configuration of expansion grid nodes
    .DESCRIPTION
    Cancels the expansion procedure and resets all user configuration of expansion grid nodes
#>

function Global:Stop-SgwExpansion {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion"
        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves the status of the current expansion procedure
    .DESCRIPTION
    Retrieves the status of the current expansion procedure
#>

function Global:Get-SgwExpansion {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Initiates the expansion procedure, allowing configuration of the expansion grid nodes
    .DESCRIPTION
    Initiates the expansion procedure, allowing configuration of the expansion grid nodes
#>

function Global:Start-SgwExpansion {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/start"
        $Method = "POST"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Executes the expansion procedure, adding configured grid nodes to the grid
    .DESCRIPTION
    Executes the expansion procedure, adding configured grid nodes to the grid
#>

function Global:Invoke-SgwExpansion {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Passphrase.")][String]$Passphrase,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/expand"
        $Method = "POST"

        $Body = ConvertTo-Json -InputObject @{ passphrase = $Passphrase }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## expansion-nodes ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieves the list of grid nodes available for expansion
    .DESCRIPTION
    Retrieves the list of grid nodes available for expansion
#>

function Global:Get-SgwExpansionNodes {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/nodes"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Removes a grid node from all procedures; the grid node may be added back in by rebooting it
    .DESCRIPTION
    Removes a grid node from all procedures; the grid node may be added back in by rebooting it
#>

function Global:Remove-SgwExpansionNode {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale node to remove from expansion.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/nodes/$id"
        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves a grid node eligbible for expansion
    .DESCRIPTION
    Retrieves a grid node eligbible for expansion
#>

function Global:Get-SgwExpansionNode {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID node eligible for expansion.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/nodes/$id"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

# TODO: Implement
<#
    .SYNOPSIS
    Configures a grid node expansion
    .DESCRIPTION
    Configures a grid node expansion
#>

function Global:New-SgwExpansionNode {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/start"
        $Method = "POST"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Resets a grid node's configuration and returns it back to pending state
    .DESCRIPTION
    Resets a grid node's configuration and returns it back to pending state
#>

function Global:Reset-SgwExpansionNode {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID node eligible for expansion.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/node/$id"
        $Method = "POST"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## expansion-sites ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieves the list of existing and new sites (empty until expansion is started)
    .DESCRIPTION
    Retrieves the list of existing and new sites (empty until expansion is started)
#>

function Global:Get-SgwExpansionSites {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/sites"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Adds a new site
    .DESCRIPTION
    Adds a new site
#>

function Global:New-SgwExpansionSite {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "Name of new site.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Name,
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/site"
        $Method = "POST"

        $Body = ConvertTo-Json -InputObject @{ name = $Name }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Delete a site
    .DESCRIPTION
    Delete a site
#>

function Global:Remove-SgwExpansionSite {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale site to remove from expansion.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/sites/$id"
        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve a site
    .DESCRIPTION
    Retrieve a site
#>

function Global:Get-SgwExpansionSite {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID site.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/sites/$id"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Updates the details of a site
    .DESCRIPTION
    Updates the details of a site
#>

function Global:Update-SgwExpansionSite {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID site to be updated.")][String]$ID,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "New ID for the StorageGRID site.")][String]$NewID,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "New name for the StorageGRID site.")][String]$Name,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/expansion/site/$id"
        $Method = "PUT"

        $Body = @{ }
        if ($Name) {
            $Body.name = $Name
        }
        if ($NewID) {
            $Body.id = $NewID
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## grid-networks ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Lists the current Grid Networks
    .DESCRIPTION
    Lists the current Grid Networks
#>

function Global:Get-SgwGridNetworks {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/grid-networks"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Change the Grid Network list
    .DESCRIPTION
    Change the Grid Network list
#>

function Global:Update-SgwGridNetworks {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "List of grid network Subnets in CIDR format (e.g. 10.0.0.0/16).")][String[]]$Subnets,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "StorageGRID Passphrase.")][String]$Passphrase
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/grid-networks/update"
        $Method = "POST"

        $Body = @{ }
        $Body.passphrase = $Passphrase
        $Body.subnets = $Subnets
        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## groups ##

<#
    .SYNOPSIS
    List Groups
    .DESCRIPTION
    List Groups
#>

function Global:Get-SgwGroups {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/groups"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/groups"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

# TODO: Implement adding tenant groups and rename cmdlets
<#
    .SYNOPSIS
    Creates a new Grid Administrator Group
    .DESCRIPTION
    Creates a new Grid Administrator Group
#>

function Global:New-SgwGroup {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Display name of the group.")][String]$displayName,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Display name of the group.")][String]$uniqueName,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Display name of the group.")][Boolean]$alarmAcknowledgment,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "Display name of the group.")][Boolean]$otherGridConfiguration,
        [parameter(
                Mandatory = $False,
                Position = 5,
                HelpMessage = "Display name of the group.")][Boolean]$gridTopologyPageConfiguration,
        [parameter(
                Mandatory = $False,
                Position = 6,
                HelpMessage = "Display name of the group.")][Boolean]$tenantAccounts,
        [parameter(
                Mandatory = $False,
                Position = 7,
                HelpMessage = "Display name of the group.")][Boolean]$changeTenantRootPassword,
        [parameter(
                Mandatory = $False,
                Position = 8,
                HelpMessage = "Display name of the group.")][Boolean]$maintenance,
        [parameter(
                Mandatory = $False,
                Position = 9,
                HelpMessage = "Display name of the group.")][Boolean]$activateFeatures,
        [parameter(
                Mandatory = $False,
                Position = 10,
                HelpMessage = "Display name of the group.")][Boolean]$rootAccess,
        [parameter(
                Mandatory = $False,
                Position = 11,
                HelpMessage = "IAM Policy.")][PSCustomObject]$IamPolicy,
        [parameter(
                Mandatory = $False,
                Position = 11,
                HelpMessage = "Use IAM Policy for Full S3 Access.")][Switch]$S3FullAccess,
        [parameter(
                Mandatory = $False,
                Position = 11,
                HelpMessage = "Use IAM Policy for Read Only S3 Access.")][Switch]$S3ReadOnlyAccess,
        [parameter(
                Mandatory = $False,
                Position = 12,
                HelpMessage = "Swift Roles.")][String[]]$SwiftRoles
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/groups"
        $Method = "POST"

        $Body = @{ }
        $Body.displayName = $displayName
        $Body.uniqueName = $uniqueName
        if ($alarmAcknowledgment -or $otherGridConfiguration -or $gridTopologyPageConfiguration -or $tenantAccounts -or $changeTenantRootPassword -or $maintenance -or $activateFeatures -or $rootAccess -or $IamPolicy -or $SwiftRoles) {
            $Body.policies = @{ }
            $Body.policies.management = @{ }
            if ($alarmAcknowledgment) {
                $Body.policies.management.alarmAcknowledgment = $alarmAcknowledgment
            }
            if ($otherGridConfiguration) {
                $Body.policies.management.otherGridConfiguration = $otherGridConfiguration
            }
            if ($tenantAccounts) {
                $Body.policies.management.tenantAccounts = $tenantAccounts
            }
            if ($changeTenantRootPassword) {
                $Body.policies.management.changeTenantRootPassword = $changeTenantRootPassword
            }
            if ($maintenance) {
                $Body.policies.management.maintenance = $maintenance
            }
            if ($activateFeatures) {
                $Body.policies.management.activateFeatures = $activateFeatures
            }
            if ($rootAccess) {
                $Body.policies.management.rootAccess = $rootAccess
            }
            if ($Capabilities -match "s3") {
                if (!$IamPolicy -and !($S3FullAccess.IsPresent -or $S3ReadOnlyAccess)) {
                    Write-Warning "S3 capability specified, but no IAM Policy provided. Users of this group will not be able to execute any S3 commands on buckets or objects."
                }
                elseif ($S3FullAccess.IsPresent) {
                    $Body.policies.s3 = New-IamPolicy -Effect "Allow" -Resource "urn:sgws:s3:::*" -Action "s3:*"
                }
                elseif ($S3ReadOnlyAccess.IsPresent) {
                    $Body.policies.s3 = New-IamPolicy -Effect "Allow" -Resource "urn:sgws:s3:::*" -Action "s3:ListBucket", "s3:ListBucketVersions", "s3:ListAllMyBuckets", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:GetAccelerateConfiguration", "s3:GetAnalyticsConfiguration", "s3:GetBucketAcl", "s3:GetBucketCORS", "s3:GetBucketLocation", "s3:GetBucketLogging", "s3:GetBucketNotification", "s3:GetBucketPolicy", "s3:GetBucketRequestPayment", "s3:GetBucketTagging", "s3:GetBucketVersioning", "s3:GetBucketWebsite", "s3:GetInventoryConfiguration", "s3:GetIpConfiguration", "s3:GetLifecycleConfiguration", "s3:GetMetricsConfiguration", "s3:GetObject", "s3:GetObjectAcl", "s3:GetObjectTagging", "s3:GetObjectTorrent", "s3:GetObjectVersion", "s3:GetObjectVersionAcl", "s3:GetObjectVersionForReplication", "s3:GetObjectVersionTagging", "s3:GetObjectVersionTorrent", "s3:GetReplicationConfiguration"
                }
                else {
                    $Body.policies.s3 = $IamPolicy
                }
            }

            if ($Capabilities -match "swift") {
                if (!$SwiftRoles) {
                    Write-Warning "Swift capability specified, but no Swift roles specified."
                }
                else {
                    $Body.policies.swift = @{ }
                    $Body.policies.swift.roles = $SwiftRoles
                }
            }
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves a local Grid Administrator Group by unique name
    .DESCRIPTION
    Retrieves a local Grid Administrator Group by unique name
#>

function Global:Get-SgwGroupByShortName {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "Short name of the user to retrieve.")][String]$ShortName,
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/groups/group/$ShortName"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/groups/group/$ShortName"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves a federated Grid Administrator Group by unique name
    .DESCRIPTION
    Retrieves a federated Grid Administrator Group by unique name
#>

function Global:Get-SgwFederatedGroupByShortName {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "Short name of the user to retrieve.")][String]$ShortName,
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/groups/federated-group/$ShortName"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/groups/federated-group/$ShortName"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Deletes a single Group
    .DESCRIPTION
    Deletes a single Group
#>

function Global:Delete-SgwGroup {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Group to delete.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(
                Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/groups/$id"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/groups/$id"
        }

        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves a single Group
    .DESCRIPTION
    Retrieves a single Group
#>

function Global:Get-SgwGroup {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Group to retrieve.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(
                Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/groups/$id"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/groups/$id"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

# TODO: Implement updating tenant group and rename cmdlet
<#
    .SYNOPSIS
    Updates a single Grid Administrator Group
    .DESCRIPTION
    Updates a single Grid Administrator Group
#>

function Global:Update-SgwGroup {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of the group to be updated.")][String]$ID,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Display name of the group.")][String]$displayName,
        [parameter(
                Mandatory = $False,
                Position = 2,
                HelpMessage = "Display name of the group.")][Boolean]$alarmAcknowledgment,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Display name of the group.")][Boolean]$otherGridConfiguration,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "Display name of the group.")][Boolean]$gridTopologyPageConfiguration,
        [parameter(
                Mandatory = $False,
                Position = 5,
                HelpMessage = "Display name of the group.")][Boolean]$tenantAccounts,
        [parameter(
                Mandatory = $False,
                Position = 6,
                HelpMessage = "Display name of the group.")][Boolean]$changeTenantRootPassword,
        [parameter(
                Mandatory = $False,
                Position = 7,
                HelpMessage = "Display name of the group.")][Boolean]$maintenance,
        [parameter(
                Mandatory = $False,
                Position = 8,
                HelpMessage = "Display name of the group.")][Boolean]$activateFeatures,
        [parameter(
                Mandatory = $False,
                Position = 9,
                HelpMessage = "Display name of the group.")][Boolean]$rootAccess,
        [parameter(
                Mandatory = $False,
                Position = 10,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/groups"
        $Method = "POST"

        $Body = @{ }
        if ($displayName) {
            $Body.displayName = $displayName
        }
        if ($alarmAcknowledgment -or $otherGridConfiguration -or $gridTopologyPageConfiguration -or $tenantAccounts -or $changeTenantRootPassword -or $maintenance -or $activateFeatures -or $rootAccess) {
            $Body.policies = @{ }
            $Body.policies.management = @{ }
            if ($alarmAcknowledgment) {
                $Body.policies.management.alarmAcknowledgment = $alarmAcknowledgment
            }
            if ($otherGridConfiguration) {
                $Body.policies.management.otherGridConfiguration = $otherGridConfiguration
            }
            if ($tenantAccounts) {
                $Body.policies.management.tenantAccounts = $tenantAccounts
            }
            if ($changeTenantRootPassword) {
                $Body.policies.management.changeTenantRootPassword = $changeTenantRootPassword
            }
            if ($maintenance) {
                $Body.policies.management.maintenance = $maintenance
            }
            if ($activateFeatures) {
                $Body.policies.management.activateFeatures = $activateFeatures
            }
            if ($rootAccess) {
                $Body.policies.management.rootAccess = $rootAccess
            }
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

# TODO: Implement replacing tenant group and rename this cmdlet
<#
    .SYNOPSIS
    Replaces a single Grid Administrator Group
    .DESCRIPTION
    Replaces a single Grid Administrator Group
#>

function Global:Replace-SgwGroup {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of the group to be updated.")][String]$ID,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Display name of the group.")][String]$displayName,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Unique name.")][String]$uniqueName,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Unique name.")][String]$accountId,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Unique name.")][Boolean]$federated,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Unique name.")][String]$groupURN,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Display name of the group.")][Boolean]$alarmAcknowledgment,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Display name of the group.")][Boolean]$otherGridConfiguration,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "Display name of the group.")][Boolean]$gridTopologyPageConfiguration,
        [parameter(
                Mandatory = $False,
                Position = 5,
                HelpMessage = "Display name of the group.")][Boolean]$tenantAccounts,
        [parameter(
                Mandatory = $False,
                Position = 6,
                HelpMessage = "Display name of the group.")][Boolean]$changeTenantRootPassword,
        [parameter(
                Mandatory = $False,
                Position = 7,
                HelpMessage = "Display name of the group.")][Boolean]$maintenance,
        [parameter(
                Mandatory = $False,
                Position = 8,
                HelpMessage = "Display name of the group.")][Boolean]$activateFeatures,
        [parameter(
                Mandatory = $False,
                Position = 9,
                HelpMessage = "Display name of the group.")][Boolean]$rootAccess,
        [parameter(
                Mandatory = $False,
                Position = 10,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/groups/$id"
        $Method = "PUT"

        $Body = @{ }
        if ($displayName) {
            $Body.displayName = $displayName
        }
        if ($uniqueName) {
            $Body.uniqueName = $uniqueName
        }
        if ($accountId) {
            $Body.accountId = $accountId
        }
        if ($federated) {
            $Body.federated = $federated
        }
        if ($groupURN) {
            $Body.groupURN = $groupURN
        }
        if ($alarmAcknowledgment -or $otherGridConfiguration -or $gridTopologyPageConfiguration -or $tenantAccounts -or $changeTenantRootPassword -or $maintenance -or $activateFeatures -or $rootAccess) {
            $Body.policies = @{ }
            $Body.policies.management = @{ }
            if ($alarmAcknowledgment) {
                $Body.policies.management.alarmAcknowledgment = $alarmAcknowledgment
            }
            if ($otherGridConfiguration) {
                $Body.policies.management.otherGridConfiguration = $otherGridConfiguration
            }
            if ($tenantAccounts) {
                $Body.policies.management.tenantAccounts = $tenantAccounts
            }
            if ($changeTenantRootPassword) {
                $Body.policies.management.changeTenantRootPassword = $changeTenantRootPassword
            }
            if ($maintenance) {
                $Body.policies.management.maintenance = $maintenance
            }
            if ($activateFeatures) {
                $Body.policies.management.activateFeatures = $activateFeatures
            }
            if ($rootAccess) {
                $Body.policies.management.rootAccess = $rootAccess
            }
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve groups of a StorageGRID Webscale Account
    .DESCRIPTION
    Retrieve groups of a StorageGRID Webscale Account
#>

function Global:Get-SgwAccountGroups {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Account to get group information for.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$id,
        [parameter(
                Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.APIVersion -gt 1) {
            Throw "This Cmdlet is only supported with API Version 1"
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/accounts/$id/groups"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## health ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieve StorageGRID Health Status
    .DESCRIPTION
    Retrieve StorageGRID Health Status
#>

function Global:Get-SgwHealth {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + '/grid/health'
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve StorageGRID Topology with Health Status
    .DESCRIPTION
    Retrieve StorageGRID Topology with Health Status
#>

function Global:Get-SgwTopologyHealth {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "Topology depth level to provide (default=node).")][String][ValidateSet("grid", "site", "node", "component", "subcomponent")]$Depth = "node"
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/health/topology?depth=$depth"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## identity-source ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieve identity sources
    .DESCRIPTION
    Retrieve identity sources
#>

function Global:Get-SgwIdentitySources {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/identity-source"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/identity-source"
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve identity sources
    .DESCRIPTION
    Retrieve identity sources
#>

function Global:Update-SgwIdentitySources {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "Identity Source ID",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Id,
        [parameter(
                Mandatory = $False,
                Position = 1,
                HelpMessage = "Disable Identity Source ID")][Switch]$Disable,
        [parameter(
                Mandatory = $False,
                Position = 2,
                HelpMessage = "Identity Source Hostname")][String]$Hostname,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Identity Source Port")][Int]$Port,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "Identity Source Username and password")][PSCredential]$Credential,
        [parameter(
                Mandatory = $False,
                Position = 6,
                HelpMessage = "Identity Source Base Group DN")][String]$BaseGroupDN,
        [parameter(
                Mandatory = $False,
                Position = 7,
                HelpMessage = "Identity Source Base User DN")][String]$BaseUserDN,
        [parameter(
                Mandatory = $False,
                Position = 8,
                HelpMessage = "Identity Source LDAP Service Type")][String]$LdapServiceType,
        [parameter(
                Mandatory = $False,
                Position = 9,
                HelpMessage = "Identity Source Type")][String]$Type,
        [parameter(
                Mandatory = $False,
                Position = 10,
                HelpMessage = "Identity Source LDAP User ID Attribute")][String]$LDAPUserIDAttribute,
        [parameter(
                Mandatory = $False,
                Position = 11,
                HelpMessage = "Identity Source LDAP User UUID Attribute")][String]$LDAPUserUUIDAttribute,
        [parameter(
                Mandatory = $False,
                Position = 12,
                HelpMessage = "Identity Source LDAP Group ID Attribute")][String]$LDAPGroupIDAttribute,
        [parameter(
                Mandatory = $False,
                Position = 13,
                HelpMessage = "Identity Source Disable TLS")][Switch]$DisableTLS,
        [parameter(
                Mandatory = $False,
                Position = 14,
                HelpMessage = "Identity Source CA Certificate")][String]$CACertificate,
        [parameter(
                Mandatory = $False,
                Position = 15,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/identity-source"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/identity-source"
        }

        $Method = "PUT"

        $Username = $Credential.UserName -replace '([a-zA-Z0-9])\\([a-zA-Z0-9])', '$1\\\\$2'
        $Password = $Credential.GetNetworkCredential().Password

        $Body = @"
{
    "id": "$Id",
    "disable": $Disable,
    "hostname": "$Hostname",
    "port": $Port,
    "username": "$Username",
    "password": "$Password",
    "baseGroupDn": "$BaseGroupDN",
    "baseUserDn": "$BaseUserDN",
    "ldapServiceType": "$LDAPServiceType",
    "type": "$Type",
    "ldapUserIdAttribute": "$LDAPUserIDAttribute",
    "ldapUserUUIDAttribute": "$LDAPUserUUIDAttribute",
    "ldapGroupIdAttribute": "$LDAPGroupIDAttribute",
    "ldapGroupUUIDAttribute": "$LDAPGroupUUIDAttribute",
    "disableTls": $DisableTLS,
    "caCert": "$CACertificate\n"
}
"@


        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieve identity sources
    .DESCRIPTION
    Retrieve identity sources
#>

function Global:Sync-SgwIdentitySources {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + "/org/identity-source/synchronize"
        }
        else {
            $Uri = $Server.BaseURI + "/grid/identity-source/synchronize"
        }

        $Method = "POST"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body "" -SkipCertificateCheck:$Server.SkipCertificateCheck
            Write-Host "Successfully synchronized users and groups of identity sources"
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## ilm ##

# TODO: Implement missing cmdlets and check existing cmdlets

<#
    .SYNOPSIS
    Evaluates proposed ILM policy
    .DESCRIPTION
    Evaluates proposed ILM policy
#>

function Global:Invoke-SgwIlmEvaluate {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "The object API that the provided object was evaluated against.")][String][ValidateSet('cdmi', 's3', 'swift')]$API,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Protocol-specific object identifier (e.g. bucket/key/1).")][String]$ObjectID,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Switch indicating that ILM evaluation should occur immediately.")][Switch]$Now,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/ilm-evaluate"
        $Method = "POST"

        $Body = @{ }
        $Body.objectID = $ObjectID
        if ($API) {
            $Body.api = $API
        }
        if ($Now) {
            $Body.now = Get-Date -Format u
        }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Lists metadata available for creating an ILM rule
    .DESCRIPTION
    Lists metadata available for creating an ILM rule
#>

function Global:Get-SgwIlmMetadata {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "The object API that the provided object was evaluated against.")][String][ValidateSet('cdmi', 's3', 'swift')]$API,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/ilm-metadata?api=$api"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Lists ILM rules
    .DESCRIPTION
    Lists ILM rules
#>

function Global:Get-SgwIlmRules {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/ilm-rules"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## license ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Retrieves the grid license
    .DESCRIPTION
    Retrieves the grid license
#>

function Global:Get-SgwLicense {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/license"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Update the license
    .DESCRIPTION
    Update the license
#>

function Global:Update-SgwLicense {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale license.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$License,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Passphrase.")][String]$Passphrase,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/license/update"
        $Method = "POST"

        $Body = @{ }
        $Body.passphrase = $Passphrase
        $Body.license = $License

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## logs ##

# complete as of API 2.1

Set-Alias -Name Get-SgwLogStatus -Value Get-SgwLogs
<#
    .SYNOPSIS
    Retrieves the log collection procedure status
    .DESCRIPTION
    Retrieves the log collection procedure status
#>

function Global:Get-SgwLogs {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/logs"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Retrieves the log collection procedure status
    .DESCRIPTION
    Retrieves the log collection procedure status
#>

function Global:Start-SgwLogCollection {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Passphrase.")][String]$Passphrase,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "List of StorageGRID Nodes to collect logs for (Default: all nodes).")][String[]]$Nodes,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "First log timestamp at start of log collection (Default: last hour).")][DateTime]$RangeStart=(Get-Date).AddHours(-1),
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "Last log timestamp at end of log collection (Default: now).")][DateTime]$RangeEnd=(Get-Date)
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/logs/collect"
        $Method = "POST"

        $Topology = Get-SgwTopologyHealth -Server $Server
        $TopologyNodes = $Topology.children.children

        $NodeIds = @()

        foreach ($Node in $Nodes) {
            if (!$TopologyNodes.id.Contains($Node)) {
                if ($TopologyNodes.name.Contains($Node)) {
                    $NodeIds += $TopologyNodes | Where-Object { $_.name -eq $Node } | Select-Object -ExpandProperty id
                }
                else {
                    Throw "Node $Node not found"
                }
            }
            else {
                NodeIds += $Node
            }
        }

        if (!$NodeIds) {
            $NodeIds = $TopologyNodes.id
        }

        $Body = @{}
        $Body.passphrase = $Passphrase
        $Body.nodes = $NodeIds
        $Body.rangeStart = Get-Date -Format o $RangeStart.ToUniversalTime()
        $Body.rangeEnd = Get-Date -Format o $RangeEnd.ToUniversalTime()

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Body $Body -ContentType "application/json" -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Deletes the previous log collection archive
    .DESCRIPTION
    Deletes the previous log collection archive
#>

function Global:Remove-SgwLogCollection {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/logs/collection"
        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Download log collection archive after procedure completes
    .DESCRIPTION
    Download log collection archive after procedure completes
#>

function Global:Get-SgwLogCollection {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Path to store log collection in")][System.IO.DirectoryInfo]$Path
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/logs/collection"
        $Method = "GET"

        $LogStatus = Get-SgwLogs

        if ($LogStatus.inProgress) {
            Throw "Log Collection still in progress"
        }

        if ($LogStatus.error) {
            Throw "Log Collection encountered error $($LogStatus.error)"
        }

        if (!(Test-Path $Path)) {
            Throw "Path $Path does not exist!"
        }
        else {
            $OutFile = Join-Path -Path $Path -ChildPath $LogStatus.fileName
            Write-Host "Saving file to $OutFile"
        }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck -OutFile $OutFile
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## metrics ##

<#
    .SYNOPSIS
    Retrieves the metric names
    .DESCRIPTION
    Retrieves the metric names
#>

function Global:Get-SgwMetricNames {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/metric-names"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Performs an instant metric query at a single point in time
    .DESCRIPTION
    Performs an instant metric query at a single point in time
#>

function Global:Get-SgwMetricQuery {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Prometheus query string.")][String]$Query,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Query start, default current time (date-time).")][DateTime]$Time,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Timeout in seconds.")][Int]$Timeout = 120
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/metric-query"
        $Method = "GET"

        $Uri += "?query=$Query"

        if ($Time) {
            $Uri += "&time=$( Get-Date -Format o $Time.ToUniversalTime() )"
        }

        if ($Timeout) {
            $Uri += "&timeout=$( $Timeout )s"
        }


        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Metrics = $Response.Json.data.result | % { [PSCustomObject]@{ Metric = $_.metric.__name__; Instance = $_.metric.instance; Time = (ConvertFrom-UnixTimestamp -Unit Seconds -Timestamp $_.value[0]); Value = $_.value[1] } }

        Write-Output $Metrics
    }
}

# TODO: Implement metrics cmdlets

## ntp-servers ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Lists configured external NTP servers
    .DESCRIPTION
    Lists configured external NTP servers
#>

function Global:Get-SgwNtpServers {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/ntp-servers"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Change the external NTP servers used by the grid
    .DESCRIPTION
    Change the external NTP servers used by the grid
#>

function Global:Update-SgwNtpServers {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale license.")][String[]]$Servers,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Passphrase.")][String]$Passphrase,
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/ntp-servers/update"
        $Method = "POST"

        $Body = @{ }
        $Body.passphrase = $Passphrase
        $Body.servers = $Servers

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## objects ##


<#
    .SYNOPSIS
    Retrieves metadata for an object
    .DESCRIPTION
    Retrieves metadata for an object
#>

function Global:Get-SgwObjectMetadata {
    [CmdletBinding(DefaultParameterSetName="objectid")]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                ParameterSetName="objectid",
                Position = 1,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Object ID (e.g. S3 bucket/key or Swift container/object).")][String]$ObjectId,
        [parameter(Mandatory = $True,
                ParameterSetName="ContainerAndKey",
                Position = 1,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "S3 Bucket or Swift Container name.")][Alias("Bucket","BucketName","ContainerName")][String]$Container,
        [parameter(Mandatory = $True,
                ParameterSetName="ContainerAndKey",
                Position = 2,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "S3 Object Key or Swift Object Name.")][Alias("Key","Name")][String]$Object,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "Maximum number of segements to return.")][Int]$MaxSegments
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/object-metadata"
        $Method = "POST"

        $Body = @{}
        if ($Container) {
            $ObjectId = "$Container/$Object"
        }
        $Body.objectId = $ObjectId
        if ($MaxSegments) {
            $Body.maxSegments = $MaxSegments
        }

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Body $Body -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## recovery ##

# complete as of API 2.1

<#
    .SYNOPSIS
    Lists grid nodes not connected to the grid
    .DESCRIPTION
    Lists grid nodes not connected to the grid
#>

function Global:Get-SgwRecoveryAvailableNodes {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/recovery/available-nodes"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Resets the recovery procedure to the not-started state
    .DESCRIPTION
    Resets the recovery procedure to the not-started state
#>

function Global:Reset-SgwRecovery {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/recovery"
        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Gets the recovery status
    .DESCRIPTION
    Gets the recovery status
#>

function Global:Get-SgwRecovery {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/recovery"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Starts the recovery procedure, retrieves configuration file and installs software
    .DESCRIPTION
    Starts the recovery procedure, retrieves configuration file and installs software
#>

function Global:Start-SgwRecovery {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Passphrase.")][String]$Passphrase,
        [parameter(Mandatory = $True,
                Position = 2,
                HelpMessage = "StorageGRID Node OID to recover.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Oid,
        [parameter(Mandatory = $True,
                Position = 3,
                HelpMessage = "StorageGRID Node Name to recover.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Name,
        [parameter(Mandatory = $True,
                Position = 4,
                HelpMessage = "StorageGRID Node IP to recover.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$Ip,
        [parameter(Mandatory = $True,
                Position = 5,
                HelpMessage = "Node to replace failed node.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][PSCustomObject]$ReplacementNode
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/recovery"
        $Method = "POST"

        $Body = @{}
        $Body.id = $ReplacementNode.Id
        $Body.ip = $Ip
        $Body.name = $Name
        $Body.oid = $Oid
        $Body.passphrase = $Passphrase

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Body $Body -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## recovery-package ##

<#
    .SYNOPSIS
    Retrieves the log collection procedure status
    .DESCRIPTION
    Retrieves the log collection procedure status
#>

function Global:Get-SgwRecoveryPackage {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "StorageGRID Webscale Passphrase.")][String]$Passphrase,
        [parameter(Mandatory = $True,
                Position = 1,
                HelpMessage = "Path to store log collection in")][System.IO.DirectoryInfo]$Path
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/recovery-package"
        $Method = "POST"

        if (!(Test-Path $Path)) {
            Throw "Path $Path does not exist!"
        }

        $Body = @{}
        $Body.passphrase = $Passphrase

        $Body = ConvertTo-Json -InputObject $Body

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Body $Body -Headers $Server.Headers -OutFile $Path -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }
    }
}

## regions ##

<#
    .SYNOPSIS
    Lists configured regions
    .DESCRIPTION
    Lists configured regions
#>

function Global:Get-SgwRegions {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/regions"
        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

<#
    .SYNOPSIS
    Change the regions used by the grid
    .DESCRIPTION
    Change the regions used by the grid
#>

function Global:Update-SgwRegions {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "Regions.")][String[]]$Regions
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if ($Server.AccountId) {
            Throw "Operation not supported when connected as tenant. Use Connect-SgwServer without the AccountId parameter to connect as grid administrator and then rerun this command."
        }
    }

    Process {
        $Uri = $Server.BaseURI + "/grid/regions"
        $Method = "PUT"

        if (!$Regions.Contains("us-east-1")) {
            # us-east-1 must always be included in list of regions
            $Regions += "us-east-1"
        }

        $Body = ConvertTo-Json -InputObject $Regions

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Body $Body -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

## server-certificate ##

# TODO: Implement server-certificate Cmdlets

## users ##

<#
    .SYNOPSIS
    Retrieve all StorageGRID Users
    .DESCRIPTION
    Retrieve all StorageGRID Users
#>

function Global:Get-SgwUsers {
    [CmdletBinding()]

    PARAM (
        [parameter(Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(Mandatory = $False,
                Position = 1,
                HelpMessage = "User type (default local).")][ValidateSet("local", "federated")][String]$Type = "local",
        [parameter(Mandatory = $False,
                Position = 2,
                HelpMessage = "Maximum number of results.")][Int]$Limit = 0,
        [parameter(Mandatory = $False,
                Position = 3,
                HelpMessage = "Pagination offset (value is Account's id).")][String]$Marker,
        [parameter(Mandatory = $False,
                Position = 4,
                HelpMessage = "if set, the marker element is also returned.")][Switch]$IncludeMarker,
        [parameter(Mandatory = $False,
                Position = 5,
                HelpMessage = "pagination order (desc requires marker).")][ValidateSet("asc", "desc")][String]$Order = "asc"
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
    }

    Process {
        if ($Server.AccountId) {
            $Uri = $Server.BaseURI + '/org/users'
        }
        else {
            $Uri = $Server.BaseURI + '/grid/users'
        }

        $Method = "GET"

        if ($Limit -eq 0) {
            $Query = "?limit=25"
        }
        else {
            $Query = "?limit=$Limit"
        }
        if ($Type) {
            $Query += "&type=$Type"
        }
        if ($Marker) {
            $Query += "&marker=$Marker"
        }
        if ($IncludeMarker) {
            $Query += "&includeMarker=true"
        }
        if ($Order) {
            $Query += "&order=$Order"
        }

        $Uri += $Query

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $( $responseBody.message )"
            return
        }

        $Response.Json.data | Add-Member -MemberType AliasProperty -Name userId -Value id

        Write-Output $Response.Json.data

        if ($Limit -eq 0 -and $Response.Json.data.count -eq 25) {
            Get-SgwAccounts -Server $Server -Limit $Limit -Marker ($Response.Json.data | select -last 1 -ExpandProperty id) -IncludeMarker:$IncludeMarker -Order $Order
        }
    }
}

# TODO: Implement users Cmdlets

## s3 ##

Set-Alias -Name Get-SgwAccountS3AccessKeys -Value Get-SgwS3AccessKeys
<#
    .SYNOPSIS
    Retrieve StorageGRID Webscale Account S3 Access Keys
    .DESCRIPTION
    Retrieve StorageGRID Webscale Account S3 Access Keys
#>

function Global:Get-SgwS3AccessKeys {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                ParameterSetName = "account",
                HelpMessage = "ID of a StorageGRID Webscale Account to get S3 Access Keys for.",
                ValueFromPipelineByPropertyName = $True)][String]$AccountId,
        [parameter(
                Mandatory = $False,
                Position = 1,
                ParameterSetName = "user",
                HelpMessage = "ID of a StorageGRID Webscale User.",
                ValueFromPipelineByPropertyName = $True)][Alias("userUUID")][String]$UserId,
        [parameter(
                Mandatory = $False,
                Position = 2,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if (!$Server.AccountId -and !$Server.SupportedApiVersions.Contains(1)) {
            Throw "This cmdlet requires API Version 1 support if connection to server was not made with a tenant account id. Either use Connect-SgwServer with the AccountId parameter or enable API version 1 with Update-SgwConfigManagement -MinApiVersion 1"
        }
    }

    Process {
        if ($Server.AccountId) {
            if ($UserId) {
                $Uri = $Server.BaseURI + "/org/users/$UserId/s3-access-keys"
            }
            else {
                $Uri = $Server.BaseURI + "/org/users/current-user/s3-access-keys"
            }
        }
        else {
            if ($AccountId) {
                $Uri = $Server.BaseURI + "/grid/accounts/$AccountId/s3-access-keys"
            }
            else {
                Throw "Account ID required. Rerun command with AccountId parameter"
            }
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }
        Write-Output $Response.Json.data
    }
}

Set-Alias -Name Get-SgwAccountS3AccessKey -Value Get-SgwS3AccessKey
<#
    .SYNOPSIS
    Retrieve a StorageGRID Webscale Account S3 Access Key
    .DESCRIPTION
    Retrieve a StorageGRID Webscale Account S3 Access Key
#>

function Global:Get-SgwS3AccessKey {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "ID of a StorageGRID Webscale Account to get S3 Access Keys for",
                ParameterSetName = "account",
                ValueFromPipelineByPropertyName = $True)][String]$AccountId,
        [parameter(
                Mandatory = $False,
                Position = 1,
                HelpMessage = "ID of a StorageGRID Webscale User.",
                ParameterSetName = "user",
                ValueFromPipelineByPropertyName = $True)][Alias("userUUID")][String]$UserId,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Access Key to retrieve.",
                ValueFromPipelineByPropertyName = $True)][Alias("id")][String]$AccessKey,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if (!$Server.AccountId -and !$Server.SupportedApiVersions.Contains(1)) {
            Throw "This cmdlet requires API Version 1 support if connection to server was not made with a tenant account id. Either use Connect-SgwServer with the AccountId parameter or enable API version 1 with Update-SgwConfigManagement -MinApiVersion 1"
        }
    }

    Process {
        if ($Server.AccountId) {
            if ($UserId) {
                $Uri = $Server.BaseURI + "/org/users/$UserId/s3-access-keys/$AccessKey"
            }
            else {
                $Uri = $Server.BaseURI + "/org/users/current-user/s3-access-keys/$AccessKey"
            }
        }
        else {
            if ($AccountId) {
                $Uri = $Server.BaseURI + "/grid/accounts/$AccountId/s3-access-keys/$AccessKey"
            }
            else {
                Throw "Account ID required. Rerun command with id parameter"
            }
        }

        $Method = "GET"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        Write-Output $Response.Json.data
    }
}

Set-Alias -Name New-SgwAccountS3AccessKey -Value New-SgwS3AccessKey
<#
    .SYNOPSIS
    Create a new StorageGRID Webscale Account S3 Access Key
    .DESCRIPTION
    Create a new StorageGRID Webscale Account S3 Access Key
#>

function Global:New-SgwS3AccessKey {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(
                Mandatory = $False,
                Position = 1,
                ParameterSetName = "account",
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Id of the StorageGRID Webscale Account to create new S3 Access Key for.")][String]$AccountId,
        [parameter(
                Mandatory = $False,
                Position = 2,
                ParameterSetName = "user",
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "ID of a StorageGRID Webscale User.")][Alias("userUUID")][String]$UserId,
        [parameter(
                Mandatory = $False,
                Position = 3,
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Expiration date of the S3 Access Key.")][DateTime]$Expires
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if (!$Server.AccountId -and !$Server.SupportedApiVersions.Contains(1)) {
            Throw "This cmdlet requires API Version 1 support if connection to server was not made with a tenant account id. Either use Connect-SgwServer with the AccountId parameter or enable API version 1 with Update-SgwConfigManagement -MinApiVersion 1"
        }
        if ($Expires) {
            $ExpirationDate = Get-Date -Format o $Expires.ToUniversalTime()
        }
    }

    Process {
        if ($Server.AccountId) {
            $AccountId = $Server.AccountId
            if ($UserId) {
                $Uri = $Server.BaseURI + "/org/users/$UserId/s3-access-keys"
            }
            else {
                $Uri = $Server.BaseURI + "/org/users/current-user/s3-access-keys"
            }
        }
        else {
            if ($AccountId) {
                $Uri = $Server.BaseURI + "/grid/accounts/$AccountId/s3-access-keys"
            }
            else {
                Throw "Account ID required. Rerun command with id parameter"
            }
        }

        $Method = "POST"

        $Body = "{}"
        if ($Expires) {
            $Body = ConvertTo-Json -InputObject @{ "expires" = "$ExpirationDate" }
        }

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -Body $Body -ContentType "application/json" -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $AccessKey = $Response.Json.data

        if (!$AccessKey) {
            Throw "Server did not return access key!"
        }

        Write-Verbose "Access Key response: $AccessKey"

        if (!$Server.AccessKeyStore[$AccountId]) {
            $Server.AccessKeyStore[$AccountId] = New-Object System.Collections.ArrayList
        }
        $Server.AccessKeyStore[$AccountId].Add($AccessKey)

        Write-Output $AccessKey
    }
}

Set-Alias -Name Remove-SgwAccountS3AccessKey -Value Remove-SgwS3AccessKey
<#
    .SYNOPSIS
    Delete a StorageGRID Webscale Account S3 Access Key
    .DESCRIPTION
    Delete a StorageGRID Webscale Account S3 Access Key
#>

function Global:Remove-SgwS3AccessKey {
    [CmdletBinding()]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "Id of the StorageGRID Webscale Account to delete S3 Access Key for.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][String]$AccountId,
        [parameter(
                Mandatory = $False,
                Position = 1,
                HelpMessage = "ID of a StorageGRID Webscale User.",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("userUUID")][String]$UserId,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "S3 Access Key ID to be deleted,",
                ValueFromPipeline = $True,
                ValueFromPipelineByPropertyName = $True)][Alias("id")][String]$AccessKey,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }
        if (!$Server.AccountId -and !$Server.SupportedApiVersions.Contains(1)) {
            Throw "This cmdlet requires API Version 1 support if connection to server was not made with a tenant account id. Either use Connect-SgwServer with the AccountId parameter or enable API version 1 with Update-SgwConfigManagement -MinApiVersion 1"
        }
    }

    Process {
        if ($Server.AccountId) {
            if ($UserId) {
                $Uri = $Server.BaseURI + "/org/users/$UserId/s3-access-keys/$AccessKey"
            }
            else {
                $Uri = $Server.BaseURI + "/org/users/current-user/s3-access-keys/$AccessKey"
            }
        }
        else {
            if ($AccountId) {
                $Uri = $Server.BaseURI + "/grid/accounts/$AccountId/s3-access-keys/$AccessKey"
            }
            else {
                Throw "Account ID required. Rerun command with AccountId parameter"
            }
        }

        $Method = "DELETE"

        try {
            $Response = Invoke-SgwRequest -WebSession $Server.Session -Method $Method -Uri $Uri -Headers $Server.Headers -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        if ($Server.AccessKeyStore[$AccountId].id -match $AccessKey -or $Server.AccessKeyStore[$AccountId].accessKey -match $AccessKey) {
            $Server.AccessKeyStore[$AccountId].Remove(($Server.AccessKeyStore[$AccountId] | Where-Object { $_.id -match $AccessKey -or $_.accessKey -match $AccessKey } | Select-Object -First 1))
        }
    }
}

### reporting ###

<#
    .SYNOPSIS
    Get StorageGRID Report
    .DESCRIPTION
    Get StorageGRID Report
#>

function Global:Get-SgwReport {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $False,
                Position = 0,
                HelpMessage = "StorageGRID Webscale Management Server object. If not specified, global CurrentSgwServer object will be used.")][PSCustomObject]$Server,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Attribut to report")][String][ValidateSet("Archive Nodes Installed (XANI)", "Archive Nodes Readable (XANR)", "Archive Nodes Writable (XANW)", "Awaiting - All (XQUZ)", "Awaiting - Client (XCQZ)", "Awaiting - Evaluation Rate (XEVT)", "CDMI - Ingested Bytes (XCRX) [Bytes]", "CDMI - Retrieved Bytes (XCTX) [Bytes]", "CDMI Ingest - Rate (XCIR) [MB/s]", "CDMI Operations - Failed (XCFA)", "CDMI Operations - Rate (XCRA) [Objects/s]", "CDMI Operations - Successful (XCSU)", "CDMI Retrieval - Rate (XCRR) [MB/s]", "Current ILM Activity (IQSZ)", "Installed Storage Capacity (XISC) [Bytes]", "Percentage Storage Capacity Used (PSCU)", "Percentage Usable Storage Capacity (PSCA)", "S3 - Ingested Bytes (XSRX) [Bytes]", "S3 - Retrieved Bytes (XSTX) [Bytes]", "S3 Ingest - Rate (XSIR) [MB/s]", "S3 Operations - Failed (XSFA)", "S3 Operations - Rate (XSRA) [Objects/s]", "S3 Operations - Successful (XSSU)", "S3 Operations - Unauthorized (XSUA)", "S3 Retrieval - Rate (XSRR) [MB/s]", "Scan Period - Estimated (XSCM) [us]", "Scan Rate (XSCT) [Objects/s]", "Storage Nodes Installed (XSNI)", "Storage Nodes Readable (XSNR)", "Storage Nodes Writable (XSNW)", "Swift - Ingested Bytes (XWRX) [Bytes]", "Swift - Retrieved Bytes (XWTX) [Bytes]", "Swift Ingest - Rate (XWIR) [MB/s]", "Swift Operations - Failed (XWFA)", "Swift Operations - Rate (XWRA) [Objects/s]", "Swift Operations - Successful (XWSU)", "Swift Operations - Unauthorized (XWUA)", "Swift Retrieval - Rate (XWRR) [MB/s]", "Total EC Objects (XECT)", "Total EC Reads - Failed (XERF)", "Total EC Reads - Successful (XERC)", "Total EC Writes - Failed (XEWF)", "Total EC Writes - Successful (XEWC)", "Total Objects Archived (XANO)", "Total Objects Deleted (XANP)", "Total Size of Archived Objects (XSAO)", "Total Size of Deleted Objects (XSAP)", "Usable Storage Capacity (XASC) [Bytes]", "Used Storage Capacity (XUSC) [Bytes]", "Used Storage Capacity for Data (XUSD) [Bytes]", "Used Storage Capacity for Metadata (XUDC) [Bytes]")]$Attribute,
        [parameter(
                Mandatory = $False,
                Position = 1,
                ParameterSetName = "oid",
                HelpMessage = "Topology OID to create report for")][String]$OID,
        [parameter(
                Mandatory = $False,
                Position = 1,
                ParameterSetName = "site",
                HelpMessage = "Site to create report for")][String]$Site,
        [parameter(
                Mandatory = $False,
                Position = 1,
                ParameterSetName = "node",
                HelpMessage = "Node to create report for")][String]$Node,
        [parameter(
                Mandatory = $False,
                Position = 2,
                HelpMessage = "Start Time (default: last hour)")][DateTime]$StartTime = (Get-Date).AddHours(-1),
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "End Time (default: current time)")][DateTime]$EndTime = (Get-Date)
    )

    Begin {
        if (!$Server) {
            $Server = $Global:CurrentSgwServer
        }
        if (!$Server) {
            Throw "No StorageGRID Webscale Management Server management server found. Please run Connect-SgwServer to continue."
        }

        if ($Server.ApiVersion -lt 2.1) {
            Write-Warning "This Cmdlet uses an internal, undocumented API to retrieve the reports. Consider upgrading to StorageGRID 11.0 and use Get-SgwMetricQuery instead."
        }
        else {
            Write-Warning "This Cmdlet uses an internal, undocumented, legacy API to retrieve the reports. Use Get-SgwMetricQuery instead."
        }
    }

    Process {
        $StartTimeString = $StartTime.ToUniversalTime() | Get-Date -UFormat "%Y%m%d%H%M%S"
        $EndTimeString = $EndTime.ToUniversalTime() | Get-Date -UFormat "%Y%m%d%H%M%S"

        $AttributeCode = $Attribute -replace ".*\((.+)\).*", '$1'

        if (!$OID) {
            $Topology = Get-SgwTopologyHealth -Server $Server
            if ($Node) {
                $OID = $Topology.children.children | Where-Object { $_.name -eq $node } | Select-Object -First 1 -ExpandProperty oid
            }
            elseif ($Site) {
                $OID = $Topology.children | Where-Object { $_.name -eq $site } | Select-Object -First 1 -ExpandProperty oid
            }
            else {
                $OID = $Topology.oid
            }
        }

        $Method = "GET"
        $Uri = "https://$( $Server.Name )/NMS/render/JSP/DoXML.jsp?requestType=RPTX&mode=PAGE&start=$StartTimeString&end=$EndTimeString&attr=$AttributeCode&attrIndex=1&oid=$OID&type=text"

        try {
            $Response = Invoke-SgwRequest -Method $Method -WebSession $Server.Session -Headers $Server.Headers -Uri $Uri -SkipCertificateCheck:$Server.SkipCertificateCheck
        }
        catch {
            $ResponseBody = ParseErrorForResponseBody $_
            Write-Error "$Method to $Uri failed with Exception $( $_.Exception.Message ) `n $responseBody"
        }

        $Body = ($Response -split "`n" | ? { $_ -match "<body" })
        Write-Verbose "Body: $Body"

        if ($Response -match "Aggregate Time") {
            $Report = $Body -replace "<body.*Aggregate Time.*Type<br>", "" -split "<br>" -replace "([^,]+),[^,]+,([^ ]+) ([^,]*),([^ ]+) ([^,]*),([^ ]+) ([^,]*),.+", '$1;$2;$4;$6' | ? { $_ }
            foreach ($Line in $Report) {
                $Time, $Average, $Minimum, $Maximum = $Line -split ';'
                $Average = $Average -replace ",", "" -replace " ", ""
                $Minimum = $Minimum -replace ",", "" -replace " ", ""
                $Maximum = $Maximum -replace ",", "" -replace " ", ""
                [PSCustomObject]@{ "Time Received" = [DateTime]$time; "Average $Attribute" = $Average; "Minimum $Attribute" = $Minimum; "Maximum $Attribute" = $Maximum }
            }
        }
        elseif ($Response -match "Time Received") {
            $Report = $Body -replace "<body.*Time Received.*Type<br>", "" -split "<br>" -replace "([^,]+),[^,]+,[^,]+,[^,]+,([^ ]+) ([^,]*),.+", '$1;$2' | ? { $_ }
            foreach ($Line in $Report) {
                $Time, $Value = $Line -split ';'
                $Value = $Value -replace ",", "" -replace " ", ""
                [PSCustomObject]@{ "Time Received" = [DateTime]$time; $Attribute = $value }
            }
        }
        else {
            Write-Error "Cannot parse report output"
        }
    }
}

### workflows ###

<#
    .SYNOPSIS
    Merge two StorageGRIDs
    .DESCRIPTION
    Merge two StorageGRIDs
#>

function Global:Merge-SgwGrids {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "Source StorageGRID Webscale Management Server object")][PSCustomObject]$SourceServer,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Destination StorageGRID Webscale Management Server object")][PSCustomObject]$DestinationServer,
        [parameter(
                Mandatory = $True,
                Position = 2,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Tenant root password (must be at least 8 characters).")][ValidateLength(8, 256)][String]$Password,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Do not execute request, just return request URI and Headers")][Switch]$DryRun
    )

    Process {
        Write-Information "Retrieving tenant accounts from source"
        $SourceAccounts = Get-SgwAccounts -Server $SourceServer
        Write-Information "Retrieving tenant accounts from destination"
        $DestinationAccounts = Get-SgwAccounts -Server $DestinationServer
        $DuplicateAccounts = $SourceAccounts  | Where-Object { $DestinationAccounts.Name -contains $_.Name }
        if ($DuplicateAccounts) {
            Write-Warning  "$( $DuplicateAccounts.Count ) duplicate accounts were found"
            if (!$DryRun) {
                $DuplicateAccountChoice = $Host.UI.PromptForChoice("Duplicate accounts",
                        "Continue with reusing existing accounts?",
                        @("&Yes", "&No"),
                        1)
                if ($DuplicateAccountChoice -eq 1) {
                    break
                }
            }
        }

        $SourceAccountsWithoutS3Capabilities = $SourceAccounts | Where-Object { !$_.capabilities.contains("s3") }
        $SourceAccountsWithS3Capabilities = $SourceAccounts | Where-Object { $_.capabilities.contains("s3") }
        $DestinationAccountsWithS3Capabilities = $DestinationAccounts | Where-Object { $_.capabilities.contains("s3") }

        if ($SourceAccountsWithoutS3Capabilities) {
            Write-Warning "$( $SourceAccountsWithoutS3Capabilities.Count ) accounts without S3 capability found which cannot be migrated"
            if (!$DryRun) {
                $NonS3AccountChoice = $Host.UI.PromptForChoice("Non-S3 accounts",
                        "Continue without transitioning non S3-Accounts?",
                        @("&Yes", "&No"),
                        1)
                if ($NonS3AccountChoice -eq 1) {
                    break
                }
            }
        }

        $SourceBuckets = $SourceAccounts | Get-SgwAccountUsage -Server $SourceServer | Select -ExpandProperty Buckets
        $DestinationBuckets = $SourceAccounts | Get-SgwAccountUsage -Server $SourceServer | Select -ExpandProperty Buckets

        $DuplicateBuckets = @()
        foreach ($SourceBucket in $SourceBuckets) {
            $DuplicateBuckets += $DestinationBuckets | Where-Object { $_.Name -ceq $SourceBucket.Name }
        }

        if ($DuplicateBuckets) {
            if (!$DryRun) {
                Throw "Source and destination contain the following duplicate buckets. Remove any duplicate buckets on source or destination before proceeding. Duplicate Buckets:`n$( $DuplicateBuckets.Name -join "`n" )"
            }
            else {
                Write-Warning "Source and destination contain the following duplicate buckets. Remove any duplicate buckets on source or destination before proceeding. Duplicate Buckets:`n$( $DuplicateBuckets.Name -join "`n" )"
            }
        }

        if (!$DryRun) {
            $ResetPasswordChoice = $Host.UI.PromptForChoice("Reset password",
                    "To configure replication on the source accounts, the root password needs to be reset to the provided password. Continue?",
                    @("&Yes", "&No"),
                    1)
            if ($ResetPasswordChoice -eq 1) {
                break
            }
        }

        $SourceAccountsWithS3Capabilities | Copy-SgwAccount -SourceServer $SourceServer -DestinationServer $DestinationServer -DryRun:$DryRun
    }
}

<#
    .SYNOPSIS
    Merge two StorageGRIDs
    .DESCRIPTION
    Merge two StorageGRIDs
#>

function Global:Copy-SgwAccount {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "Source StorageGRID Webscale Management Server object")][PSCustomObject]$SourceServer,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Destination StorageGRID Webscale Management Server object")][PSCustomObject]$DestinationServer,
        [parameter(
                Mandatory = $True,
                Position = 2,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Source StorageGRID Webscale Management Account")][PSCustomObject]$AccountId,
        [parameter(
                Mandatory = $True,
                Position = 3,
                ValueFromPipelineByPropertyName = $True,
                HelpMessage = "Tenant root password (must be at least 8 characters).")][ValidateLength(8, 256)][String]$Password,
        [parameter(
                Mandatory = $False,
                Position = 4,
                HelpMessage = "Do not execute request, just return request URI and Headers")][Switch]$DryRun
    )

    Process {
        $AccountCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "root", ($Password | ConvertTo-SecureString -AsPlainText -Force)

        $SourceAccount = Get-SgwAccount -Server $SourceServer -id $AccountId

        Write-Information "Resetting root password of account $( $SourceAccount.Name ) on source server"
        if (!$DryRun) {
            $SourceAccount | Update-SgwPassword -Server $SourceServer -id $AccountId -NewPassword
        }

        Write-Information "Logging in as root to account $( $SourceAccount.Name ) on source server"
        if (!$DryRun) {
            $SourceServer = $SourceServer | Connect-SgwServer -Credential $AccountCredential -AccountId $AccountId -Transient
        }

        Write-Information "Retrieving all buckets from source account"
        if (!$DryRun) {
            $SourceBuckets = Get-S3Buckets -Server $SourceServer -AccountId $AccountId
        }

        Write-Information "Creating account $( $SourceAccount.Name ) on destination server"
        if (!$DryRun) {
            $DestinationAccount = $SourceAccount | New-SgwAccount -Server $DestinationServer -Password $Password
        }

        Write-Information "Logging in as root to account $( $SourceAccount.Name ) on destination server"
        if (!$DryRun) {
            $DestinationServer = $DestinationServer | Connect-SgwServer -Credential $AccountCredential -Transient
        }

        Write-Information "Creating local group CopyGroup in account $( $SourceAccount.Name ) on destination server"
        if (!$DryRun) {
            New-SgwGroup -Server $DestinationServer -DisplayName "Temporary Group for Data Replication" -UniqueName "DataReplication" -rootAcces $true -S3FullAccess
        }

        Write-Information "Generating S3 Access Key on destination for Bucket replication"
        if (!$DryRun) {
            $DestinationAccessKey = New-SgwS3AccessKey -Server $DestinationServer
        }

        if (!$DryRun) {
            foreach ($SourceBucket in $SourceBuckets) {
                Copy-S3Bucket -SourceServer $SourceServer -DestinationServer $DestinationServer -Bucket $SourceBucket
            }
        }
    }
}

<#
    .SYNOPSIS
    Copy an S3 Bucket including all properties and data
    .DESCRIPTION
    Copy an S3 Bucket including all properties and data
#>

function Global:Copy-SgwBucket {
    [CmdletBinding(DefaultParameterSetName = "none")]

    PARAM (
        [parameter(
                Mandatory = $True,
                Position = 0,
                HelpMessage = "Source StorageGRID Webscale Management Server object")][PSCustomObject]$SourceServer,
        [parameter(
                Mandatory = $True,
                Position = 1,
                HelpMessage = "Destination StorageGRID Webscale Management Server object")][PSCustomObject]$DestinationServer,
        [parameter(
                Mandatory = $True,
                Position = 2,
                HelpMessage = "Bucket Name")][PSCustomObject]$Bucket,
        [parameter(
                Mandatory = $False,
                Position = 3,
                HelpMessage = "Do not execute request, just return request URI and Headers")][Switch]$DryRun
    )

    Begin {
        if (!$SourceServer.AccountId) {
            Throw "Source server is not connected to account"
        }
        if (!$DestinationServer.AccountId) {
            Throw "Destination server is not connected to account"
        }
    }

    Process {
        Write-Information "Creating bucket $( $Bucket.Name ) on destination"
        if (!$DryRun) {
            New-S3Bucket -Server $DestinationServer -Name $Bucket.Name -Region $Bucket.Region
            # TODO: Copy other bucket properties such as ACLs from source bucket to destination bucket!
        }

        Write-Information "Adding endpoint configuration for bucket $( $Bucket.Name ) on source"
        if (!$DryRun) {
            New-SgwEndpoint -Server $SourceServer -DisplayName $Bucket.Name -EndpointUri $DestinationServerS3EndpointUrl -EndpointUrn "urn:sgws:s3:::$( $Bucket.Name )" -SkipCertificateCheck $DestinationServer.SkipCertificateCheck -AccessKey $AccessKey.AccessKey -SecretAccessKey $AccessKey.SecretAccessKey
        }

        Write-Information "Enable Bucket replication for bucket $( $Bucket.Name ) on source"
        if (!$DryRun) {
            # TODO: Replace with S3 Cmdlet
            Add-SgwBucketReplicationRule -Server $SourceServer -Id $Bucket.Name -Bucket $Bucket.Name -DestinationBucket $Bucket.Name -Status Enabled
            $ReplicationStartDate = Get-Date
        }

        Write-Information "Copy all older objects on themselve to update their modification time and trigger replication"

        if (!$DryRun) {
            Get-S3Objects -Server $SourceServer -Bucket $Bucket.Name | ForEach-Object {
                if ($_.LastModified -lt $ReplicationStartDate.ToUniversalTime()) {
                    $Metadata = $_ | Get-S3ObjectMetadata | Select -ExpandProperty CustomMetadata
                    Copy-S3Object -Server $SourceServer -Region $_.Region -Bucket $_.Bucket $_.Key -SourceBucket $_.Bucket -SourceKey $_.SourceKey -MetadataDirective REPLACE -Metadata $Metadata
                }
            }
        }
    }
}
# SIG # Begin signature block
# MIIVAAYJKoZIhvcNAQcCoIIU8TCCFO0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU7qSxwdS1iSnMqLWbXaH8SPtO
# 9D6ggg/vMIIEmTCCA4GgAwIBAgIPFojwOSVeY45pFDkH5jMLMA0GCSqGSIb3DQEB
# BQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQg
# TGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNV
# BAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJG
# aXJzdC1PYmplY3QwHhcNMTUxMjMxMDAwMDAwWhcNMTkwNzA5MTg0MDM2WjCBhDEL
# MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
# BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKjAoBgNVBAMT
# IUNPTU9ETyBTSEEtMSBUaW1lIFN0YW1waW5nIFNpZ25lcjCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBAOnpPd/XNwjJHjiyUlNCbSLxscQGBGue/YJ0UEN9
# xqC7H075AnEmse9D2IOMSPznD5d6muuc3qajDjscRBh1jnilF2n+SRik4rtcTv6O
# KlR6UPDV9syR55l51955lNeWM/4Og74iv2MWLKPdKBuvPavql9LxvwQQ5z1IRf0f
# aGXBf1mZacAiMQxibqdcZQEhsGPEIhgn7ub80gA9Ry6ouIZWXQTcExclbhzfRA8V
# zbfbpVd2Qm8AaIKZ0uPB3vCLlFdM7AiQIiHOIiuYDELmQpOUmJPv/QbZP7xbm1Q8
# ILHuatZHesWrgOkwmt7xpD9VTQoJNIp1KdJprZcPUL/4ygkCAwEAAaOB9DCB8TAf
# BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUjmstM2v0
# M6eTsxOapeAK9xI1aogwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwFgYD
# VR0lAQH/BAwwCgYIKwYBBQUHAwgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny
# bC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDA1BggrBgEF
# BQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20w
# DQYJKoZIhvcNAQEFBQADggEBALozJEBAjHzbWJ+zYJiy9cAx/usfblD2CuDk5oGt
# Joei3/2z2vRz8wD7KRuJGxU+22tSkyvErDmB1zxnV5o5NuAoCJrjOU+biQl/e8Vh
# f1mJMiUKaq4aPvCiJ6i2w7iH9xYESEE9XNjsn00gMQTZZaHtzWkHUxY93TYCCojr
# QOUGMAu4Fkvc77xVCf/GPhIudrPczkLv+XZX4bcKBUCYWJpdcRaTcYxlgepv84n3
# +3OttOe/2Y5vqgtPJfO44dXddZhogfiqwNGAwsTEOYnB9smebNd0+dmX+E/CmgrN
# Xo/4GengpZ/E8JIh5i15Jcki+cPwOoRXrToW9GOUEB1d0MYwggVqMIIEUqADAgEC
# AhEAsPVV6jnRNgfABcUA7um0CDANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJH
# QjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3Jk
# MRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEjMCEGA1UEAxMaQ09NT0RPIFJT
# QSBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcwNjI4MDAwMDAwWhcNMjAwNjI3MjM1OTU5
# WjCBqzELMAkGA1UEBhMCREUxDjAMBgNVBBEMBTg1NTUxMRwwGgYDVQQIDBNOb3Jk
# cmhlaW4tV2VzdGZhbGVuMRIwEAYDVQQHDAlLaXJjaGhlaW0xFjAUBgNVBAkMDVNv
# bm5lbmFsbGVlIDExIDAeBgNVBAoMF05ldEFwcCBEZXV0c2NobGFuZCBHbWJIMSAw
# HgYDVQQDDBdOZXRBcHAgRGV1dHNjaGxhbmQgR21iSDCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAKnyWe0IrmZ8r6hU9qhoH7pNv9HTv67xaWRFcmeQilAS
# F72E9oyFa65OTxdXoOpLD129692Rbaswbqy57P+qkj6MgBvTYFqBX9BZpnCOlCjw
# YGQVWTj34fSxUtzxKISRI4wjvYT212T0deqhpJ/Oy1BlDik7WNiNncVZRHD2RfbH
# qVZI37LaFGlSytG4z6VS7nUTGZLGay/IWEQIEwQ6AktWm696mSTKono680z7ZK2U
# OGFneQVKagfzOIqD5ZFvlZjvC6z5181/oSEFtn5MMp71HoNd4ABYOrzldFUy0CPI
# ruBB7yagUomtvLPFWux3OcA/pbJMK6EdAowrTQxxG1UCAwEAAaOCAbQwggGwMB8G
# A1UdIwQYMBaAFCmRYP+KTfrr+aZquM/55ku9Sc4SMB0GA1UdDgQWBBTChARqgCg3
# JCwDL3vvWQz8tzA8XjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNV
# HSUEDDAKBggrBgEFBQcDAzARBglghkgBhvhCAQEEBAMCBBAwRgYDVR0gBD8wPTA7
# BgwrBgEEAbIxAQIBAwIwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29t
# b2RvLm5ldC9DUFMwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9j
# YS5jb20vQ09NT0RPUlNBQ29kZVNpZ25pbmdDQS5jcmwwdAYIKwYBBQUHAQEEaDBm
# MD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9SU0FD
# b2RlU2lnbmluZ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2Rv
# Y2EuY29tMCUGA1UdEQQeMByBGmZsb3JpYW4uZmVsZGhhdXNAZ21haWwuY29tMA0G
# CSqGSIb3DQEBCwUAA4IBAQBPLYiedwvRO7FPhAkDLqCKNWwRgRWaP5fA6X/bYjcu
# eV2oAAaPGdhee5mZgpjqvzLDxLCZiLJ/Z9wCAhiK9C42Lgahbe7XOIM8NDV/MmGn
# Mo2Ba+X9KYmyVlRV6bQsNDpnW4t7mWkh4JpV2LZbLluSrJ88SAqCsrZb1C49H5m5
# YdwFZUSIL8P19MSCDxPr0OC/3qX0dFcSEBIIBKtKF3mm9/Yind3SgkxoPfxViX2D
# eK80uOmm2Gb7bOhSuzqjkvDG0INsF4zyTX4HldBJmQfKeQiD8RlF6DpUcm0AoChM
# qCLwiJOHaHHYOOS7Busif3LkvIKd+tWtrg5fw7gZ0fU3MIIF4DCCA8igAwIBAgIQ
# LnyHzA6TSlL+lP0ct800rzANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UEBhMCR0Ix
# GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEa
# MBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0Eg
# Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTMwNTA5MDAwMDAwWhcNMjgwNTA4
# MjM1OTU5WjB9MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVz
# dGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRl
# ZDEjMCEGA1UEAxMaQ09NT0RPIFJTQSBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmmJBjd5E0f4rR3elnMRHrzB79MR2zuWJX
# P5O8W+OfHiQyESdrvFGRp8+eniWzX4GoGA8dHiAwDvthe4YJs+P9omidHCydv3Lj
# 5HWg5TUjjsmK7hoMZMfYQqF7tVIDSzqwjiNLS2PgIpQ3e9V5kAoUGFEs5v7BEvAc
# P2FhCoyi3PbDMKrNKBh1SMF5WgjNu4xVjPfUdpA6M0ZQc5hc9IVKaw+A3V7Wvf2p
# L8Al9fl4141fEMJEVTyQPDFGy3CuB6kK46/BAW+QGiPiXzjbxghdR7ODQfAuADcU
# uRKqeZJSzYcPe9hiKaR+ML0btYxytEjy4+gh+V5MYnmLAgaff9ULAgMBAAGjggFR
# MIIBTTAfBgNVHSMEGDAWgBS7r34CPfqm8TyEjq3uOJjs2TIy1DAdBgNVHQ4EFgQU
# KZFg/4pN+uv5pmq4z/nmS71JzhIwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQI
# MAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEQYDVR0gBAowCDAGBgRVHSAA
# MEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9E
# T1JTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHEGCCsGAQUFBwEBBGUwYzA7
# BggrBgEFBQcwAoYvaHR0cDovL2NydC5jb21vZG9jYS5jb20vQ09NT0RPUlNBQWRk
# VHJ1c3RDQS5jcnQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNv
# bTANBgkqhkiG9w0BAQwFAAOCAgEAAj8COcPu+Mo7id4MbU2x8U6ST6/COCwEzMVj
# EasJY6+rotcCP8xvGcM91hoIlP8l2KmIpysQGuCbsQciGlEcOtTh6Qm/5iR0rx57
# FjFuI+9UUS1SAuJ1CAVM8bdR4VEAxof2bO4QRHZXavHfWGshqknUfDdOvf+2dVRA
# GDZXZxHNTwLk/vPa/HUX2+y392UJI0kfQ1eD6n4gd2HITfK7ZU2o94VFB696aSdl
# kClAi997OlE5jKgfcHmtbUIgos8MbAOMTM1zB5TnWo46BLqioXwfy2M6FafUFRun
# UkcyqfS/ZEfRqh9TTjIwc8Jvt3iCnVz/RrtrIh2IC/gbqjSm/Iz13X9ljIwxVzHQ
# NuxHoc/Li6jvHBhYxQZ3ykubUa9MCEp6j+KjUuKOjswm5LLY5TjCqO3GgZw1a6lY
# YUoKl7RLQrZVnb6Z53BtWfhtKgx/GWBfDJqIbDCsUgmQFhv/K53b0CDKieoofjKO
# Gd97SDMe12X4rsn4gxSTdn1k0I7OvjV9/3IxTZ+evR5sL6iPDAZQ+4wns3bJ9ObX
# wzTijIchhmH+v1V04SF3AwpobLvkyanmz1kl63zsRQ55ZmjoIs2475iFTZYRPAmK
# 0H+8KCgT+2rKVI2SXM3CZZgGns5IW9S1N5NGQXwH3c/6Q++6Z2H/fUnguzB9XIDj
# 5hY5S6cxggR7MIIEdwIBATCBkjB9MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
# YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
# RE8gQ0EgTGltaXRlZDEjMCEGA1UEAxMaQ09NT0RPIFJTQSBDb2RlIFNpZ25pbmcg
# Q0ECEQCw9VXqOdE2B8AFxQDu6bQIMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEM
# MQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQB
# gjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBSvZRtxaHHq5/TF
# BQ2lXdMvLIA8njANBgkqhkiG9w0BAQEFAASCAQCgc0b+YlLwO4awr6VTRgME8X8N
# 4RMLQEhX6DTSIZXaBX4zsrwZf0VOCb8+PmQ4UjJY9g/3YXKF7xIkE0Q8hWj7bNPD
# sF4Tgd8ZrZa0/0AIeMgjyWOfXA8dhsX41AHjtWbWQFjVhqwjKbNXA2QRN9gRBJpQ
# 32PH5beSPIE4GU53a0LHJpnZt8QAfqlZPIKsXlkWIa9nGPb4/tZDVyHV3F42kR62
# +df1DXTKi0B9Y85RDwhi2yZ3OWJNtHNYl9Eg5wFKimwmcDXXIdufdkdWSAjJnMNa
# rThWOw1wFqi1FNuGqYHXltN7PE9JF+rl336iL81OET9k67zUaLm8c8jtI4muoYIC
# QzCCAj8GCSqGSIb3DQEJBjGCAjAwggIsAgEBMIGpMIGVMQswCQYDVQQGEwJVUzEL
# MAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVU
# aGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy
# dXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3QCDxaI8DklXmOO
# aRQ5B+YzCzAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMTgwNjEzMTcwMzQwWjAjBgkqhkiG9w0BCQQxFgQUf+fx
# pd+FvFWzIlbxbQiAeAMeq54wDQYJKoZIhvcNAQEBBQAEggEAQUKJjS4OeeVpw9/B
# oCuX11Txh/TN1gokeqX1ix18ebP70u5Gy51LnGty2QcA6oIX2dI6zkt2qVUNhirP
# 7Q+I0N9unoSlBqVG+SafhYz4d209ZSrmu/G1YaZ7nwrkGndP/LisGhAZ4n2mrAkW
# ln/Ja99f0Bkn1HrdpBcUCeHxcOdhGf1JoDSuNS3IOhHlHYmd3HwB4o5vDNP9xFRL
# EqEhwsc499fEEUOtb08EXqVl+e++4XwYbwm1N9n0RaNeDe/BvwzNJxfB33SbbJR3
# PKJ7ioH8k904BH7xO1lFRZj1p537GrjxaOg0chW1JzRlFtil1yDf+V/JpV5gYiMi
# B6Liyg==
# SIG # End signature block