PureStorage.Pure1.psm1

function Get-PureOneCertificate {
  <#
  .SYNOPSIS
    Returns the Windows Certificate or RSA Private Key used for Pure1 Authentication.
  .DESCRIPTION
    Returns the default Pure1 certificate or key. Or returns the specified certificate object if a non-default one is used.
  .INPUTS
    Certificate store (optional), certificate thumbrint (optional)
  .OUTPUTS
    Returns the certificate object or private key path
  .EXAMPLE
    PS C:\ Get-PureOneCertificate
 
    Returns the default Pure1 certificate in the default certificiate store cert:\currentuser\my or the Default Private Key path if using Linux or MacOS
  .EXAMPLE
    PS C:\ Get-PureOneCertificate -certificateStore cert:\localmachine\my
 
    Windows only: Returns the default Pure1 certificate in the certificiate store cert:\localmachine\my
  .EXAMPLE
    PS C:\ Get-PureOneCertificate -CertificateThumbprint 3ED3EB9BF753849820CFF43B2444100D334B60DD
 
    Windows only: Returns the Pure1 certificate with the specified thumbprint in the default certificiate store cert:\currentuser\my
  .EXAMPLE
    PS C:\ Get-PureOneCertificate -certificateStore cert:\localmachine\my -CertificateThumbprint 3ED3EB9BF753849820CFF43B2444100D334B60DD
 
    Windows only: Returns the Pure1 certificate with the specified thumbprint in the specified certificiate store
  .EXAMPLE
    PS C:\ $password = Read-Host -AsSecureString
    PS C:\ Get-PureOneCertificate -export -CertificatePassword $password
   
    Will export the certificate to a PFX file with the specified password. Returns the file path.
  .EXAMPLE
    PS C:\ $password = Read-Host -AsSecureString
    PS C:\ Get-PureOneCertificate -export -CertificatePassword $password -exportdirectory C:\Users\Pureuser\Certs
   
    Will export the certificate to a PFX file in the specified directory with the specified password. Returns the file path.
  .NOTES
    Version: 1.1
    Author: Cody Hosterman https://codyhosterman.com
    Creation Date: 12/05/2020
    Purpose/Change: Initial Release
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding()]
  Param(
          [Parameter(Position=0)]
          [String]$CertificateStore,

          [Parameter(Position=1)]
          [String]$CertificateThumbprint,

          [Parameter(Position=2,ParameterSetName='Export')]
          [Switch]$Export,

          [Parameter(Position=3,ParameterSetName='Export')]
          [SecureString]$CertificatePassword,

          [Parameter(Position=4,ParameterSetName='Export')]
          [String]$ExportDirectory
  )
  if (($IsLinux -eq $true) -or ($IsMacOS -eq $true))
  {
    if ($Export -eq $true)
    {
      throw "Export is only valid for Windows."
    }
  }
  if ($IsWindows -eq $false)
  {
    if (![string]::IsNullOrEmpty($CertificateStore))
    {
      throw "The use of the CertificateStore parameter is only valid for Windows."
    }
    if (![string]::IsNullOrEmpty($CertificateThumbprint))
    {
      throw "The use of the CertificateThumbprint parameter is only valid for Windows."
    }
    $PrivateKeyFilePath = (Get-Location).Path + "/PureOnePrivate.pem"
    $checkPath = Test-Path $PrivateKeyFilePath
    if ($checkPath -eq $true)
    {
      return $PrivateKeyFilePath
    }
    else {
        throw "Key not found at the default location of $($PrivateKeyFilePath). Please create a new one with New-PureOneCertificate. If a custom path is used, there is no need for this cmdlet. Instead specify the custom path for subsequent cmds."
    }
  }
  else {
    if ([string]::IsNullOrEmpty($CertificateStore))
    {
      $CertificateStore = "cert:\currentuser\my"
    }
    if ([string]::IsNullOrEmpty($CertificateThumbprint))
    {
      $cert = Get-ChildItem -Path $CertificateStore |where-object {$_.FriendlyName -eq "Default Pure1 REST API Certificate"}
      if ($cert.Count -gt 1)
      {
        throw "More than one default certificate was found in the specified certificate store (a certificate that has the friendly name of `"Default Pure1 REST API Certificate`")."
      }
      if ($null -eq $cert)
      {
        throw "No default certificate found in the specified certificate store (a certificate that has the friendly name of `"Default Pure1 REST API Certificate`")."
      }
    }
    else {
      $cert = Get-ChildItem -Path ($CertificateStore + "\" + $CertificateThumbprint) -ErrorAction Stop
    }
  }
  if ($Export -eq $true)
  {
    if ($null -eq $CertificatePassword)
    {
      do {
        $CertificatePassword = Read-Host "Please enter a certificate export password" -AsSecureString
      }while ($CertificatePassword.length -eq 0)
    }
    if ([string]::IsNullOrEmpty($ExportDirectory))
    {
      $keyPath = (Get-Location).Path
    }
    else {
      if ((Test-Path -Path $ExportDirectory) -eq $false)
      {
        throw "Entered path $($ExportDirectory) is not valid. Please enter a valid directory. For example, C:\Users\Janice\Certs"
      }
      else {
        $keyPath = $ExportDirectory
      }
    }
    if ($PSVersionTable.PSEdition -ne "Core")
    {
      $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CertificatePassword)
      $DecryptedCertificatePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
    }
    else {
      $DecryptedCertificatePassword = ConvertFrom-SecureString $CertificatePassword -AsPlainText
    }
    $cert |Foreach-Object { [system.IO.file]::WriteAllBytes("$($keyPath)\PureOneCert.pfx",($_.Export('PFX', $DecryptedCertificatePassword)) ) }
    $foundKey = test-path "$($keyPath)\PureOneCert.pfx"
    if ($foundKey -eq $true)
    {
      return "$($keyPath)\PureOneCert.pfx"
    }
    else {
      throw "The certificate could not be exported to $($keyPath)\PureOneCert.pfx. Ensure directory is accessible."
    }
  }
  else {
    return $cert 
  }
}
function Set-PureOneDefaultCertificate {
  <#
  .SYNOPSIS
    Set a Windows Certificate to the default certificate used for Pure1 Authentication.
  .DESCRIPTION
    The default certificate is designated by using the friendly name of Default Pure1 REST API Certificate
  .INPUTS
    Certificate, or certificate store and certificate thumbprint
  .OUTPUTS
    Returns the certificate object
  .EXAMPLE
    PS C:\ Set-PureOneDefaultCertificate -certificateStore cert:\localmachine\my -CertificateThumbprint 3ED3EB9BF753849820CFF43B2444100D334B60DD
 
    Set the specified certificate to the default.
  .EXAMPLE
    PS C:\ $cert = Get-ChildItem -Path cert:\localmachine\my\3ED3EB9BF753849820CFF43B2444100D334B60DD
    PS C:\ $cert | Set-PureOneDefaultCertificate
 
    Set the specified certificate to the default.
  .EXAMPLE
    PS C:\ $cert = Get-ChildItem -Path cert:\localmachine\my\3ED3EB9BF753849820CFF43B2444100D334B60DD
    PS C:\ $cert | Set-PureOneDefaultCertificate -Confirm:$false
 
    Set the specified certificate to the default without prompt
 
  .NOTES
    Version: 1.0
    Author: Cody Hosterman https://codyhosterman.com
    Creation Date: 11/11/2020
    Purpose/Change: Initial Release
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
  Param(
        [Parameter(Position=0,ValueFromPipeline=$True,mandatory=$True,ParameterSetName='Certificate')]
        [System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate,

        [Parameter(Position=0,ParameterSetName='Thumbprint')]
        [String]$CertificateStore,

        [Parameter(Position=1,mandatory=$True,ParameterSetName='Thumbprint')]
        [String]$CertificateThumbprint
  )
  Begin {
    $checkForOneCert = $false
    if (($IsLinux -eq $true) -or ($IsMacOS -eq $true))
    {
      throw "This cmdlet is only valid/relevant for Windows-based installations of PowerShell."
    }
  }
  Process {
    if ($checkForOneCert -eq $false)
    {
      $checkForOneCert = $True
    }
    else {
      throw "Please only pass in one certificate at a time. More than one found in the pipelined input for parameter Certificate."
    }
  }
  End {
    if ([string]::IsNullOrEmpty($CertificateStore))
    {
      $CertificateStore = "cert:\currentuser\my"
    }
    if ([string]::IsNullOrEmpty($Certificate))
    {
      $Certificate = Get-ChildItem -Path ($CertificateStore + "\" + $CertificateThumbprint) -ErrorAction Stop
    }
    $certs = Get-ChildItem -Path $Certificate.PSParentPath
    foreach ($eachCert in $certs) 
    {
      if ($Certificate.Thumbprint -eq $eachCert.Thumbprint)
      {
        continue
      }
      if ($eachCert.FriendlyName -eq "Default Pure1 REST API Certificate")
      {
        $foundCert = $eachCert
        break
      }
    }
    if ($null -ne $foundCert)
    {
      $confirmText = "A default certificate is already found with the thumbprint of $($foundCert.Thumbprint). Remove this certificate as default and set $($Certificate.Thumbprint) as the default?"
      if ($PSCmdlet.ShouldProcess("","$($confirmText)`n`r","Setting $($Certificate.Thumbprint) as the default. `n`r")) 
      {
        (Get-ChildItem -Path $foundCert.PSPath).FriendlyName = $null  |Out-Null
        ((Get-ChildItem -Path $Certificate.PSPath).FriendlyName = "Default Pure1 REST API Certificate") |Out-Null
        return $Certificate
      }
    }
    else {
      ((Get-ChildItem -Path $Certificate.PSPath).FriendlyName = "Default Pure1 REST API Certificate")  |Out-Null
      return $Certificate
    }
  }
}
function New-PureOneCertificate {
    <#
    .SYNOPSIS
      Creates a new certificate for use in authentication with Pure1.
    .DESCRIPTION
      Creates a properly formatted RSA 256 certificate
    .INPUTS
      Certificate store (optional)
    .OUTPUTS
      Returns the certificate
    .EXAMPLE
      PS C:\ New-PureOneCertificate
 
      Creates a properly formatted self-signed certificate for Pure1 authentication. Defaults to certificate store of cert:\currentuser\my
    .EXAMPLE
      PS C:\ New-PureOneCertificate -NonDefault
 
      Creates a properly formatted self-signed certificate for Pure1 authentication. Defaults to certificate store of cert:\currentuser\my. The nonDefault switch makes the created certificate not the default one.
    .EXAMPLE
      PS C:\ New-PureOneCertificate -certificateStore cert:\localmachine\my
 
      Creates a properly formatted self-signed certificate for Pure1 authentication. Uses the specifed certificate store. Non-default stores usually require running as administrator. Windows only.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> New-PureOneCertificate -RsaPassword $password
 
      Creates a properly formatted private and public key pair for Pure1 authentication. Uses the working directory. Linux or MacOS only.
 
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> New-PureOneCertificate -RsaPassword $password -PrivateKeyFileDirectory "/home/pureuser"
 
      Creates a properly formatted private and public key pair for Pure1 authentication and stores it in specified directory. Linux or MacOS only.
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='Certificate')]
    Param(
            [Parameter(Position=0,ParameterSetName='Certificate')]
            [Parameter(Position=0,ParameterSetName='Import')]
            [String]$CertificateStore = "cert:\currentuser\my",

            [Parameter(Position=1,ParameterSetName='RSAPair')]
            [SecureString]$RsaPassword,

            [Parameter(Position=2,ParameterSetName='RSAPair',mandatory=$True)]
            [Parameter(Position=2,ParameterSetName='Import')]
            [SecureString]$Password,
            
            [Parameter(Position=3)]
            [Switch]$Overwrite,

            [Parameter(Position=4,ParameterSetName='RSAPair')]
            [String]$PrivateKeyFileDirectory,

            [Parameter(Position=5,ParameterSetName='Certificate')]
            [Parameter(Position=5,ParameterSetName='Import')]
            [Switch]$NonDefault,

            [Parameter(Position=6,ParameterSetName='Import',mandatory=$True)]
            [String]$CertificateFile
    )
    if (![string]::IsNullOrEmpty($CertificateFile))
    {
      if (($IsMacOS -eq $true) -or ($IsLinux -eq $true))
      {
        throw "Import feature is only valid for Windows environments."
      }
      $checkFile = Test-Path $CertificateFile
      if ($checkFile -eq $false)
      {
        throw "$($certificateFile) is not found. Please confirm file path is correct."
      }
      $certExtension = [IO.Path]::GetExtension($CertificateFile)
      if ($certExtension -ne ".pfx")
      {
        throw "The certificate should be a pfx file. File type found is $($certExtension)"
      }
      if ($Password.Length -eq 0)
      {
        $Password = Read-Host -Prompt "Please enter a password to be used for the private key" -AsSecureString
      }
      $ErrorActionPreference = "Stop"
      $CertObj = Import-PfxCertificate -FilePath $CertificateFile -CertStoreLocation $CertificateStore -Password $Password -Exportable -ErrorAction Stop
    }
    if ($IsWindows -eq $false)
    {
      if ([string]::IsNullOrEmpty($PrivateKeyFileDirectory))
      {
        $keyPath = (Get-Location).Path
      }
      else {
        if ((Test-Path -Path $PrivateKeyFileDirectory) -eq $false)
        {
          throw "Entered path $($PrivateKeyFileDirectory) is not valid. Please enter a valid directory. For example, /home/user"
        }
        else {
          $keyPath = $PrivateKeyFileDirectory
        }
      }
      $checkPath = Test-Path "$($keyPath)/PureOnePrivate.pem"
      if (($checkPath -eq $true) -and ($Overwrite -eq $false))
      {
        throw "A pre-existing Pure1 Private Key exists at $($keyPath)/PureOnePrivate.pem. Overwriting this key will require a new application ID to be generated for the new key in Pure1. Either re-run with the -overwrite switch, or specify a different directory in the -keypath parameter, or skip this cmdlet and pass in the path of your custom key location to New-PureOneConnection."
      }
      if ($Password.Length -eq 0)
      {
        if ($RsaPassword.Length -eq 0)
        {
          $Password = Read-Host -Prompt "Please enter a password to be used for the private key" -AsSecureString
        }
        else {
          Write-Warning "The RsaPassword parameter is being deprecated. Please use the Password parameter instead."
          $Password = $RsaPassword
        }
      }
      $DecryptedPassword = ConvertFrom-SecureString $Password -AsPlainText 
      if (($DecryptedPassword.length -lt 4) -or ($DecryptedPassword.length -gt 1022))
      {
        throw "The entered private key password must be more than 4 characters and less than 1023 characters."
      }
      openssl genrsa -aes256 -passout pass:$DecryptedPassword -out $keypath/PureOnePrivate.pem 2048 2>/dev/null
      openssl rsa -in $keypath/PureOnePrivate.pem -outform PEM -pubout -out $keypath/PureOnePublic.pem -passin pass:$DecryptedPassword 2>/dev/null
      $keyPaths = [ordered]@{
        PrivateKey = "$($keyPath)/PureOnePrivate.pem"
        PublicKey = "$($keyPath)/PureOnePublic.pem"
      }
      return $keyPaths
    }
    if (($null -eq $isWindows) -or ($isWindows -eq $true))
    {
      if ([string]::IsNullOrEmpty($CertificateFile))
      {
        if (([System.Environment]::OSVersion.Version).Major -eq 6)
        {
            #For Windows 2012 support--less specific but the default certificate still works.
            $CertObj = New-SelfSignedCertificate -certstorelocation $certificateStore -DnsName PureOneCert
        }
        else 
        {
            $policies = [System.Security.Cryptography.CngExportPolicies]::AllowPlaintextExport,[System.Security.Cryptography.CngExportPolicies]::AllowExport
            $CertObj = New-SelfSignedCertificate -certstorelocation $certificateStore -HashAlgorithm "SHA256" -KeyLength 2048 -KeyAlgorithm RSA -KeyUsage DigitalSignature  -KeyExportPolicy $policies -Subject "PureOneCert" -ErrorAction Stop   
        }
      }
      $cert = Get-ChildItem -Path $CertObj.PSPath
      if ($NonDefault -eq $false)
      {
        $certs = Get-ChildItem -Path $cert.PSParentPath
        foreach ($eachCert in $certs) 
        {
          if ($cert.Thumbprint -eq $eachCert.Thumbprint)
          {
            continue
          }
          if ($eachCert.FriendlyName -eq "Default Pure1 REST API Certificate")
          {
            $foundCert = $eachCert
            break
          }
        }
        if (($null -ne $foundCert) -and ($Overwrite -ne $true))
        {
          $cert = Set-PureOneDefaultCertificate -Certificate $cert -Confirm:$true
        }
        else {
          $cert = Set-PureOneDefaultCertificate -Certificate $cert -Confirm:$false
        }
      }
      return $cert
    }
}
function Get-PureOnePublicKey {
    <#
    .SYNOPSIS
      Retrives and formats a PEM based Public Key from a Windows-based certificate
    .DESCRIPTION
      Pulls out the public key and formats it in INT 64 PEM encoding for use in Pure1
    .INPUTS
      Certificate
    .OUTPUTS
      Returns the PEM based public key
    .EXAMPLE
      PS C:\ Get-PureOnePublicKey
 
      Returns the PEM formatted Public Key of the default certificate.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> Get-PureOnePublicKey -RsaPassword $password
 
      Returns the PEM formatted Public Key of the default private key.
    .EXAMPLE
      PS C:\ $cert = New-PureOneCertificate
      PS C:\ Get-PureOnePublicKey -certificate $cert
 
      Returns the PEM formatted Public Key of the certificate passed in so that it can be entered in Pure1.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> $keys = New-PureOneCertificate -RsaPassword $password
      PS /home/pureuser> Get-PureOnePublicKey -PrivateKeyFileLocation $keys.PrivateKey -RsaPassword $password
 
      Returns the PEM formatted Public Key of the default Pure1 private key file passed in so that it can be entered in Pure1.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> Get-PureOnePublicKey -PrivateKeyFileLocation /home/pureuser/PureOnePrivateKey.pem -RsaPassword $password
 
      Returns the PEM formatted Public Key of a private key file passed in so that it can be entered in Pure1.
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='Certificate')]
    Param(
        [Parameter(Position=0,ValueFromPipeline=$True,ParameterSetName='Certificate')]
        [System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate,

        [Parameter(Position=1,ParameterSetName='RSAPair')]
        [String]$PrivateKeyFileLocation,

        [Parameter(Position=2,ParameterSetName='RSAPair')]
        [securestring]$RsaPassword
    )
    Begin {
      $publicKeys = @()
    }
    Process {
      if (($IsLinux -eq $true) -or ($IsMacOS -eq $true))
      {
        if ([string]::IsNullOrEmpty($PrivateKeyFileLocation))
        {
          $PrivateKeyFileLocation = Get-PureOneCertificate -ErrorAction SilentlyContinue
        }
        if ([string]::IsNullOrEmpty($PrivateKeyFileLocation))
        {
          throw "No private key provided and default key does not exist. Please provide a private key path or create a new one."
        }
        if ($RsaPassword.length -eq 0)
        {
          do {
            $RsaPassword = Read-Host "Please enter your RSA private key password" -AsSecureString
          } while ($RsaPassword.length -eq 0)
        }
        $checkPath = Test-Path $PrivateKeyFileLocation
        if ($checkPath -eq $false)
        {
          throw "File not found at $($PrivateKeyFileLocation). Check path and try again."
        }
        $DecryptedRsaPassword = ConvertFrom-SecureString $RsaPassword -AsPlainText
        openssl rsa -in $($PrivateKeyFileLocation) -outform PEM -pubout -out ./PureOnePublicTemp.pem -passin pass:$DecryptedRsaPassword  2>/dev/null
        $checkPath = Test-Path ./PureOnePublicTemp.pem
        if ($checkPath -eq $false)
        {
          throw "Public key could not be generated. Confirm password and/or permission access to private key"
        }
        $publicKey = Get-Content ./PureOnePublicTemp.pem
        Remove-Item -Path ./PureOnePublicTemp.pem
        $publicKeys += $publicKey
      }
      else {
        if ($null -eq $certificate)
        {
            $Certificate = Get-PureOneCertificate -ErrorAction SilentlyContinue
            if ($null -eq $certificate)
            {
              throw "You must pass in a x509 certificate or create/set a default one."
            }
        }
        $certRaw = ([System.Convert]::ToBase64String($certificate.PublicKey.EncodedKeyValue.RawData)).tostring()
        $publicKeys += ("-----BEGIN PUBLIC KEY-----`n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A" + $certRaw + "`n-----END PUBLIC KEY-----")
      }
    }
    End {
      return $publicKeys
    }
}
function New-PureOneJwt {
    <#
    .SYNOPSIS
      Takes in a Pure1 Application ID and certificate to create a JSON Web Token.
    .DESCRIPTION
      Takes in a Pure1 Application ID and certificate to create a JSON Web Token that is valid for by default 30 days, but is extended if a custom expiration is passed in. Can also take in a private key in lieu of the full cert. Will reject if the private key is not properly formatted.
    .INPUTS
      Pure1 Application ID, an expiration, and a certificate or a private key.
    .OUTPUTS
      Returns the JSON Web Token as a string.
    .EXAMPLE
        PS C:\ New-PureOneJwt -pureAppID pure1:apikey:v4u3ZXXXXXXXXC6o
 
        Returns a JSON Web Token that can be used to create a Pure1 REST session. A JWT generated with no specificed expiration is valid for 30 days. Since no certificate is specified it will use the default certificate if it exists.
      .EXAMPLE
        PS /home/pureuser> $password = Read-Host -AsSecureString
        PS /home/pureuser> New-PureOneJwt -RsaPassword $password -PureAppID pure1:apikey:TACAwKsXL7kLa96q
 
        Returns a JSON Web Token that can be used to create a Pure1 REST session. A JWT generated with no specificed expiration is valid for 30 days. Since no key file is specified it will use the default key file if it exists.
    .EXAMPLE
        PS C:\ $cert = New-PureOneCertificate
        PS C:\ New-PureOneJwt -certificate $cert -pureAppID pure1:apikey:v4u3ZXXXXXXXXC6o
 
        Returns a JSON Web Token that can be used to create a Pure1 REST session. A JWT generated with no specificed expiration is valid for 30 days.
    .EXAMPLE
        PS C:\ $cert = New-PureOneCertificate
        PS C:\ New-PureOneJwt -certificate $cert -pureAppID pure1:apikey:v4u3ZXXXXXXXXC6o -expiration ((get-date).addDays(2))
 
        Returns a JSON Web Token that can be used to create a Pure1 REST session. An expiration is set for two days from now, so this JWT will be valid to create new REST sessions for 48 hours.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> New-PureOneJwt -PrivateKeyFileLocation /home/pureuser/PureOnePrivate.pem -RsaPassword $password -PureAppID pure1:apikey:TACAwKsXL7kLa96q
 
      Creates a JSON web token for external use for the specified private key and the associated Pure1 API key.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> New-PureOneJwt -PrivateKeyFileLocation /home/pureuser/PureOnePrivate.pem -RsaPassword $password -PureAppID pure1:apikey:TACAwKsXL7kLa96q
 
      Creates a JSON web token for external use for the specified private key and the associated Pure1 API key. An expiration is set for two days from now, so this JWT will be valid to create new REST sessions for 48 hours.
    .NOTES
      Version: 1.2
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 12/05/2020
      Purpose/Change: Improved interactions
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='None')]
    Param(
            [Parameter(Position=0,ValueFromPipeline=$True,ParameterSetName='WindowsCert')]
            [System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate,

            [Parameter(Position=1,mandatory=$True,ParameterSetName='WindowsCert')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='WindowsKey')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='Unix')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='None')]
            [string]$PureAppID,
            
            [Parameter(Position=2,ParameterSetName='WindowsKey')]
            [System.Security.Cryptography.RSA]$PrivateKey,

            [Parameter(Position=3,ParameterSetName='WindowsCert')]
            [Parameter(Position=3,ParameterSetName='WindowsKey')]
            [Parameter(Position=3,ParameterSetName='Unix')]
            [Parameter(Position=3,ParameterSetName='None')]
            [System.DateTime]$Expiration,

            [Parameter(Position=4,ParameterSetName='Unix')]
            [string]$PrivateKeyFileLocation,

            [Parameter(Position=5,ParameterSetName='Unix',mandatory=$True)]
            [securestring]$RsaPassword
    )
    Begin {
      $checkForOneCert = $false
    }
    Process {
      if ($checkForOneCert -eq $false)
      {
        $checkForOneCert = $True
      }
      else {
        throw "Please only pass in one certificate at a time. More than one found in the pipelined input for parameter Certificate."
      }
    }
    End {
      if (($IsLinux -eq $true) -or ($IsMacOS -eq $true))
      {
        if ([string]::IsNullOrEmpty($PrivateKeyFileLocation))
        {
          $PrivateKeyFileLocation = Get-PureOneCertificate -ErrorAction SilentlyContinue
        }
        if ([string]::IsNullOrEmpty($PrivateKeyFileLocation))
        {
          throw "No private key provided and default key does not exist. Please provide a private key path or create a new one."
        }
      }
      if (($null -eq $isWindows) -or ($isWindows -eq $true))
      {
        if (($null -eq $privateKey) -and ($null -eq $certificate))
        {
            $Certificate = Get-PureOneCertificate -ErrorAction SilentlyContinue
            if ($null -eq $certificate)
            {
              throw "You must pass in a x509 certificate or a RSA Private Key"
            }
        }
        #checking for certificate accuracy
        if ($null -ne $certificate)
        {
            if ($certificate.HasPrivateKey -ne $true)
            {
                throw "There is no private key associated with this certificate. Please regenerate certificate with a private key."
            }
            if ($null -ne $certificate.PrivateKey)
            {
                $privateKey = $certificate.PrivateKey
            }
            else {
                try {
                    $privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
                }
                catch {
                    throw "Could not obtain the private key from the certificate. Please re-run this cmdlet from a PowerShell session started with administrative rights or ensure you have Read Only or higher rights to the certificate."
                }
            }
        }
        #checking for correct private key type. Must be SHA-256, 2048 bit.
        if ($null -ne $privateKey)
        {
            if ($privateKey.KeySize -ne 2048)
            {
                throw "The key must be 2048 bit. It is currently $($privateKey.KeySize)"
            }
            if ($privateKey.SignatureAlgorithm -ne "RSA")
            {
                throw "This key is not an RSA-based key."
            }
        }
      }
      $pureHeader = '{"alg":"RS256","typ":"JWT"}'
      $curTime = (Get-Date).ToUniversalTime()
      $curTime = [Math]::Floor([decimal](Get-Date($curTime) -UFormat "%s"))
      if ($null -eq $expiration)
      {
          $expTime = $curTime  + 2592000
      }
      else {
          $expTime = $expiration.ToUniversalTime()
          $expTime = [Math]::Floor([decimal](Get-Date($expTime) -UFormat "%s"))
      }
      $payloadJson = '{"iss":"' + $pureAppID + '","iat":' + $curTime + ',"exp":' + $expTime + '}'
      $encodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($pureHeader)) -replace '\+','-' -replace '/','_' -replace '='
      $encodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadJson)) -replace '\+','-' -replace '/','_' -replace '='
      $toSign = $encodedHeader + '.' + $encodedPayload
      if ($IsWindows -eq $false) {
          if ($RsaPassword.Length -eq 0)
          {
            $RsaPassword = Read-Host -Prompt "Please enter a password to be used for the private key" -AsSecureString
          }
          $DecryptedRsaPassword = ConvertFrom-SecureString $RsaPassword -AsPlainText
          set-content -value $tosign -Path ./PureOneHeader.txt -NoNewline
          Start-Process -FilePath ./openssl -ArgumentList "dgst -binary -sha256 -sign $($PrivateKeyFileLocation) -passin pass:$($DecryptedRsaPassword) -out ./PureOneSignedHeader.txt ./PureOneHeader.txt"
          #file lock often still exists, wait for it to release.
          start-sleep 1
          $signature = openssl base64 -in ./PureOneSignedHeader.txt
          $signature = $signature -replace '\+','-' -replace '/','_' -replace '='
          Remove-Item -Path ./PureOneSignedHeader.txt
          Remove-Item -Path ./PureOneHeader.txt
      }
      else {
        $toSignEncoded = [System.Text.Encoding]::UTF8.GetBytes($toSign)
        $signature = [Convert]::ToBase64String($privateKey.SignData($toSignEncoded,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) -replace '\+','-' -replace '/','_' -replace '='
      }
      $jwt = $toSign + '.' + $signature 
      return $jwt.Replace(" ", "")
  }
}
function New-PureOneConnection {
    <#
    .SYNOPSIS
      Takes in a Pure1 Application ID and certificate to create a 10 hour access token.
    .DESCRIPTION
      Takes in a Pure1 Application ID and certificate to create a 10 hour access token. Can also take in a private key in lieu of the full cert. Will reject if the private key is not properly formatted.
    .INPUTS
      Pure1 Application ID, a certificate or a private key.
    .OUTPUTS
      Does not return anything--it stores the Pure1 REST access token in a global variable called $Global:PureOneConnections. Valid for 10 hours.
    .EXAMPLE
      PS C:\ New-PureOneConnection -pureAppID pure1:apikey:PZogg67LcjImYTiI
 
      Create a Pure1 REST connection using a passed in certificate and the specified Pure1 App ID. Since no certificate/key is specified uses the default certificate/key if it exists.
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> New-PureOneConnection -RsaPassword $password -PureAppID pure1:apikey:TACAwKsXL7kLa96q
 
      Creates a Pure1 REST connection for use with additional Pure1 cmdlets. Since no key location is specified it uses the default key if it exists.
    .EXAMPLE
      PS C:\ $cert = New-PureOneCertificate
      PS C:\ $cert | New-PureOneConnection -pureAppID pure1:apikey:PZogg67LcjImYTiI
 
      Create a Pure1 REST connection using a passed in certificate and the specified Pure1 App ID
    .EXAMPLE
      PS C:\ $cert = New-PureOneCertificate
      PS C:\ $privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
      PS C:\ $privateKey | New-PureOneConnection -pureAppID pure1:apikey:PZogg67LcjImYTiI
 
      Create a Pure1 REST connection using a passed in private key and the specified Pure1 App ID
    .EXAMPLE
      PS /home/pureuser> $password = Read-Host -AsSecureString
      PS /home/pureuser> New-PureOneConnection -PrivateKeyFileLocation /home/pureuser/PureOnePrivate.pem -RsaPassword $password -PureAppID pure1:apikey:TACAwKsXL7kLa96q
 
      Creates a Pure1 REST connection for use with additional Pure1 cmdlets.
    .NOTES
      Version: 1.3
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 12/04/2020
      Purpose/Change: Fix for PowerShell 5.x
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='AppID')]
    Param(
            [Parameter(Position=0,ValueFromPipeline=$True,ParameterSetName='Certificate')]
            [System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate,

            [Parameter(Position=1,mandatory=$True,ParameterSetName='AppID')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='PrivateKey')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='Certificate')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='Unix')]
            [string]$PureAppID,
            
            [Parameter(Position=2,ValueFromPipeline=$True,mandatory=$True,ParameterSetName='PrivateKey')]
            [System.Security.Cryptography.RSA]$PrivateKey,

            [Parameter(Position=3,ParameterSetName='AppID')]
            [Parameter(Position=3,ParameterSetName='PrivateKey')]
            [Parameter(Position=3,ParameterSetName='Certificate')]
            [Parameter(Position=3,ParameterSetName='Unix')]
            [switch]$ReturnOrg,

            [Parameter(Position=4,ParameterSetName='Unix')]
            [string]$PrivateKeyFileLocation,

            [Parameter(Position=5,mandatory=$True,ParameterSetName='Unix')]
            [securestring]$RsaPassword,

            [Parameter(Position=6,ParameterSetName='JWT')]
            [string]$Jwt
    )Begin {
      $checkForOneCert = $false
    }
    Process {
      if ([string]::IsNullOrEmpty($Jwt))
      {
        if ($checkForOneCert -eq $false)
        {
          $checkForOneCert = $True
        }
        else {
          throw "Please only pass in one certificate/key at a time. More than one found in the pipelined input for parameter Certificate/private key."
        }
      }
    }
    End {
      if ([string]::IsNullOrEmpty($Jwt))
      {
        if (($isWindows -eq $false) -and ([string]::IsNullOrEmpty($RsaPassword)))
        {
          $RsaPassword = Read-Host "Please enter in the password for your private key" -AsSecureString
        }
        if (($isWindows -eq $true) -or ($null -eq $isWindows))
        {
          if (($null -eq $certificate) -and ($null -eq $PrivateKey))
          {
            $Certificate = Get-PureOneCertificate -ErrorAction SilentlyContinue
            if ($null -eq $certificate)
            {
              throw "Please pass in a certificate or RSA private key."
            }
          }
          if ($null -eq $certificate)
          {
              $jwt = New-PureOneJwt -privateKey $privateKey -pureAppID $pureAppID -expiration ((Get-Date).AddSeconds(60)) -ErrorAction Stop
          }
          else 
          {
              $jwt = New-PureOneJwt -certificate $certificate -pureAppID $pureAppID -expiration ((Get-Date).AddSeconds(60)) -ErrorAction Stop
          }
        }
        else 
        {
          if (($isWindows -eq $false) -and ([string]::IsNullOrEmpty($PrivateKeyFileLocation)))
          {
            $PrivateKeyFileLocation = Get-PureOneCertificate -ErrorAction SilentlyContinue
            if ([string]::IsNullOrEmpty($PrivateKeyFileLocation)) 
            {
              throw "No default private key found. Please pass in a private key file location or create a new one with New-PureOneCertificate."
            }
          }
        }
        try {
          $jwt = New-PureOneJwt -PrivateKeyFileLocation $PrivateKeyFileLocation -RsaPassword $RsaPassword -pureAppID $pureAppID -expiration ((Get-Date).AddSeconds(60)) -ErrorAction Stop
        }
        catch
        {
        #throw ($_.errordetails.message |ConvertFrom-Json).error_description
        }
      }
      $apiendpoint = $Global:PureOneRestEndpointURL 
      $AuthAction = @{
          grant_type = "urn:ietf:params:oauth:grant-type:token-exchange"
          subject_token = $jwt
          subject_token_type = "urn:ietf:params:oauth:token-type:jwt"
          }
      try {
        $pureOnetoken = Invoke-RestMethod -Method Post -Uri $apiendpoint -ContentType "application/x-www-form-urlencoded" -Body $AuthAction -ErrorAction Stop
      }
      catch {
        throw ($_.errordetails.message |ConvertFrom-Json).error_description
      }
      write-debug $pureOnetoken
      $orgInfo = Resolve-JWTtoken -token $pureOnetoken
      $jwtInfo = Resolve-JWTtoken -token $jwt
      $date = get-date "1/1/1970"
      $date = $date.AddSeconds($orgInfo.exp).ToLocalTime()
      if (($null -eq $isWindows) -or ($isWindows -eq $true))
      {
        $newOrg = New-Object -TypeName WindowsPureOneOrganization -ArgumentList $orgInfo.org,$pureOnetoken.access_token,$jwtInfo.iss,$orgInfo.max_role,$date,$certificate -ErrorAction Stop
      }
      else {
        $newOrg = New-Object -TypeName UnixPureOneOrganization -ArgumentList $orgInfo.org,$pureOnetoken.access_token,$jwtInfo.iss,$orgInfo.max_role,$date,$RsaPassword,$PrivateKeyFileLocation -ErrorAction Stop
      }
      if ($Global:PureOneConnections.Count -eq 0)
      {
        $Global:PureOneConnections += $newOrg
        $Global:PureOneConnections[0].SetDefault($true)
      }
      else 
      {
        foreach ($connection in $Global:PureOneConnections) 
        {
          if ($connection.PureOneOrgID -eq $newOrg.PureOneOrgID)
            {
              if ($connection.updateLock -eq $false)
              {
                throw "The Pure1 Organization with ID $($connection.PureOneOrgID) is already connected."
              }
              else {
                $pureOneUpdate = $True
                break
              }
            }
          }
        if ($pureOneUpdate -ne $true)
        {
          $Global:PureOneConnections += $newOrg
        }
      }
      if ($returnOrg -eq $true)
      {
        return $newOrg
      }
    }
}
function New-PureOneOperation {
  <#
  .SYNOPSIS
    Allows you to run a Pure1 REST operation that has not yet been built into this module.
  .DESCRIPTION
    Runs a REST operation to Pure1
  .INPUTS
    A filter/query, an resource, a REST body, and optionally an access token.
  .OUTPUTS
    Returns Pure1 REST response.
  .EXAMPLE
    PS C:\ $cert = New-PureOneCertificate
    PS C:\ $cert | New-PureOneConnection -pureAppID pure1:apikey:PZogg67LcjImYTiI
    PS C:\ New-PureOneOperation -resourceType volumes -restOperationType GET
 
    Create a Pure1 REST connection and requests all volumes
  .EXAMPLE
    PS C:\ $cert = New-PureOneCertificate
    PS C:\ $cert | New-PureOneConnection -pureAppID pure1:apikey:PZogg67LcjImYTiI
    PS C:\ New-PureOneOperation -resourceType arrays -restOperationType GET
 
    Create a Pure1 REST connection and requests all arrays
  .NOTES
      Version: 1.2
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 09/02/2020
      Purpose/Change: Core support
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding()]
  Param(
      [Parameter(Position=0,mandatory=$True)]
      [string]$ResourceType,

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

      [Parameter(Position=2)]
      [string]$JsonBody,

      [Parameter(Position=3,mandatory=$True)]
      [ValidateSet('POST','GET','DELETE','PUT')]
      [string]$RestOperationType,

      [Parameter(Position=4)]
      [string]$PureOneToken
  )
  $pureOneHeader = Set-PureOneHeader -pureOneToken $pureOneToken -ErrorAction Stop
  Write-Debug $pureOneHeader.authorization
  $apiendpoint = "$($global:PureOneRestUrl)/$($global:pureOneRestVersion)/" + $resourceType + $queryFilter
  Write-Debug $apiendpoint
  $ErrorActionPreference = "Stop"
  if ($jsonBody -ne "")
  {
      $pureResponse = Invoke-RestMethod -Method $restOperationType -Uri $apiendpoint -ContentType "application/json" -Headers $pureOneHeader  -Body $jsonBody -ErrorAction Stop
      $pureObjects = $pureResponse.items
  }
  else 
  {
    $pureResponse = Invoke-RestMethod -Method $restOperationType -Uri $apiendpoint -ContentType "application/json" -Headers $pureOneHeader -ErrorAction Stop
    Write-Debug $pureResponse
    $pureObjects = $pureResponse.items
    while ($null -ne $pureResponse.continuation_token) 
    {
        $continuationToken = $pureResponse.continuation_token
        if (($queryFilter -eq "") -and ($oneRound -ne $true))
        {
            $apiendpoint = $apiendpoint + "?"
        }
        try {      
          Write-Debug ($apiendpoint + "&continuation_token=`'$($continuationToken)`'")     
          $pureResponse = Invoke-RestMethod -Method $restOperationType -Uri  ($apiendpoint + "&continuation_token=`'$($continuationToken)`'")  -ContentType "application/json" -Headers $pureOneHeader -ErrorAction Stop
          $oneRound = $True
          write-debug $pureResponse
          $pureObjects += $pureResponse.items
        }
        catch {
          write-debug $_
          if ($_.Exception -like "*The remote server returned an error: (504) Gateway Timeout.*")
          {
            Write-Warning -Message "The remote server returned an error: (504) Gateway Timeout. Pausing briefly and re-trying."
            start-sleep 5
          }
          elseif ((convertfrom-json -inputobject $_.ErrorDetails.Message).Message -eq "API org rate limit exceeded")
          {
            Write-Warning -Message "Pure1 API rate limit exceeded. Sleeping briefly to reset counter."
            start-sleep 5
          }
          continue
        } 
    }  
  }   
  $ErrorActionPreference = "Continue"
  return $pureObjects
}
function Get-PureOneSupportContract {
  <#
  .SYNOPSIS
    Returns all support contracts listed in your Pure1 account.
  .DESCRIPTION
    Returns all support contracts listed in your Pure1 account. Allows for some filters.
  .INPUTS
    None required. Optional inputs are array name and Pure1 access token.
  .OUTPUTS
    Returns the support contract information in Pure1.
  .EXAMPLE
    PS C:\ Get-PureOneSupportContract
 
    Returns all support contracts from all arrays in all connected Pure1 organizations
  .EXAMPLE
    PS C:\ Get-PureOneSupportContract -arrayId ef9d6965-7e16-4d46-9425-d2fea48a8fe5
 
    Returns the support contract from the specified array ID
  .EXAMPLE
    PS C:\ Get-PureOneSupportContract -arrayName sn1-m20r2-c05-36
 
    Returns the support contract from the specified array
  .NOTES
    Version: 1.0
    Author: Cody Hosterman https://codyhosterman.com
    Creation Date: 09/23/2020
    Purpose/Change: First release
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding(DefaultParameterSetName='OrgProduct')]
  Param(
          [Parameter(Position=0,ParameterSetName='Name',mandatory=$True)]
          [string]$ArrayName,
          
          [Parameter(Position=1,ParameterSetName='ID',mandatory=$True)]
          [string]$ArrayId,

          [Parameter(Position=2)]
          [string]$PureOneToken,

          [Parameter(Position=3)]
          [PureOneOrganization[]]$PureOneOrganization
  )
    $restQuery = "?filter="
    if ($arrayName -ne "")
    {
        $restQuery = $restQuery + "resource.name=`'$($arrayName)`'"
    }
    if ($arrayId -ne "")
    {
        $restQuery = $restQuery + "resource.id=`'$($arrayId)`'"
    }
    if ($restQuery -eq "?filter=")
    {
      $restQuery = $null
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureContracts = @()
    foreach ($token in $tokens) {
      $pureContracts += New-PureOneOperation -resourceType arrays/support-contracts -queryFilter $restQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
    }
    foreach ($pureContract in $pureContracts) {
      $epochTime = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
      $pureContract.start_date  = $epochTime.AddmilliSeconds($pureContract.start_date)
      $epochTime = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
      $pureContract.end_date  = $epochTime.AddmilliSeconds($pureContract.end_date)
    }
    $contracts = $pureContracts |Select-Object @{N="ID";E={$_.Resource.ID}}, @{N="Name";E={$_.Resource.Name}},@{N="Resource_Type";E={$_.Resource.resource_type}},@{N="FQDN";E={$_.Resource.fqdn}},start_date,end_date
    return $contracts    
}
function Get-PureOneAlert {
  <#
  .SYNOPSIS
    Returns all Pure Storage alerts listed in your Pure1 account.
  .DESCRIPTION
    Returns all Pure Storage alerts listed in your Pure1 account. Allows for some filters.
  .INPUTS
    None required. Optional inputs are array type, array name, and Pure1 access token.
  .OUTPUTS
    Returns the alert information in Pure1.
  .EXAMPLE
    PS C:\ Get-PureOneAlert
 
    Returns all open alerts from all arrays in all connected Pure1 organizations
  .EXAMPLE
    PS C:\ Get-PureOneAlert -closed
 
    Returns all closed alerts from all arrays in all connected Pure1 organizations
  .EXAMPLE
    PS C:\ Get-PureOneAlert -Severity Warning
 
    Returns all alerts of severity level "warning" from all arrays in all connected Pure1 organizations
  .EXAMPLE
    PS C:\ Get-PureOneAlert -arrayId ef9d6965-7e16-4d46-9425-d2fea48a8fe5
 
    Returns alerts from the specified array ID
  .EXAMPLE
    PS C:\ Get-PureOneAlert -arrayName sn1-m20r2-c05-36
 
    Returns alerts from the specified array
  .NOTES
    Version: 1.1
    Author: Cody Hosterman https://codyhosterman.com
    Creation Date: 08/29/2020
    Purpose/Change: Core support
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding(DefaultParameterSetName='OrgProduct')]
  Param(
          [Parameter(Position=0,ParameterSetName='Name',mandatory=$True)]
          [string]$ArrayName,
          
          [Parameter(Position=1,ParameterSetName='ID',mandatory=$True)]
          [string]$ArrayId,

          [Parameter(Position=2)]
          [switch]$Closed,

          [Parameter(Position=3)]
          [ValidateSet('hidden','warning','critical','info')]
          [string]$Severity,

          [Parameter(Position=4)]
          [string]$PureOneToken,

          [Parameter(Position=5)]
          [PureOneOrganization[]]$PureOneOrganization
  )
    $restQuery = "?filter="
    if ($arrayName -ne "")
    {
        $restQuery = $restQuery + "arrays.name=`'$($arrayName)`'"
    }
    if ($arrayId -ne "")
    {
        $restQuery = $restQuery + "arrays.id=`'$($arrayId)`'"
    }
    if ($Severity -ne "")
    {
        if ($restQuery -ne "?filter=")
        {
          $restQuery = $restQuery + " and severity=`'$($Severity)`'"
        }
        else {
          $restQuery = $restQuery + "severity=`'$($Severity)`'"
        }
    }
    if ($Closed -eq $true)
    {
      $desiredStatus = "closed"
    }
    else {
      $desiredStatus = "open"
    }
    if ($restQuery -ne "?filter=")
    {
      $restQuery = $restQuery + " and state=`'$($desiredStatus)`'"
    }
    else {
      $restQuery = $restQuery + "state=`'$($desiredStatus)`'"
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureArrays = @()
    foreach ($token in $tokens) {
      $pureArrays += New-PureOneOperation -resourceType alerts -queryFilter $restQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
    }
    return $pureArrays    
}
function Get-PureOneArray {
    <#
    .SYNOPSIS
      Returns all Pure Storage arrays listed in your Pure1 account.
    .DESCRIPTION
      Returns all Pure Storage arrays listed in your Pure1 account. Allows for some filters.
    .INPUTS
      None required. Optional inputs are array type, array name, and Pure1 access token.
    .OUTPUTS
      Returns the Pure Storage array information in Pure1.
    .EXAMPLE
      PS C:\ Get-PureOneArray
 
      Returns all arrays in all connected Pure1 organizations
    .EXAMPLE
      PS C:\ Get-PureOneArray -arrayProduct FlashBlade
 
      Returns all FlashBlades in all connected Pure1 organizations
    .EXAMPLE
      PS C:\ Get-PureOneArray -arrayId ef9d6965-7e16-4d46-9425-d2fea48a8fe5
 
      Returns array with specified ID
    .EXAMPLE
      PS C:\ Get-PureOneArray -arrayName sn1-m20r2-c05-36
 
      Returns array with specified name
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='OrgProduct')]
    Param(
            [Parameter(Position=0,ParameterSetName='Name',mandatory=$True)]
            [string]$ArrayName,

            [Parameter(Position=1,ParameterSetName='Product',mandatory=$True)]
            [ValidateSet('Purity//FA','Purity//FB','FlashArray','FlashBlade')]
            [string]$ArrayProduct,
            
            [Parameter(Position=2,ParameterSetName='ID',mandatory=$True)]
            [string]$ArrayId,

            [Parameter(Position=3)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
      if ($arrayProduct -ne "")
      {
          switch ($arrayProduct) {
              "FlashArray" {$arrayProduct = 'Purity//FA'; break}
              "FlashBlade" {$arrayProduct = 'Purity//FB'; break}
            }
      }
      if ($arrayName -ne "")
      {
          $restQuery = "?names=`'$($arrayName)`'"
      }
      if ($arrayProduct -ne "")
      {
          $restQuery = "?filter=os=`'$($arrayProduct)`'"
      }
      if ($arrayId -ne "")
      {
          $restQuery = "?ids=`'$($arrayId)`'"
      }
      $tokens = @()
      if ([string]::IsNullOrWhiteSpace($pureOneToken))
      {
        $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
      }
      else{
        $tokens += $pureOneToken
      }
      $pureArrays = @()
      foreach ($token in $tokens) {
        $pureArrays += New-PureOneOperation -resourceType arrays -queryFilter $restQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
      }
      if (($pureArrays | Measure-Object).Count -eq 0)
      {
        throw "No matching arrays were found on entered Pure1 organization(s)."
      }
      return $pureArrays    
}
function Get-PureOneArrayTag {
    <#
    .SYNOPSIS
      Gets a tag for a given array or arrays in Pure1
    .DESCRIPTION
      Gets a tag for a given array or arrays in Pure1
    .INPUTS
      Array name(s) or ID(s) and optionally a tag key name and/or an access token.
    .OUTPUTS
      Returns the Pure Storage array(s) key/value tag information in Pure1.
    .EXAMPLE
      PS C:\ Get-PureOneArrayTag
 
      Returns all tags
    .EXAMPLE
      PS C:\ Get-PureOneArrayTag -tagKey owner
 
      Returns all tags with the key of "owner"
    .EXAMPLE
      PS C:\ Get-PureOneArrayTag -arrayNames flasharray-m50-2
 
      Returns all matching tags on array with specified name
    .EXAMPLE
      PS C:\ Get-PureOneArrayTag -tagKey owner -arrayIds aad42743-611e-45ac-8b93-a869c4728a1d
 
      Returns matching tags with key of "owner" on array with specified ID
    .EXAMPLE
      PS C:\ Get-PureOneArrayTag -tagKey owner -arrayIds aad42743-611e-45ac-8b93-a869c4728a1d,e8998e19-aa08-45db-8bd0-4ea9171277a3
 
      Returns matching tags with key of "owner" on the arrays with specified IDs
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='TagKey')]
    Param(
            [Parameter(Position=0,ParameterSetName='ArrayNames')]
            [string[]]$ArrayNames,
         
            [Parameter(Position=1,ParameterSetName='ArrayIDs')]
            [string[]]$ArrayIds,

            [Parameter(Position=2,ParameterSetName='ArrayIDs')]
            [Parameter(Position=2,ParameterSetName='ArrayNames')]
            [Parameter(Position=2,ParameterSetName='TagKey')]
            [string]$TagKey,

            [Parameter(Position=3)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
      if ($arrayNames.count -gt 0)
      {
          $objectQuery = "resource_names="
          for ($i=0;$i -lt $arrayNames.count; $i++)
          {
              if ($i-eq 0)
              {
                  $objectQuery = $objectQuery + "`'$($arrayNames[$i])`'"
              }
              else {
                  $objectQuery = $objectQuery + ",`'$($arrayNames[$i])`'"
              }
          }
      }
      if ($arrayIds.Count -gt 0)
      {
          $objectQuery = "resource_ids="
          for ($i=0;$i -lt $arrayIds.count; $i++)
          {
              if ($i-eq 0)
              {
                  $objectQuery = $objectQuery + "`'$($arrayIds[$i])`'"
              }
              else {
                  $objectQuery = $objectQuery + ",`'$($arrayIds[$i])`'"
              }
          }
      }
      if ($tagKey -ne "")
      {
          $keyQuery = "?keys=`'$($tagKey)`'"
          if (($arrayNames.count -gt 0) -or ($arrayIds.count -gt 0))
          {
              $keyQuery = $keyQuery + "&"
          }
      }
      else
      {    
          $keyQuery = "?"
      }
      $tokens = @()
      if ([string]::IsNullOrWhiteSpace($pureOneToken))
      {
        $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
      }
      else{
        $tokens += $pureOneToken
      }
      $pureArrayTags = @()
      foreach ($token in $tokens) {
        $pureArrayTags += New-PureOneOperation -resourceType "arrays/tags" -queryFilter "$($keyQuery)$($objectQuery)" -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
      }
      return $pureArrayTags    
}
function Set-PureOneArrayTag {
    <#
    .SYNOPSIS
      Sets/updates a tag for a given array or arrays in Pure1
    .DESCRIPTION
      Sets/updates a tag for a given array or arrays in Pure1
    .INPUTS
      Array name(s) or ID(s) and a tag key name/value and/or optionally an access token.
    .OUTPUTS
      Returns the Pure Storage array(s) key/value tag information in Pure1.
    .EXAMPLE
      PS C:\ Set-PureOneArrayTag -tagKey owner -tagValue cody -arrayNames flasharray-m50-2
 
      Creates/updates tag on array with specified name
    .EXAMPLE
      PS C:\ Set-PureOneArrayTag -tagKey owner -tagValue cody -arrayNames flasharray-m50-2,flasharray-m50-1
 
      Creates/updates tag on specified arrays
    .EXAMPLE
      PS C:\ Set-PureOneArrayTag -tagKey owner -arrayIds aad42743-611e-45ac-8b93-a869c4728a1d
 
      Creates/updates tag on array with specified ID
    .EXAMPLE
      PS C:\ Set-PureOneArrayTag -tagKey owner -arrayIds aad42743-611e-45ac-8b93-a869c4728a1d,e8998e19-aa08-45db-8bd0-4ea9171277a3
 
      Creates/updates tag on the arrays with specified IDs
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='ArrayNames')]
    Param(
            [Parameter(Position=0,mandatory=$True,ParameterSetName='ArrayNames')]
            [string[]]$ArrayNames,
         
            [Parameter(Position=1,mandatory=$True,ParameterSetName='ArrayIDs')]
            [string[]]$ArrayIds,

            [Parameter(Position=2,mandatory=$True,ParameterSetName='ArrayIDs')]
            [Parameter(Position=2,mandatory=$True,ParameterSetName='ArrayNames')]
            [string]$TagKey,

            [Parameter(Position=3,mandatory=$True,ParameterSetName='ArrayIDs')]
            [Parameter(Position=3,mandatory=$True,ParameterSetName='ArrayNames')]
            [string]$TagValue,

            [Parameter(Position=4)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization

    )
    if ($arrayNames.count -gt 0)
    {
        $objectQuery = "?resource_names="
        for ($i=0;$i -lt $arrayNames.count; $i++)
        {
            if ($i-eq 0)
            {
                $objectQuery = $objectQuery + "`'$($arrayNames[$i])`'"
            }
            else {
                $objectQuery = $objectQuery + ",`'$($arrayNames[$i])`'"
            }
        }
    }
    if ($arrayIds.Count -gt 0)
    {
        $objectQuery = "?resource_ids="
        for ($i=0;$i -lt $arrayIds.count; $i++)
        {
            if ($i-eq 0)
            {
                $objectQuery = $objectQuery + "`'$($arrayIds[$i])`'"
            }
            else {
                $objectQuery = $objectQuery + ",`'$($arrayIds[$i])`'"
            }
        }
    }
    $newTag = @{
        key = ${tagKey}
        value = ${tagValue}
    }
    $newTagJson = $newTag |ConvertTo-Json
    $newTagJson = "[" + $newTagJson + "]"
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureArrayTags = @()
    foreach ($token in $tokens) {
      $pureArrayTags += New-PureOneOperation -resourceType "arrays/tags/batch" -queryFilter $objectQuery -pureOneToken $token -restOperationType PUT -jsonBody $newTagJson -ErrorAction SilentlyContinue
    }
    if (($pureArrayTags | Measure-Object).Count -eq 0)
    {
      throw "Tag not created. No matching arrays were found on entered Pure1 organization(s)."
    } 
    return $pureArrayTags        
}
function Remove-PureOneArrayTag {
    <#
    .SYNOPSIS
      Removes one or more tags for a given array or arrays in Pure1
    .DESCRIPTION
      Removes one or more tags for a given array or arrays in Pure1
    .INPUTS
      Array name(s) or ID(s) and one or more tag key names and/or optionally an access token. If you do not enter a key name, all tags for the input arrays will be removed.
    .OUTPUTS
      Returns nothing.
    .EXAMPLE
      PS C:\ Remove-PureOneArrayTag -tagKey owner -arrayNames flasharray-m50-2
 
      Removes all matching tags on array with specified name
    .EXAMPLE
      PS C:\ Remove-PureOneArrayTag -tagKey owner -arrayIds aad42743-611e-45ac-8b93-a869c4728a1d
 
      Removes matching tags with key of "owner" on array with specified ID
    .EXAMPLE
      PS C:\ Remove-PureOneArrayTag -tagKey owner -arrayIds aad42743-611e-45ac-8b93-a869c4728a1d,e8998e19-aa08-45db-8bd0-4ea9171277a3
 
      Removes matching tags with key of "owner" on the arrays with specified IDs
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='ArrayNames')]
    Param(
            [Parameter(Position=0,mandatory=$True,ParameterSetName='ArrayNames')]
            [string[]]$ArrayNames,
         
            [Parameter(Position=1,mandatory=$True,ParameterSetName='ArrayIDs')]
            [string[]]$ArrayIds,

            [Parameter(Position=2,mandatory=$True,ParameterSetName='ArrayIDs')]
            [Parameter(Position=2,mandatory=$True,ParameterSetName='ArrayNames')]
            [string[]]$TagKeys,

            [Parameter(Position=3)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
    if ($arrayNames.count -gt 0)
    {
        $objectQuery = "?resource_names="
        for ($i=0;$i -lt $arrayNames.count; $i++)
        {
            if ($i-eq 0)
            {
                $objectQuery = $objectQuery + "`'$($arrayNames[$i])`'"
            }
            else {
                $objectQuery = $objectQuery + ",`'$($arrayNames[$i])`'"
            }
        }
    }

    if ($arrayIds.Count -gt 0)
    {
        $objectQuery = "?resource_ids="
        for ($i=0;$i -lt $arrayIds.count; $i++)
        {
            if ($i-eq 0)
            {
                $objectQuery = $objectQuery + "`'$($arrayIds[$i])`'"
            }
            else {
                $objectQuery = $objectQuery + ",`'$($arrayIds[$i])`'"
            }
        }
    }
    if ($tagKeys.Count -gt 0)
    {
        $objectQuery = $objectQuery + "&keys="
        for ($i=0;$i -lt $tagKeys.count; $i++)
        {
            if ($i -eq 0)
            {
                $objectQuery = $objectQuery + "`'$($tagKeys[$i])`'"
            }
            else {
                $objectQuery = $objectQuery + ",`'$($tagKeys[$i])`'"
            }
        }
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureArrayTags = @()
    foreach ($token in $tokens) {
      $pureArrayTags += New-PureOneOperation -resourceType "arrays/tags" -queryFilter $objectQuery -pureOneToken $token -restOperationType DELETE -ErrorAction SilentlyContinue
    }
    if (($pureArrayTags | Measure-Object).Count -eq 0)
    {
      throw "No matching arrays were found on entered Pure1 organization(s)."
    }
    return $pureArrayTags   
}
function Get-PureOneArrayNetworking {
    <#
    .SYNOPSIS
      Returns the networking information for a given array in Pure1
    .DESCRIPTION
      Returns the the networking information for a given array in Pure1
    .INPUTS
      Array name or ID and optionally access token.
    .OUTPUTS
      Returns the Pure Storage array network information in all connected Pure1 organizations
    .EXAMPLE
      PS C:\ Get-PureOneArrayNetworking -arrayName sn1-m20-c08-17
 
      Returns the networking information for all network interfaces
    .EXAMPLE
      PS C:\ Get-PureOneArrayNetworking -arrayName sn1-m20-c08-17 -virtualIP
 
      Returns the networking information for virtual IP interfaces
    .EXAMPLE
      PS C:\ Get-PureOneArrayNetworking -arrayName sn1-m20-c08-17 -service iscsi
 
      Returns the networking information for iscsi interfaces
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='ArrayName')]
    Param(
            [Parameter(Position=0,mandatory=$True,ParameterSetName='ArrayName')]
            [string]$ArrayName,
         
            [Parameter(Position=1,mandatory=$True,ParameterSetName='ArrayID')]
            [string]$ArrayId,

            [Parameter(Position=2,ParameterSetName='ArrayID')]
            [Parameter(Position=2,ParameterSetName='ArrayName')]
            [Switch]$VirtualIP,

            [Parameter(Position=3,ParameterSetName='ArrayID')]
            [Parameter(Position=3,ParameterSetName='ArrayName')]
            [string]$Service,

            [Parameter(Position=4)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
      if (($virtualIP -eq $true) -and (($service -ne "management") -and ($service -ne "") ))
      {
          throw "Virtual IPs are only management-based services, so you cannot request virtual IPs with $($service) as the service"
      }
      $objectQuery = "?"
      if ($virtualIP -eq $true)
      {
          $objectQuery = $objectQuery + "names=`'vir1`',`'vir0`'&"
      }
      if ($arrayName -ne "")
      {
          #URL encoding the square brackets as some network do not pass them properly
          $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayName)`'"
      }
      if ($arrayId -ne "")
      {
          $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayId)`'"
      }
      if ($service -ne "")
      {
          $objectQuery = $objectQuery + ([System.Web.HttpUtility]::Urlencode(" and services[any]")) + "=`'$($service)`'"
      }
      $tokens = @()
      if ([string]::IsNullOrWhiteSpace($pureOneToken))
      {
        $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
      }
      else{
        $tokens += $pureOneToken
      }
      $pureArrayNetwork = @()
      foreach ($token in $tokens) {
        $pureArrayNetwork += New-PureOneOperation -resourceType "network-interfaces" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET  -ErrorAction SilentlyContinue
      }
      if (($pureArrayNetwork | Measure-Object).Count -eq 0)
      {
        throw "No networking information found. The specificied service: [$($service)] might not exist on this array or it might be misspelled"
      }
      return $pureArrayNetwork
}
function Get-PureOneMetricDetail {
    <#
    .SYNOPSIS
      Returns the available metrics in Pure1
    .DESCRIPTION
      Returns the available metrics in Pure1 and their specifics
    .INPUTS
      Resource type or metric name and/or access token.
    .OUTPUTS
      Returns the Pure Storage metric details
    .EXAMPLE
      PS C:\ Get-PureOneMetricDetail
 
      Returns the details for all available metrics
    .EXAMPLE
      PS C:\ Get-PureOneMetricDetail -resourceType volumes
 
      Returns the details for all available volume-based metrics
    .EXAMPLE
      PS C:\ Get-PureOneMetricDetail -metricName pod_write_qos_rate_limit_time_us
 
      Returns the details for the metric named pod_write_qos_rate_limit_time_us
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='ResourceType')]
    Param(
            [Parameter(Position=0,ParameterSetName='MetricName')]
            [string]$MetricName,
         
            [Parameter(Position=1,ParameterSetName='ResourceType')]
            [string]$ResourceType,

            [Parameter(Position=2)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization]$PureOneOrganization
    )
        $objectQuery = "?"
        if ($resourceType -ne "")
        {
            $objectQuery = $objectQuery + "resource_types=`'$($resourceType)`'&"
        }
        if ($metricName -ne "")
        {
            $objectQuery = $objectQuery +"names=`'$($metricName)`'"
        }
        $tokens = @()
        if ([string]::IsNullOrWhiteSpace($pureOneToken))
        {
          $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
        }
        else{
          $tokens += $pureOneToken
        }
        $pureOneMetrics = @()
        $pureOneMetrics += New-PureOneOperation -resourceType "metrics" -queryFilter $objectQuery -pureOneToken $tokens[0] -restOperationType GET -ErrorAction SilentlyContinue
        return $pureOneMetrics  
}
function Get-PureOneMetric {
    <#
    .SYNOPSIS
      Returns the metrics for a given array in Pure1
    .DESCRIPTION
      Returns the metrics for a given array in Pure1, either an average or a maximum of a given time period. Default behavior is to return the average.
    .INPUTS
      Required: resource name or ID and metric name. Optional: timeframe, granularity, and aggregation type (if none entered defaults will be used based on metric entered). Also optionally an access token.
    .OUTPUTS
      Returns the Pure Storage array information in Pure1.
    .EXAMPLE
      PS C:\ Get-PureOneMetric -metricName array_read_iops -objectName sn1-x70-c05-33
 
      Returns all data points available for the specified metric on the target object (in this case read IOPs for the array)
    .EXAMPLE
      PS C:\ Get-PureOneMetric -metricName array_read_iops -objectName sn1-x70-c05-33 -maximum
 
      Returns all maximum data points (no average taken, the highest value instead is used) available for the specified metric on the target object (in this case read IOPs for the array)
    .EXAMPLE
      PS C:\ Get-PureOneMetric -metricName array_read_iops -objectName sn1-x70-c05-33 -startTime (get-date).AddDays(-10)
 
      Returns all data points for the last 10 days for the specified metric on the target object (in this case read IOPs for the array)
    .EXAMPLE
      PS C:\ Get-PureOneMetric -metricName array_read_iops -objectName sn1-x70-c05-33 -startTime (get-date).AddDays(-7) -endTime (get-date).AddDays(-6)
 
      Returns all data points for the the one day a week prior for the specified metric on the target object (in this case read IOPs for the array)
    .EXAMPLE
      PS C:\ Get-PureOneMetric -metricName array_read_iops -objectName sn1-x70-c05-33 -startTime (get-date).AddDays(-7) -endTime (get-date).AddDays(-6) -granularity 3600000 -maximum
 
      Returns the highest valued data point per hour (every 3,600,000 milliseconds) for the the one day a week prior for the specified metric on the target object (in this case read IOPs for the array)
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>

    [CmdletBinding(DefaultParameterSetName='ObjectNameAvg')]
    Param(
            [Parameter(Position=0,mandatory=$True,ParameterSetName='ObjectNameAvg')]
            [Parameter(Position=0,mandatory=$True,ParameterSetName='ObjectNameMax')]
            [string]$ObjectName,
        
            [Parameter(Position=1,mandatory=$True,ParameterSetName='ObjectIDAvg')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='ObjectIDMax')]
            [string]$ObjectId,

            [Parameter(Position=2,ParameterSetName='ObjectIDAvg')]
            [Parameter(Position=2,ParameterSetName='ObjectNameAvg')]
            [switch]$Average,

            [Parameter(Position=3,ParameterSetName='ObjectIDMax')]
            [Parameter(Position=3,ParameterSetName='ObjectNameMax')]
            [switch]$Maximum,

            [Parameter(Position=4,mandatory=$True)]
            [string]$MetricName,

            [Parameter(Position=5)]
            [System.DateTime]$StartTime,

            [Parameter(Position=6)]
            [System.DateTime]$EndTime,

            [Parameter(Position=7)]
            [Int64]$Granularity,

            [Parameter(Position=8)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
      if (($average -eq $false) -and ($maximum -eq $false)) 
      {
          #defaulting to average if neither option is entered
          $average = $true
      }
      #get metric rules
      $metricDetails = Get-PureOneMetricDetail -metricName $metricName
      #set granularity if not set
      if ($granularity -eq 0)
      {
          $granularity = $metricDetails.availabilities.resolution
      }

      #set end time to start time minus retention for that stat (if not entered) and convert to epoch time
      if ($null -eq $endTime)
      {
          $endTime = Get-Date
          $endTime = $endTime.ToUniversalTime()
      }
      else {
          $endTime = $endTime.ToUniversalTime()
      }
      [datetime]$epoch = '1970-01-01 00:00:00'
      $endEpoch = (New-TimeSpan -Start $epoch -End $endTime).TotalMilliSeconds
      $endEpoch = [math]::Round($endEpoch)

      #set start time to current time (if not entered) and convert to epoch time
      if ($null -eq $startTime)
      {
          $startTime = $epoch.AddMilliseconds($metricDetails._as_of - $metricDetails.availabilities.retention)
      }
      else {
          $startTime = $startTime.ToUniversalTime()
      }
      $startEpoch = (New-TimeSpan -Start $epoch -End $startTime).TotalMilliSeconds
      $startEpoch = [math]::Round($startEpoch)

      #building query
      if ($average -eq $true)
      {
          $objectQuery = "?aggregation='avg'&end_time=$($endEpoch)&names=`'$($metricName)`'&resolution=$($granularity)&start_time=$($startEpoch)&"
      }
      else {
          $objectQuery = "?aggregation='max'&end_time=$($endEpoch)&names=`'$($metricName)`'&resolution=$($granularity)&start_time=$($startEpoch)&"
      }
      if ($objectName -ne "")
      {
          $objectQuery = $objectQuery + "resource_names=`'$($objectName)`'"
      }
      else {
          $objectQuery = $objectQuery + "ids=`'$($objectId)`'"
      }
      $tokens = @()
      if ([string]::IsNullOrWhiteSpace($pureOneToken))
      {
        $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
      }
      else{
        $tokens += $pureOneToken
      }
      $pureOneMetrics = @()
      foreach ($token in $tokens) {
        $pureOneMetrics += New-PureOneOperation -resourceType "metrics/history" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
      }
      if (($pureOneMetrics | Measure-Object).Count -eq 0)
      {
        throw "No matching arrays were found on entered Pure1 organization(s)."
      }
      return $pureOneMetrics  
}
function Get-PureOneVolume {
    <#
    .SYNOPSIS
      Returns all Pure Storage volumes listed in your Pure1 account.
    .DESCRIPTION
      Returns all Pure Storage volumes listed in your Pure1 account. Allows for some filters.
    .INPUTS
      None required. Optional inputs are array type, array name, and Pure1 access token.
    .OUTPUTS
      Returns the Pure Storage array information in Pure1.
    .EXAMPLE
      PS C:\ Get-PureOneVolume
 
      Get all volumes on all FlashArrays in all connected Pure1 organizations.
    .EXAMPLE
      PS C:\ Get-PureOneVolume -arrayName sn1-x70-b05-33
 
      Get all volumes on specified FlashArray.
    .EXAMPLE
      PS C:\ Get-PureOneVolume -volumeName myVolume-01
 
      Get all volumes with the specified name. If the same name exists on two more more arrays, all objects will be returned.
    .EXAMPLE
      PS C:\ Get-PureOneVolume -volumeName myVolume-01 -arrayName sn1-x70-b05-33
 
      Get the volume with the specified name if it exists on that array.
    .EXAMPLE
      PS C:\ Get-PureOneVolume -volumeSerial 1037B35FD0EF40A500C65559
 
      Get the volume with the specified serial number.
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='ArrayNameVolName')]
    Param(
            [Parameter(Position=0,ParameterSetName='ArrayNameVolName')]
            [Parameter(Position=0,ParameterSetName='ArrayNameVolSerial')]
            [string]$ArrayName,
            
            [Parameter(Position=1,ParameterSetName='ArrayIDVolName')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='ArrayIDVolSerial')]
            [string]$ArrayId,

            [Parameter(Position=2,ParameterSetName='ArrayIDVolName')]
            [Parameter(Position=2,ParameterSetName='ArrayNameVolName')]
            [string]$VolumeName,

            [Parameter(Position=3,ParameterSetName='ArrayIDVolSerial')]
            [Parameter(Position=3,ParameterSetName='ArrayNameVolSerial')]
            [string]$VolumeSerial,

            [Parameter(Position=4)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
      if ($null -ne $global:pureOneRateLimit)
      {
        if ($Global:pureOneRateLimit -in 1..1000)
        {
          $objectQuery = "?limit=$($global:pureOneRateLimit)&"
        }
        else {
          throw "Pure1 Rate limit set to invalid amount. Must be between 1-1000. Currently set to $($global:pureOneRateLimit)"
        }
      }
      else {
        $objectQuery = "?"
      }
      if ($volumeName -ne "")
      {
          $objectQuery = $objectQuery + "names=`'$($volumeName)`'"
          if (($arrayName -ne "") -or ($arrayId -ne ""))
          {
              $objectQuery = $objectQuery + "&"
          }
      }
      elseif ($volumeSerial -ne "")
      {
          $volumeSerial = $volumeSerial.ToUpper()
          $objectQuery = $objectQuery +"filter=serial=`'$($volumeSerial)`'"
          if ($arrayName -ne "")
          {
              $objectQuery = $objectQuery + ([System.Web.HttpUtility]::Urlencode(" and arrays[any].name")) + "=`'$($arrayName)`'"
          }
          if ($arrayId -ne "")
          {
              $objectQuery = $objectQuery + ([System.Web.HttpUtility]::Urlencode(" and arrays[any].id")) + "=`'$($arrayId)`'"
          }
      }
      if ($volumeSerial -eq "")
      {
          if ($arrayName -ne "")
          {
              $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayName)`'"
          }
          if ($arrayId -ne "")
          {
              $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].id")) + "=`'$($arrayId)`'"
          }
      }
      $tokens = @()
      if ([string]::IsNullOrWhiteSpace($pureOneToken))
      {
        $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
      }
      else{
        $tokens += $pureOneToken
      }
      $pureVolumes = @()
      foreach ($token in $tokens) {
        $pureVolumes += New-PureOneOperation -resourceType "volumes" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
      }
      if (($pureVolumes | Measure-Object).Count -eq 0)
      {
        throw "No matching volumes were found on entered Pure1 organization(s)."
      }
      return $pureVolumes
}
function Get-PureOnePod {
    <#
    .SYNOPSIS
      Returns all Pure Storage pods listed in your Pure1 account.
    .DESCRIPTION
      Returns all Pure Storage pods listed in your Pure1 account. Allows for some filters.
    .INPUTS
      None required. Optional inputs are pod name, array name or ID, and Pure1 access token.
    .OUTPUTS
      Returns the Pure Storage pod information in all connected Pure1 organizations.
    .EXAMPLE
      PS C:\ Get-PureOnePod
 
      Returns all pods on all FlashArrays in all connected Pure1 organizations
    .EXAMPLE
      PS C:\ Get-PureOnePod -arrayId 2dcf29ad-6aca-4913-b62e-a15875c6635f
 
      Returns all pods on FlashArray with specified ID
    .EXAMPLE
      PS C:\ Get-PureOnePod -podName newpod
 
      Returns all pods with the specified name
    .EXAMPLE
      PS C:\ Get-PureOnePod -podName newpod -arrayName sn1-m20-c12-25
 
      Returns the pod with the specified name on the specified FlashArray
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='Pod')]
    Param(
            [Parameter(Position=0,ParameterSetName='ArrayName')]
            [Parameter(Position=0,ParameterSetName='Pod')]
            [string]$ArrayName,
            
            [Parameter(Position=1,ParameterSetName='ArrayId')]
            [Parameter(Position=1,ParameterSetName='Pod')]
            [string]$ArrayId,

            [Parameter(Position=2,ParameterSetName='Pod')]
            [string]$PodName,

            [Parameter(Position=3)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
        $objectQuery = "?"
        if ($podName -ne "")
        {
            $objectQuery = $objectQuery + "names=`'$($podName)`'"
            if (($arrayName -ne "") -or ($arrayId -ne ""))
            {
                $objectQuery = $objectQuery + "&"
            }
        }
        if ($arrayName -ne "")
        {
            $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayName)`'"
        }
        if ($arrayId -ne "")
        {
            $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].id")) + "=`'$($arrayId)`'"
        }
        $tokens = @()
        if ([string]::IsNullOrWhiteSpace($pureOneToken))
        {
          $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
        }
        else{
          $tokens += $pureOneToken
        }
        $purePods = @()
        foreach ($token in $tokens) {
          $purePods += New-PureOneOperation -resourceType "pods" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET-ErrorAction SilentlyContinue
        }
        if (($purePods | Measure-Object).Count -eq 0)
        {
          throw "No matching pods were found on entered Pure1 organization(s)."
        }
        return $purePods
}
function Get-PureOneVolumeSnapshot {
    <#
    .SYNOPSIS
      Returns all Pure Storage volume snapshots listed in all connected Pure1 organizations.
    .DESCRIPTION
      Returns all Pure Storage volume snapshots listed in all connected Pure1 organizations. Allows for some filters.
    .INPUTS
      None required. Optional inputs are array type, array name, volume name, snapshot name or snapshot serial, or Pure1 access token.
    .OUTPUTS
      Returns the Pure Storage array information in all connected Pure1 organizations.
    .EXAMPLE
      PS C:\ Get-PureOneVolumeSnapshot
 
      Returns all snapshots on all FlashArrays
    .EXAMPLE
      PS C:\ Get-PureOneVolumeSnapshot -arrayName flasharray-m50-2
 
      Returns all snapshots on the specified array
    .EXAMPLE
      PS C:\ Get-PureOneVolumeSnapshot -snapshotName db-001.test
 
      Returns the snapshots with the specified name
    .EXAMPLE
      PS C:\ Get-PureOneVolumeSnapshot -volumeName sql00-Backup02
 
      Returns all snapshots for the specified volume
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='SnapshotName')]
    Param(
            [Parameter(Position=0,ParameterSetName='ArrayName')]
            [string]$ArrayName,
            
            [Parameter(Position=1,ParameterSetName='ArrayID')]
            [string]$ArrayId,

            [Parameter(Position=2,ParameterSetName='SnapshotName')]
            [string]$SnapshotName,

            [Parameter(Position=3,ParameterSetName='SnapshotSerial')]
            [string]$SnapshotSerial,

            [Parameter(Position=4,ParameterSetName='VolumeName')]
            [string]$VolumeName,

            [Parameter(Position=5)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
    if ($null -ne $global:pureOneRateLimit)
    {
      if ($Global:pureOneRateLimit -in 1..1000)
      {
        $objectQuery = "?limit=$($global:pureOneRateLimit)&"
      }
      else {
        throw "Pure1 Rate limit set to invalid amount. Must be between 1-1000. Currently set to $($global:pureOneRateLimit)"
      }
    }
    else {
      $objectQuery = "?limit=200"
    }
      if ($snapshotName -ne "")
      {
          $objectQuery = $objectQuery + "&names=`'$($snapshotName)`'"
      }
      elseif ($snapshotSerial -ne "")
      {
          $snapshotSerial = $snapshotSerial.ToUpper()
          $objectQuery = $objectQuery +"&filter=serial=`'$($snapshotSerial)`'"
          if ($arrayName -ne "")
          {
              $objectQuery = $objectQuery + ([System.Web.HttpUtility]::Urlencode(" and arrays[any].name")) + "=`'$($arrayName)`'"
          }
          if ($arrayId -ne "")
          {
              $objectQuery = $objectQuery + ([System.Web.HttpUtility]::Urlencode(" and arrays[any].id")) + "=`'$($arrayId)`'"
          }
      }
      if ($snapshotSerial -eq "")
      {
          if ($arrayName -ne "")
          {
              $objectQuery = $objectQuery + "&filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayName)`'"
          }
          if ($arrayId -ne "")
          {
              $objectQuery = $objectQuery + "&filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].id")) + "=`'$($arrayId)`'"
          }
      }
      if ($volumeName -ne "")
      {
          $objectQuery = $objectQuery + "&filter=" + ([System.Web.HttpUtility]::Urlencode("source.name")) + "=`'$($volumeName)`'"
      }
      $tokens = @()
      if ([string]::IsNullOrWhiteSpace($pureOneToken))
      {
        $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
      }
      else{
        $tokens += $pureOneToken
      }
      $pureSnaps = @()
      foreach ($token in $tokens) {
        $pureSnaps += New-PureOneOperation -resourceType "volume-snapshots" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
      }
      if (($pureSnaps | Measure-Object).Count -eq 0)
      {
        throw "No matching snapshots were found on entered Pure1 organization(s)."
      }
      return $pureSnaps
}
function Get-PureOneFileSystem {
    <#
    .SYNOPSIS
      Returns all Pure Storage file systems listed in all connected Pure1 organizations.
    .DESCRIPTION
      Returns all Pure Storage file systems listed in all connected Pure1 organizations. Allows for some filters.
    .INPUTS
      None required. Optional inputs are array type, array name, file system name, or Pure1 access token.
    .OUTPUTS
      Returns the Pure Storage array information in all connected Pure1 organizations.
    .EXAMPLE
      PS C:\ Get-PureOneFileSystem
       
      Return all FlashBlade file systems (NFS, SMB, S3)
    .EXAMPLE
      PS C:\ Get-PureOneFileSystem
       
      Return all FlashBlade file systems (NFS, SMB, S3)
    .EXAMPLE
      PS C:\ Get-PureOneFileSystem -fsName fs20
       
      Return the specified FlashBlade file system (NFS, SMB, S3)
    .EXAMPLE
      PS C:\ Get-PureOneFileSystem -arrayName sn1-fb-c02-33
       
      Return all FlashBlade file systems on specified array (NFS, SMB, S3)
    .EXAMPLE
      PS C:\ Get-PureOneFileSystem -arrayId 0e30e967-d749-4e03-9d32-701eeff14376
       
      Return all FlashBlade file systems on specified array(NFS, SMB, S3)
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
 
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='FileSystem')]
    Param(
            [Parameter(Position=0,ParameterSetName='ArrayName')]
            [string]$ArrayName,
            
            [Parameter(Position=1,ParameterSetName='ArrayID')]
            [string]$ArrayId,

            [Parameter(Position=2,ParameterSetName='FileSystem')]
            [string]$FsName,

            [Parameter(Position=3)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
    $objectQuery = "?"
    if ($fsName -ne "")
    {
        $restQuery = $restQuery + "names=`'$($fsName)`'"
        if (($arrayName -ne "") -or ($arrayId -ne ""))
        {
            $objectQuery = $objectQuery + "&"
        }
    }
    if ($arrayName -ne "")
    {
        $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayName)`'"
    }
    if ($arrayId -ne "")
    {
        $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].id")) + "=`'$($arrayId)`'"
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureFilesystems = @()
    foreach ($token in $tokens) {
      $pureFilesystems += New-PureOneOperation -resourceType "file-systems" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
    }
    if (($pureFilesystems | Measure-Object).Count -eq 0)
    {
      throw "No matching arrays were found on entered Pure1 organization(s)."
    }
    return $pureFilesystems
}
function Get-PureOneFileSystemSnapshot {
    <#
    .SYNOPSIS
      Returns all Pure Storage file system snapshots listed in all connected Pure1 organizations.
    .DESCRIPTION
      Returns all Pure Storage file system snapshots listed in all connected Pure1 organizations. Allows for some filters.
    .INPUTS
      None required. Optional inputs are array name, file system name, snapshot name, or Pure1 access token.
    .OUTPUTS
      Returns the Pure Storage file system(s) information in all connected Pure1 organizations.
    .EXAMPLE
      PS C:\ Get-PureOneFileSystemSnapshot
 
      Returns all file system snapshots on all FlashBlades
    .EXAMPLE
      PS C:\ Get-PureOneFileSystemSnapshot -arrayName sn1-fb-c02-33
 
      Returns all file system snapshots on specified FlashBlade
    .EXAMPLE
      PS C:\ Get-PureOneFileSystemSnapshot -snapshotName nbu-msdp-metadata.2020_04_30_00_00
 
      Returns the specified file system snapshot
    .EXAMPLE
      PS C:\ Get-PureOneFileSystemSnapshot -fsName nbu-msdp-metadata
 
      Returns all snapshots for the specified file system
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>


    [CmdletBinding(DefaultParameterSetName='FileSystemName')]
    Param(
            [Parameter(Position=0,ParameterSetName='ArrayName')]
            [string]$ArrayName,
            
            [Parameter(Position=1,ParameterSetName='ArrayID')]
            [string]$ArrayId,

            [Parameter(Position=2,ParameterSetName='SnapshotName')]
            [string]$SnapshotName,

            [Parameter(Position=3,ParameterSetName='FileSystemName')]
            [string]$FsName,

            [Parameter(Position=4)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
    $objectQuery = "?"
    if ($snapshotName -ne "")
    {
        $objectQuery = $objectQuery + "names=`'$($snapshotName)`'"
        if (($arrayName -ne "") -or ($arrayId -ne ""))
        {
            $objectQuery = $objectQuery + "&"
        }
    }
    if ($arrayName -ne "")
    {
        $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].name")) + "=`'$($arrayName)`'"
    }
    if ($arrayId -ne "")
    {
        $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("arrays[any].id")) + "=`'$($arrayId)`'"
    }
    if ($fsName -ne "")
    {
        $objectQuery = $objectQuery + "filter=" + ([System.Web.HttpUtility]::Urlencode("source.name")) + "=`'$($fsName)`'"
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $purefsSnapshots = @()
    foreach ($token in $tokens) {
      $purefsSnapshots += New-PureOneOperation -resourceType "file-system-snapshots" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
    }
    if (($purefsSnapshots | Measure-Object).Count -eq 0)
    {
      throw "No matching file system snapshots were found on entered Pure1 organization(s)."
    }
    return $purefsSnapshots
}
function Get-PureOneArrayLoadMeter {
    <#
    .SYNOPSIS
      Returns the busy meter for a given array in all connected Pure1 organizations
    .DESCRIPTION
      Returns the busy meter for a given array (or arrays) in all connected Pure1 organizations, either an average or a maximum of a given time period. Default behavior is to return the average.
    .INPUTS
      Required: resource names or IDs--must be an array. Optional: timeframe, granularity, and aggregation type (if none entered defaults will be used based on metric entered). Also optionally an access token.
    .OUTPUTS
      Returns the Pure Storage busy meter metric information in all connected Pure1 organizations.
    .EXAMPLE
      PS C:\ Get-PureOneArrayBusyMeter -objectName flasharray-m50-1
 
      Returns the busy meter at default resolution for all available time, for the specified array.
    .EXAMPLE
      PS C:\ Get-PureOneArrayBusyMeter -objectName flasharray-m50-1 -startTime (get-date).AddDays(-10)
 
      Returns the busy meter at default resolution for the past ten days, for the specified array.
    .EXAMPLE
      PS C:\ Get-PureOneArrayBusyMeter -objectName flasharray-m50-1 -startTime (get-date).AddDays(-2) -endTime (get-date).AddDays(-1)
 
      Returns the busy meter at default resolution for one day ending 24 hours ago, for the specified array.
    .EXAMPLE
      PS C:\ Get-PureOneArrayBusyMeter -objectName flasharray-m50-1 -startTime (get-date).AddDays(-1) -startTime (get-date).AddDays(-1) -granularity 86400000 -maximum
 
      Returns one value for the previous 24 hours, representing the maximum busyness value for the specified array in that window.
    .NOTES
      Version: 1.1
      Author: Cody Hosterman https://codyhosterman.com
      Creation Date: 08/29/2020
      Purpose/Change: Core support
   
    *******Disclaimer:******************************************************
    This scripts are offered "as is" with no warranty. While this
    scripts is tested and working in my environment, it is recommended that you test
    this script in a test lab before using in a production environment. Everyone can
    use the scripts/commands provided here without any written permission but I
    will not be liable for any damage or loss to the system.
    ************************************************************************
    #>

    [CmdletBinding(DefaultParameterSetName='objectName')]
    Param(
            [Parameter(Position=0,mandatory=$True,ParameterSetName='objectName')]
            [Parameter(Position=0,mandatory=$True,ParameterSetName='objectNameAVG')]
            [Parameter(Position=0,mandatory=$True,ParameterSetName='objectNameMAX')]
            [string[]]$ObjectName,
         
            [Parameter(Position=1,mandatory=$True,ParameterSetName='objectID')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='objectIDAVG')]
            [Parameter(Position=1,mandatory=$True,ParameterSetName='objectIDMAX')]
            [string[]]$ObjectId,

            [Parameter(Position=2,mandatory=$True,ParameterSetName='objectNameAVG')]
            [Parameter(Position=2,mandatory=$True,ParameterSetName='objectIDAVG')]
            [switch]$Average,

            [Parameter(Position=2,mandatory=$True,ParameterSetName='objectNameMAX')]
            [Parameter(Position=2,mandatory=$True,ParameterSetName='objectIDMAX')]
            [switch]$Maximum,

            [Parameter(Position=5)]
            [System.DateTime]$StartTime,

            [Parameter(Position=6)]
            [System.DateTime]$EndTime,

            [Parameter(Position=7)]
            [Int64]$Granularity,

            [Parameter(Position=8)]
            [string]$PureOneToken,

            [Parameter(Position=4)]
            [PureOneOrganization[]]$PureOneOrganization
    )
      $metricName = "array_total_load"
      if (($average -eq $false) -and ($maximum -eq $false)) 
      {
          #defaulting to average if neither option is entered
          $average = $true
      }
      if (($null -ne $startTime) -and ($null -ne $endTime))
      {
        if ($startTime -ge $endTime)
        {
          throw "The specified start time $($startTime) cannot be the same or later than the specified end time $($endTime)"
        }
      }
      #get metric rules
      $metricDetails = Get-PureOneMetricDetail -metricName $metricName
      #set granularity if not set
      if ($granularity -eq 0)
      {
          $granularity = $metricDetails.availabilities.resolution
      }

      #set end time to start time minus retention for that stat (if not entered) and convert to epoch time
      if ($endTime -eq $null)
      {
          $endTime = Get-Date
          $endTime = $endTime.ToUniversalTime()
      }
      else {
          $endTime = $endTime.ToUniversalTime()
      }
      [datetime]$epoch = '1970-01-01 00:00:00'
      $endEpoch = (New-TimeSpan -Start $epoch -End $endTime).TotalMilliSeconds
      $endEpoch = [math]::Round($endEpoch)

      #set start time to current time (if not entered) and convert to epoch time
      if ($startTime -eq $null)
      {
          $startTime = $epoch.AddMilliseconds($metricDetails._as_of - $metricDetails.availabilities.retention)
      }
      else {
          $startTime = $startTime.ToUniversalTime()
      }
      $startEpoch = (New-TimeSpan -Start $epoch -End $startTime).TotalMilliSeconds
      $startEpoch = [math]::Round($startEpoch)

      #building query
      if ($average -eq $true)
      {
          $objectQuery = "?aggregation='avg'&end_time=$($endEpoch)&names=`'$($metricName)`'&resolution=$($granularity)&start_time=$($startEpoch)&"
      }
      else {
          $objectQuery = "?aggregation='max'&end_time=$($endEpoch)&names=`'$($metricName)`'&resolution=$($granularity)&start_time=$($startEpoch)&"
      }
      if ($objectName -ne "")
      {
          if ($objectName.count -gt 1)
          {
              foreach ($arrayName in $objectName)
              {
                  $pureArrays = $pureArrays + "`'$($arrayName)`'"
                  if ($arrayName -ne ($objectName |Select-Object -Last 1))
                  {
                      $pureArrays = $pureArrays + ","
                  }
              }
              $objectQuery = $objectQuery + "resource_names=" + $pureArrays
          }
          else {
              $objectQuery = $objectQuery + "resource_names=`'$($objectName)`'"
          }
      }
      else {
          if ($objectId.count -gt 1)
          {
              foreach ($arrayName in $objectId)
              {
                  $pureArrays = $pureArrays + "`'$($arrayName)`'"
                  if ($arrayName -ne ($objectId |Select-Object -Last 1))
                  {
                      $pureArrays = $pureArrays + ","
                  }
              }
              $objectQuery = $objectQuery + "resource_ids=" + $pureArrays
          }
          else {
              $objectQuery = $objectQuery + "resource_ids=`'$($objectId)`'"
          }
      }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $loadMeters = @()
    foreach ($token in $tokens) {
      $loadMeters += New-PureOneOperation -resourceType "metrics/history" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET  -ErrorAction SilentlyContinue
    }
    if (($loadMeters | Measure-Object).Count -eq 0)
    {
      throw "No matching arrays were found on entered Pure1 organization(s)."
    }
    return $loadMeters
}
function Get-PureOneLicense {
  <#
  .SYNOPSIS
    Returns all Pure Storage licenses.
  .DESCRIPTION
    Returns all or specified Pure Storage licenses.
  .INPUTS
    None required. Optional inputs are subscription or license information
  .OUTPUTS
    Returns the Pure Storage licenses information in Pure1.
  .EXAMPLE
    PS C:\ Get-PureOneLicense
 
    Returns all licenses
  .EXAMPLE
    PS C:\ Get-PureOneLicense -Name mytestlicense
 
    Returns the license with the specified name.
  .EXAMPLE
    PS C:\ Get-PureOneLicense -ArrayName myFlashArray
 
    Returns the license for the specified array name.
  .EXAMPLE
    PS C:\ Get-PureOneLicense -ArrayId f5b5f364-c644-441d-adab-5ab894924255
 
    Returns the license for the specified array ID.
  .EXAMPLE
    PS C:\ Get-PureOneLicense -SubscriptionName SC-9999990
 
    Returns all licenses under the specified subscription name
  .EXAMPLE
    PS C:\ Get-PureOneLicense -SubscriptionId 4844ba62-6e15-4d6f-8e51-40257c28dab1
 
    Returns all licenses under the specified subscription ID
  .EXAMPLE
    PS C:\ Get-PureOneLicense -ServiceTierType Block
 
    Returns all licenses for the block storage.
  .EXAMPLE
    PS C:\ Get-PureOneLicense -ServiceTierLevel Performance
 
    Returns all licenses for the performance tier
  .NOTES
    Version: 1.0
    Author: Cody Hosterman https://codyhosterman.com
    Creation Date: 01/26/2021
    Purpose/Change: Initial release
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding()]
  Param(
          [Parameter(Position=0)]
          [string]$Id,
          
          [Parameter(Position=1)]
          [ValidateSet("Block","UFFO")]
          [string]$ServiceTierType,

          [Parameter(Position=2)]
          [ValidateSet("Ultra","Premium","Performance","Capacity")]
          [string]$ServiceTierLevel,

          [Parameter(Position=3)]
          [string]$Name,

          [Parameter(Position=4)]
          [string]$SubscriptionId,

          [Parameter(Position=5)]
          [string]$SubscriptionName,

          [Parameter(Position=6)]
          [string]$ArrayId,

          [Parameter(Position=7)]
          [string]$ArrayName,

          [Parameter(Position=8)]
          [string]$PureOneToken,

          [Parameter(Position=9)]
          [PureOneOrganization[]]$PureOneOrganization
  )
    if ((![string]::IsNullOrWhiteSpace($Name)) -and (![string]::IsNullOrWhiteSpace($Id)))
    {
      throw "Please only pass in a license name or an ID. Not both."
    }
    if ((![string]::IsNullOrWhiteSpace($SubscriptionName)) -and (![string]::IsNullOrWhiteSpace($SubscriptionId)))
    {
      throw "Please only pass in a subscription name or an ID. Not both."
    }
    if ((![string]::IsNullOrWhiteSpace($ArrayName)) -and (![string]::IsNullOrWhiteSpace($ArrayId)))
    {
      throw "Please only pass in a array name or an ID. Not both."
    }
    if ($null -ne $global:pureOneRateLimit)
    {
      if ($Global:pureOneRateLimit -in 1..1000)
      {
        $objectQuery = "?limit=$($global:pureOneRateLimit)&"
      }
      else {
        throw "Pure1 Rate limit set to invalid amount. Must be between 1-1000. Currently set to $($global:pureOneRateLimit)"
      }
    }
    else {
      $objectQuery = "?"
    }
    if (![string]::IsNullOrWhiteSpace($Name))
    {
        $objectQuery = $objectQuery + "names=`'$($Name)`'"
        if (![string]::IsNullOrWhiteSpace($Id))
        {
            $objectQuery = $objectQuery + "&ids=`'$($Id)`'"
        }
    }
    elseif (![string]::IsNullOrWhiteSpace($Id)) {
      $objectQuery = $objectQuery + "ids=`'$($Id)`'"
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureLicenses = @()
    foreach ($token in $tokens) {
      $pureLicenses += New-PureOneOperation -resourceType "subscription-licenses" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
    }
    if (![string]::IsNullOrWhiteSpace($ServiceTierType))
    {
      $pureLicenses = $pureLicenses |Where-Object {$_.service_tier -like "*$($serviceTierType)*"}
    }
    if (![string]::IsNullOrWhiteSpace($ServiceTierLevel))
    {
      $pureLicenses = $pureLicenses |Where-Object {$_.service_tier -like "*$($serviceTierLevel)"}
    }
    if (![string]::IsNullOrWhiteSpace($SubscriptionId))
    {
      $pureLicenses = $pureLicenses |Where-Object {$_.subscription.id -eq $SubscriptionId}
    }
    if (![string]::IsNullOrWhiteSpace($SubscriptionName))
    {
      $pureLicenses = $pureLicenses |Where-Object {$_.subscription.name -eq $SubscriptionName}
    }
    if (![string]::IsNullOrWhiteSpace($ArrayId))
    {
      $pureLicenses = $pureLicenses |Where-Object {$_.resources.id -eq $ArrayId}
    }
    if (![string]::IsNullOrWhiteSpace($ArrayName))
    {
      $pureLicenses = $pureLicenses |Where-Object {$_.resources.name -eq $ArrayName}
    }
    if (($pureLicenses | Measure-Object).Count -eq 0)
    {
      throw "No matching licenses were found on entered Pure1 organization(s)."
    }
    return $pureLicenses
}

function Get-PureOneSubscription {
  <#
  .SYNOPSIS
    Returns all Pure Storage subscriptions.
  .DESCRIPTION
    Returns all or specified Pure Storage subscriptions.
  .INPUTS
    None required. Optional inputs are subscription
  .OUTPUTS
    Returns the Pure Storage subscription information in Pure1.
  .EXAMPLE
    PS C:\ Get-PureOneSubscription
 
    Returns all subscriptions
  .EXAMPLE
    PS C:\ Get-PureOneSubscription -Name mytestsub
 
    Returns the subscription with the specified name.
 
  .NOTES
    Version: 1.0
    Author: Cody Hosterman https://codyhosterman.com
    Creation Date: 01/26/2021
    Purpose/Change: Initial release
 
  *******Disclaimer:******************************************************
  This scripts are offered "as is" with no warranty. While this
  scripts is tested and working in my environment, it is recommended that you test
  this script in a test lab before using in a production environment. Everyone can
  use the scripts/commands provided here without any written permission but I
  will not be liable for any damage or loss to the system.
  ************************************************************************
  #>


  [CmdletBinding()]
  Param(
          [Parameter(Position=0)]
          [string]$Id,
          
          [Parameter(Position=1)]
          [string]$Name,

          [Parameter(Position=2)]
          [string]$PureOneToken,

          [Parameter(Position=3)]
          [PureOneOrganization[]]$PureOneOrganization
  )
    if ((![string]::IsNullOrWhiteSpace($Name)) -and (![string]::IsNullOrWhiteSpace($Id)))
    {
      throw "Please only pass in a license name or an ID. Not both."
    }
    if ($null -ne $global:pureOneRateLimit)
    {
      if ($Global:pureOneRateLimit -in 1..1000)
      {
        $objectQuery = "?limit=$($global:pureOneRateLimit)&"
      }
      else {
        throw "Pure1 Rate limit set to invalid amount. Must be between 1-1000. Currently set to $($global:pureOneRateLimit)"
      }
    }
    else {
      $objectQuery = "?"
    }
    if (![string]::IsNullOrWhiteSpace($Name))
    {
        $objectQuery = $objectQuery + "names=`'$($Name)`'"
        if (![string]::IsNullOrWhiteSpace($Id))
        {
            $objectQuery = $objectQuery + "&ids=`'$($Id)`'"
        }
    }
    elseif (![string]::IsNullOrWhiteSpace($Id)) {
      $objectQuery = $objectQuery + "ids=`'$($Id)`'"
    }
    $tokens = @()
    if ([string]::IsNullOrWhiteSpace($pureOneToken))
    {
      $tokens += Get-PureOneToken -pureOneOrganization $pureOneOrganization
    }
    else{
      $tokens += $pureOneToken
    }
    $pureSubscriptions = @()
    foreach ($token in $tokens) {
      $pureSubscriptions += New-PureOneOperation -resourceType "subscriptions" -queryFilter $objectQuery -pureOneToken $token -restOperationType GET -ErrorAction SilentlyContinue
    }
    if (($pureSubscriptions | Measure-Object).Count -eq 0)
    {
      throw "No matching subscriptions were found on entered Pure1 organization(s)."
    }
    return $pureSubscriptions
}

#internal functions
function Resolve-JWTtoken {
  [cmdletbinding()]
  param([Parameter(Mandatory=$true)][string]$token)
  $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/')
  while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
  $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
  $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray)
  $tokobj = $tokenArray | ConvertFrom-Json
  return $tokobj
}
function Set-PureOneHeader {
  [CmdletBinding()]
  Param(
          [Parameter(Position=0)]
          [string]$pureOneToken
  )
    if (($null -eq $Global:PureOneConnections) -and ([string]::IsNullOrWhiteSpace($pureOneToken)))
    {
        throw "No access token found in the global variable or passed in. Run the cmdlet New-PureOneRestConnection to authenticate."
    }
    if (![string]::IsNullOrWhiteSpace($pureOneToken)) {
        $pureOneHeader = @{authorization="Bearer $($pureOnetoken)"}
    }
    else {
        $foundDefaultOrg = $Global:PureOneConnections |Where-Object {$_.DefaultOrg -eq $true}
        $pureOneHeader = @{authorization="Bearer $($foundDefaultOrg.pureOneToken)"} 
    }
    return $pureOneHeader
}
function Get-PureOneToken{
  [CmdletBinding()]
  Param(
        [Parameter(Position=0)]
        [PureOneOrganization[]]$pureOneOrganization,

        [Parameter(Position=1)]
        [switch]$defaultOrg
  )
  if ($pureOneOrganization.Count -eq 0)
  {
      if ($defaultOrg -eq $true)
      {
        $foundDefaultOrg = $null
        $foundDefaultOrg = $Global:PureOneConnections |Where-Object {$_.DefaultOrg -eq $true}
        if ($null -eq $foundDefaultOrg)
        {
          throw "No default Pure1 Connection found. Please authenticate with New-PureOneConnection or set a connection with the .SetDefault(`$true) operation"
        }
        else {
          return $Global:foundDefaultOrg.PureOneToken
        }
      }
      else {
        return $Global:PureOneConnections.PureOneToken
      }
  }
  else {
    return $pureOneOrganization.PureOneToken
  }
}
#custom classes
class PureOneOrganization
{
  [int] $PureOneOrgID 
  [string] $Role
  [datetime] $SessionExpiration
  [string] $PureOneAppID
  [string] $PureOneToken
  [bool]$DefaultOrg = $false
  hidden [bool]$updateLock = $false
    # Constructor
    
    SetDefault ([bool]$DefaultOrg)
    {
      if ($DefaultOrg -eq $true)
      {
        $count = 0
        foreach ($connection in $Global:PureOneConnections) 
        {
          if (($connection.DefaultOrg -eq $true) -and ($connection.PureOneOrgID -ne $this.PureOneOrgID))
          {
            throw "Cannot set this connection as default, connection for Pure1 organization $($connection.PureOneOrgID) is already default. Unset it via: `$Global:PureOneConnections[$($count)].SetDefault(`$false)."
          }
          $count++
        }
      }
      $this.DefaultOrg = $DefaultOrg
    }
}
class UnixPureOneOrganization : PureOneOrganization {
  [securestring]$RsaPassword 
  [String]$PrivateKeyFileLocation
  UnixPureOneOrganization ([int] $PureOneOrgID, [string] $pureOneToken, [string] $PureOneAppID, [string] $role,[datetime] $SessionExpiration,[securestring]$RsaPassword, [String]$PrivateKeyFileLocation)
  {
      $this.PureOneOrgID = $PureOneOrgID
      $this.PureOneAppID = $PureOneAppID
      $this.SessionExpiration = $SessionExpiration
      $this.Role = $role
      $this.PureOneToken = $pureOnetoken
      $this.RsaPassword = $RsaPassword
      $this.PrivateKeyFileLocation = $PrivateKeyFileLocation
  }
  RefreshConnection ()
  {
    $this.updateLock = $true
    $org = New-PureOneConnection -pureAppID $this.PureOneAppID -PrivateKeyFileLocation $this.PrivateKeyFileLocation -RsaPassword $this.RsaPassword -returnOrg
    $this.SessionExpiration = $org.SessionExpiration
    $this.PureOneToken = $org.pureOnetoken
    $this.updateLock = $false
    return 
  }
}
class WindowsPureOneOrganization : PureOneOrganization {
  [System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate
  # Constructor
  WindowsPureOneOrganization ([int] $PureOneOrgID, [string] $pureOneToken, [string] $PureOneAppID, [string] $role,[datetime] $SessionExpiration, [System.Security.Cryptography.X509Certificates.X509Certificate]$Certificate)
  {
      $this.PureOneOrgID = $PureOneOrgID
      $this.PureOneAppID = $PureOneAppID
      $this.SessionExpiration = $SessionExpiration
      $this.Role = $role
      $this.PureOneToken = $pureOnetoken
      $this.Certificate = $certificate
  }
  RefreshConnection ()
  {
    $this.updateLock = $true
    $org = New-PureOneConnection -pureAppID $this.PureOneAppID -certificate $this.Certificate -returnOrg
    $this.SessionExpiration = $org.SessionExpiration
    $this.PureOneToken = $org.pureOnetoken
    $this.updateLock = $false
    return 
  }
}
#Global variables
$global:pureOneRateLimit = $null
$global:pureOneRestVersion = "1.latest"
$Global:PureOneConnections = @()
$Global:PureOneRestUrl = "https://api.pure1.purestorage.com/api"
$Global:PureOneRestEndpointUrl = "https://api.pure1.purestorage.com/oauth2/1.0/token"

New-Alias -Name Get-PureOneArrayBusyMeter -Value Get-PureOneArrayLoadMeter
New-Alias -Name New-PureOneRestConnection -Value New-PureOneConnection
New-Alias -Name New-PureOneRestOperation -Value New-PureOneOperation