# . . . . . . . . . . . . . . . . . . .
#  PowerShell Module for Cohesity API
#  Version 2020.12.22 - Brian Seltzer
# . . . . . . . . . . . . . . . . . . .
#
# 2020.10.16 - added password parameter to storePasswordInFile function
# 2020.10.20 - code cleanup (moved old version history to end of file)
# 2020.12.22 - added v2 support for file download
#
# . . . . . . . . . . . . . . . . . . . . . . . . 
$versionCohesityAPI = '2020.12.22'

# demand modern powershell version (must support TLSv1.2)
if($Host.Version.Major -le 5 -and $Host.Version.Minor -lt 1){
    Write-Warning "PowerShell version must be upgraded to 5.1 or higher to connect to Cohesity!"
    Pause
    exit
}

$REPORTAPIERRORS = $true

$pwfile = $(Join-Path -Path $PSScriptRoot -ChildPath YWRtaW4)
$apilogfile = $(Join-Path -Path $PSScriptRoot -ChildPath cohesity-api-debug.log)

# platform detection ==========================================================================
if ($PSVersionTable.Platform -eq 'Unix') {
    $CONFDIR = '~/.cohesity-api'
    if ($(Test-Path $CONFDIR) -eq $false) { $null = New-Item -Type Directory -Path $CONFDIR}
}else{
    $registryPath = 'HKCU:\Software\Cohesity-API'
    $WEBCLI = New-Object System.Net.WebClient;    
}

if($PSVersionTable.PSEdition -eq 'Desktop'){
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { return $true }
    $ignoreCerts = @"
public class SSLHandler
{
    public static System.Net.Security.RemoteCertificateValidationCallback GetSSLHandler()
    {
        return new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
    }
}
"@

    if(!("SSLHandler" -as [type])){
        Add-Type -TypeDefinition $ignoreCerts
    }
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [SSLHandler]::GetSSLHandler()
}

function __writeLog($logmessage){
    "$(Get-Date): $logmessage" | Out-File -FilePath $apilogfile -Append
}

# authentication functions ========================================================================

function apiauth($vip, $username='helios', $domain='local', $passwd=$null, $password = $null, $tenantId = $null, [switch] $quiet, [switch] $noprompt, [switch] $updatePassword, [switch] $helios, [switch] $useApiKey){

    if(-not $vip){
        if($helios){
            $vip = 'helios.cohesity.com'
        }else{
            Write-Host 'vip is required' -foregroundcolor Yellow
            break
        }
    }

    # parse domain\username or username@domain
    if($username.Contains('\')){
        $domain, $username = $username.Split('\')
    }
    if($password){ $passwd = $password }
    if($updatePassword){
        $fpasswd = Get-CohesityAPIPasswordFromFile -vip $vip -username $username -domain $domain
        if($fpasswd){
            storePasswordInFile  -vip $vip -username $username -domain $domain
        }else{
            Set-CohesityAPIPassword -vip $vip -username $username -domain $domain
        }
    }
    # get password
    if(!$passwd){
        $passwd = Get-CohesityAPIPassword -vip $vip -username $username -domain $domain
        if(!$passwd -and !$noprompt){
            Set-CohesityAPIPassword -vip $vip -username $username -domain $domain
            $passwd = Get-CohesityAPIPassword -vip $vip -username $username -domain $domain
        }
        if(!$passwd){
            Write-Host "No password provided for $username at $vip" -ForegroundColor Yellow
            $global:AUTHORIZED = $false
            break
        }
    }

    $body = ConvertTo-Json @{
        'domain' = $domain;
        'username' = $username;
        'password' = $passwd
    }

    $global:APIROOT = 'https://' + $vip + '/irisservices/api/v1'
    $global:APIROOTv2 = 'https://' + $vip + '/v2/'
    $HEADER = @{'accept' = 'application/json'; 'content-type' = 'application/json'}
    if($useApiKey){
        $HEADER['apiKey'] = $passwd
        $global:HEADER = $HEADER
        $global:AUTHORIZED = $true
        $global:CLUSTERSELECTED = $true
        $cluster = api get cluster
        if($cluster){
            if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
        }else{
            $global:AUTHORIZED = $false
        }
    }elseif($vip -eq 'helios.cohesity.com' -or $helios){
        # Authenticate Helios
        $HEADER['apiKey'] = $passwd
        $URL = 'https://helios.cohesity.com/mcm/clusters/connectionStatus'
        try{
            if($PSVersionTable.Edition -eq 'Core'){
                $global:HELIOSALLCLUSTERS = Invoke-RestMethod -Method get -Uri $URL -Header $HEADER -SkipCertificateCheck
            }else{
                $global:HELIOSALLCLUSTERS = Invoke-RestMethod -Method get -Uri $URL -Header $HEADER
            }
            $global:HELIOSCONNECTEDCLUSTERS = $global:HELIOSALLCLUSTERS | Where-Object connectedToCluster -eq $true
            $global:HEADER = $HEADER
            $global:AUTHORIZED = $true
            $global:CLUSTERSELECTED = $false
            $global:CLUSTERREADONLY = $false
            if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
        }catch{
            $global:AUTHORIZED = $false
            __writeLog $_.ToString()
            if($_.ToString().contains('"message":')){
                Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
            }else{
                Write-Host $_.ToString() -foregroundcolor yellow
            }
        }
    }else{
        # Authenticate Cluster
        $url = $APIROOT + '/public/accessTokens'
        try {
            # authenticate
            if($PSVersionTable.PSEdition -eq 'Core'){
                $auth = Invoke-RestMethod -Method Post -Uri $url -Header $HEADER -Body $body -SkipCertificateCheck
            }else{
                $auth = Invoke-RestMethod -Method Post -Uri $url -Header $HEADER -Body $body
            }
            # set file transfer details
            if($PSVersionTable.Platform -eq 'Unix'){
                $global:CURLHEADER = "authorization: $($auth.tokenType) $($auth.accessToken)"
            }else{
                $WEBCLI.Headers['authorization'] = $auth.tokenType + ' ' + $auth.accessToken;
            }
            # store token
            $global:AUTHORIZED = $true
            $global:CLUSTERSELECTED = $true
            $global:CLUSTERREADONLY = $false
            $global:HEADER = @{'accept' = 'application/json'; 
                'content-type' = 'application/json'; 
                'authorization' = $auth.tokenType + ' ' + $auth.accessToken
            }
            if($tenantId){
                $global:HEADER['x-impersonate-tenant-id'] = "$tenantId/"
            }
            if(!$quiet){ Write-Host "Connected!" -foregroundcolor green }
        }catch{
            $global:AUTHORIZED = $false
            __writeLog $_.ToString()
            $global:AUTHORIZED = $false
            if($REPORTAPIERRORS){
                if($_.ToString().contains('"message":')){
                    Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
                }else{
                    Write-Host $_.ToString() -foregroundcolor yellow
                }
            }
        }
    }
}

# select helios access cluster
function heliosCluster($clusterName, [switch] $verbose){
    if($clusterName -and $HELIOSCONNECTEDCLUSTERS){
        if(! ($clusterName -is [string])){
            $clusterName = $clusterName.name
        }
        $cluster = $HELIOSCONNECTEDCLUSTERS | Where-Object name -eq $clusterName
        if($cluster){
            $global:HEADER.accessClusterId = $cluster.clusterId
            $global:CLUSTERSELECTED = $true
            $global:CLUSTERREADONLY = (api get /mcm/config).mcmReadOnly
            if($verbose){
                Write-Host "Connected ($($cluster.name))" -ForegroundColor Green
            }
        }else{
            Write-Host "Cluster $clusterName not connected to Helios" -ForegroundColor Yellow
            $global:CLUSTERSELECTED = $false
            return $null
        }
    }else{
        $HELIOSCONNECTEDCLUSTERS | Sort-Object -Property name | Select-Object -Property name, clusterId, softwareVersion
        "`ntype heliosCluster <clustername> to connect to a cluster"
    }
    if (-not $global:AUTHORIZED){ 
        if($REPORTAPIERRORS){
            Write-Host 'Please use apiauth to connect to helios' -foregroundcolor yellow
        }
    }
}

function heliosClusters(){
    return $HELIOSCONNECTEDCLUSTERS | Sort-Object -Property name
}

# terminate authentication
function apidrop([switch] $quiet){
    $global:AUTHORIZED = $false
    $global:HEADER = ''
    $global:HELIOSALLCLUSTERS = $null
    $global:HELIOSCONNECTEDCLUSTERS = $null
    if(!$quiet){ Write-Host "Disonnected!" -foregroundcolor green }
}

# api call functions ==============================================================================

$methods = 'get', 'post', 'put', 'delete'
function api($method, $uri, $data, $version=1, [switch]$v2){
    if (-not $global:AUTHORIZED){ 
        if($REPORTAPIERRORS){
            Write-Host 'Not authenticated to a cohesity cluster' -foregroundcolor yellow
            if($MyInvocation.PSCommandPath){
                exit 1
            }
        }
    }else{
        if($method -ne 'get' -and $global:CLUSTERREADONLY -eq $true){
            Write-Host "Cluster connection is READ-ONLY" -ForegroundColor Yellow
            break
        }
        if (-not $methods.Contains($method)){
            if($REPORTAPIERRORS){
                Write-Host "invalid api method: $method" -foregroundcolor yellow
            }
            break
        }
        try {
            
            if($version -eq 2 -or $v2){
                $url = $APIROOTv2 + $uri
            }else{
                if ($uri[0] -ne '/'){ $uri = '/public/' + $uri}
                $url = $APIROOT + $uri
            }
            $body = ConvertTo-Json -Depth 100 $data
            if ($PSVersionTable.PSEdition -eq 'Core'){
                if($body){
                    $result = Invoke-RestMethod -Method $method -Uri $url -Body $body -Header $HEADER -SkipCertificateCheck
                }else{
                    $result = Invoke-RestMethod -Method $method -Uri $url -Header $HEADER -SkipCertificateCheck
                }
            }else{
                $result = Invoke-RestMethod -Method $method -Uri $url -Body $body -Header $HEADER
            }
            return $result
        }catch{
            __writeLog $_.ToString()
            if($REPORTAPIERRORS){
                if($_.ToString().contains('"message":')){
                    Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
                }else{
                    Write-Host $_.ToString() -foregroundcolor yellow
                }
            }            
        }
    }
}

# file download function
function fileDownload($uri, $fileName, $version=1, [switch]$v2){
    if (-not $global:AUTHORIZED){ Write-Host 'Please use apiauth to connect to a cohesity cluster' -foregroundcolor yellow; break }
    try {
        if($version -eq 2 -or $v2){
            $url = $APIROOTv2 + $uri
        }else{
            if ($uri[0] -ne '/'){ $uri = '/public/' + $uri}
            $url = $APIROOT + $uri
        }
        if ($PSVersionTable.Platform -eq 'Unix'){
            curl -k -s -H "$global:CURLHEADER" -o "$fileName" "$url"
        }else{
            if($fileName -notmatch '\\'){
                $fileName = $(Join-Path -Path $PSScriptRoot -ChildPath $fileName)
            }
            $WEBCLI.DownloadFile($url, $fileName)
        } 
    }catch{
        __writeLog $_.ToString()
        $_.ToString()
        if($_.ToString().contains('"message":')){
            Write-Host (ConvertFrom-Json $_.ToString()).message -foregroundcolor yellow
        }else{
            Write-Host $_.ToString() -foregroundcolor yellow
        }                
    }
}

# date functions ==================================================================================

function timeAgo([int64] $age, [string] $units){
    $currentTime = [int64](((get-date).ToUniversalTime())-([datetime]"1970-01-01 00:00:00")).TotalSeconds*1000000
    $secs=@{'seconds'= 1; 'sec'= 1; 'secs' = 1;
            'minutes' = 60; 'min' = 60; 'mins' = 60;
            'hours' = 3600; 'hour' = 3600; 
            'days' = 86400; 'day' = 86400;
            'weeks' = 604800; 'week' = 604800;
            'months' = 2628000; 'month' = 2628000;
            'years' = 31536000; 'year' = 31536000 }
    $age = $age * $secs[$units.ToLower()] * 1000000
    return [int64] ($currentTime - $age)
}

function usecsToDate($usecs){
    $unixTime=$usecs/1000000
    $origin = ([datetime]'1970-01-01 00:00:00')
    return $origin.AddSeconds($unixTime).ToLocalTime()
}

function dateToUsecs($datestring){
    if($datestring -isnot [datetime]){ $datestring = [datetime] $datestring }
    $usecs = [int64](($datestring.ToUniversalTime())-([datetime]"1970-01-01 00:00:00")).TotalSeconds*1000000
    $usecs
}

# password functions ==============================================================================

function Get-CohesityAPIPassword($vip, $username, $domain='local'){
    # parse domain\username or username@domain
    if($username.Contains('\')){
        $domain, $username = $username.Split('\')
    }
    if($username.Contains('@')){
        $username, $domain = $username.Split('@')
    }
    $passwd = Get-CohesityAPIPasswordFromFile -vip $vip -username $username -domain $domain
    if($passwd){
        return $passwd
    }
    $keyName = "$vip`:$domain`:$username"
    if($PSVersionTable.Platform -eq 'Unix'){
        # Unix
        $keyFile = "$CONFDIR/$keyName"
        if (Test-Path $keyFile) {
            $key, $storedPassword = Get-Content $keyFile
            return Unprotect-CohesityAPIPassword $key $storedPassword
        }
    }else{
        # Windows
        $storedPassword = Get-ItemProperty -Path "$registryPath" -Name "$keyName" -ErrorAction SilentlyContinue
        If (($null -ne $storedPassword) -and ($storedPassword.Length -ne 0)) {
            $securePassword = $storedPassword.$keyName  | ConvertTo-SecureString
            return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $securePassword ))
        }
    }
}


function Get-CohesityAPIPasswordFromFile($vip, $username, $domain){
    $pwlist = Get-Content -Path $pwfile -ErrorAction SilentlyContinue
    foreach($pwitem in $pwlist){
        $v, $d, $u, $cpwd = $pwitem.split(":", 4)
        if($v -eq $vip -and $d -eq $domain -and $u -eq $username){
            return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($cpwd))
        }
    }
    return $null
}


function storePasswordInFile($vip='helios.cohesity.com', $username='helios', $domain='local', [switch]$helios, $password=$null){
    # parse domain\username or username@domain
    if($username.Contains('\')){
        $domain, $username = $username.Split('\')
    }
    if($username.Contains('@')){
        $username, $domain = $username.Split('@')
    }

    if($vip -eq 'helios.cohesity.com' -and $username -eq 'helios' -and ! $helios){
        # prompt for vip
        __writeLog "Prompting for VIP, USERNAME, DOMAIN"
        $newVip = Read-Host -Prompt "Enter VIP ($vip)"
        if($newVip -ne ''){ $vip = $newVip }

        # prompt for domain
        $newDomain = Read-Host -Prompt "Enter domain ($domain)"
        if($newDomain -ne ''){ $domain = $newDomain }

        # prompt for username
        $newUsername = Read-Host -Prompt "Enter username ($username)"
        if($newUsername -ne ''){ $username = $newUsername }
    }

    # prompt for password
    __writeLog "Prompting for Password"
    if(!$password){
        $secureString = Read-Host -Prompt "Enter password for $domain\$username at $vip" -AsSecureString
        $passwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $secureString ))    
    }else{
        $passwd = $password
    }
    $opwd = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($passwd))

    $pwlist = Get-Content -Path $pwfile -ErrorAction SilentlyContinue
    $updatedContent = ''
    $foundPwd = $false
    foreach($pwitem in $pwlist){
        $v, $d, $u, $cpwd = $pwitem.split(":", 4)
        # update existing
        if($v -eq $vip -and $d -eq $domain -and $u -eq $username){
            $foundPwd = $true
            $updatedContent += "{0}:{1}:{2}:{3}`n" -f $vip, $domain, $username, $opwd
        # other existing records    
        }else{
            if($pwitem -ne ''){
                $updatedContent += "{0}`n" -f $pwitem
            }
        }
    }
    # add new
    if(!$foundPwd){
        $updatedContent += "{0}:{1}:{2}:{3}`n" -f $vip, $domain, $username, $opwd
    }

    $updatedContent | out-file -FilePath $pwfile
    Write-Host "Password stored!" -ForegroundColor Green
}


function Set-CohesityAPIPassword($vip, $username, $domain='local', $passwd=$null){
    # prompt for vip
    if(-not $vip){
        __writeLog "Prompting for VIP"
        Write-Host 'VIP: ' -foregroundcolor green -nonewline
        $vip = Read-Host
        if(-not $vip){Write-Host 'vip is required' -foregroundcolor red; break}
    }
    # prompt for username
    if(-not $username){
        __writeLog "Prompting for Username"
        Write-Host 'Username: ' -foregroundcolor green -nonewline
        $username = Read-Host
        if(-not $username){Write-Host 'username is required' -foregroundcolor red; break}
    }
    # parse domain\username or username@domain
    if($username.Contains('\')){
        $domain, $username = $username.Split('\')
    }
    if($username.Contains('@')){
        $username, $domain = $username.Split('@')
    }
    $keyName = "$vip`:$domain`:$username"
    if(!$passwd){
        __writeLog "Prompting for Password"
        $secureString = Read-Host -Prompt "Enter password for $username at $vip" -AsSecureString
        $passwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( $secureString ))
    }
    if($PSVersionTable.Platform -eq 'Unix'){
        # Unix
        $keyFile = "$CONFDIR/$keyName"
        $key = New-AesKey 
        $key | Out-File $keyFile
        Protect-CohesityAPIPassword $key $passwd | Out-File $keyFile -Append
    }else{
        # Windows
        $securePassword = ConvertTo-SecureString -String $passwd -AsPlainText -Force
        $encryptedPasswordText = $securePassword | ConvertFrom-SecureString
        if(!(Test-Path $registryPath)){
            New-Item -Path $registryPath -Force | Out-Null
        }
        Set-ItemProperty -Path "$registryPath" -Name "$keyName" -Value "$encryptedPasswordText"
    }
}


# security functions ==============================================================================

function New-AesManagedObject($key, $IV){
    $aesManaged = New-Object "System.Security.Cryptography.AesManaged"
    $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
    $aesManaged.BlockSize = 128
    $aesManaged.KeySize = 256
    if($IV){
        if($IV.getType().Name -eq "String"){
            $aesManaged.IV = [System.Convert]::FromBase64String($IV)
        }else{
            $aesManaged.IV = $IV
        }
    }
    if($key){
        if($key.getType().Name -eq "String") {
            $aesManaged.Key = [System.Convert]::FromBase64String($key)
        }else{
            $aesManaged.Key = $key
        }
    }
    $aesManaged
}

function New-AesKey() {
    $aesManaged = New-AesManagedObject
    $aesManaged.GenerateKey()
    [System.Convert]::ToBase64String($aesManaged.Key)
}

function Protect-CohesityAPIPassword($key, $unencryptedString) {
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
    $aesManaged = New-AesManagedObject $key
    $encryptor = $aesManaged.CreateEncryptor()
    $encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
    [byte[]] $fullData = $aesManaged.IV + $encryptedData
    $aesManaged.Dispose()
    [System.Convert]::ToBase64String($fullData)
}

function Unprotect-CohesityAPIPassword($key, $encryptedStringWithIV) {
    $bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
    $IV = $bytes[0..15]
    $aesManaged = New-AesManagedObject $key $IV
    $decryptor = $aesManaged.CreateDecryptor();
    $unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
    $aesManaged.Dispose()
    [System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
}

# developer tools =================================================================================

function saveJson($object, $jsonFile = './debug.json'){
    $object | ConvertTo-Json -Depth 99 | out-file -FilePath $jsonFile
}

function loadJson($jsonFile = './debug.json'){
    return Get-Content $jsonFile | ConvertFrom-Json
}

function json2code($json = '', $jsonFile = '', $psFile = 'myObject.ps1'){

    if($jsonFile -ne ''){
        $json = (Get-Content $jsonFile) -join "`n"
    }

    $json = $json | ConvertFrom-Json | ConvertTo-Json -Depth 99
    
    $pscode = ''
    foreach ($line in $json.split("`n")) {
        $line = $line.TrimEnd()
        # preserve end of line character
        $finalEntry = $true
        if ($line[-1] -eq ',') {
            $finalEntry = $false
            $line = $line -replace ".$"
        }
        
        # key value delimiter :
        $key, $value = $line.split(':', 2)

        # line is braces only
        $key = $key.Replace('{', '@{').Replace('[','@(').Replace(']', ')')

        if ($value) {
            $value = $value.trim()

        # value is quoted text
            if ($value[0] -eq '"') {
                $line = "$key = $value"
            }

        # value is opening { brace
            elseif ('{' -eq $value) {
                $value = $value.Replace('{', '@{')
                $line = "$key = $value"
            }
        
        # value is opening [ list
            elseif ('[' -eq $value) {
                $value = $value.Replace('[', '@(')
                $line = "$key = $value"                  
            }

        # empty braces
            elseif ('{}' -eq $value) {
                $value = '@{}'
                $line = "$key = $value"
            }
        
        # empty list
            elseif ('[]' -eq $value) {
                $value = '@()'
                $line = "$key = $value"
            }

        # value is opening ( list
            elseif ('[' -eq $value) {
                $value = $value.Replace('[', '@(')
                $line = "$key = $value"
            }

        # value is a boolean
            elseif ($value -eq 'true') {
                $line = "$key = " + '$true'
            }

            elseif ($value -eq 'false') {
                $line = "$key = " + '$false'
            }

        # null
            elseif ($value -eq 'null') {
                $line = "$key = " + '$null'
            }
            else {

        # value is numeric
                if ($value -as [long] -or $value -eq '0') {
                    $line = "$($key) = $value"
                }
                else {

        # delimeter : was inside of quotes
                    $line = "$($key):$($value)"
                }
            }
        }
        else {

        # was no value on this line
            $line = $key
        }

        # replace end of line character ;
        if (! $finalEntry) {
            $line = "$line;"
        }

        $pscode += "$line`n"
    }

    $pscode = '$myObject = ' + $pscode

    $pscode | out-file $psFile
    return $pscode
}

# add a property
function setApiProperty{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)][string]$name,
        [Parameter(Mandatory = $True)][System.Object]$value,
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)][System.Object]$object
    )
    if(! $object.PSObject.Properties[$name]){
        $object | Add-Member -MemberType NoteProperty -Name $name -Value $value
    }else{
        $object.$name = $value
    }
}

# delete a propery
function delApiProperty{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)][string]$name,
        [Parameter(Mandatory = $True, ValueFromPipeline = $True)][System.Object]$object
    )
    $object.PSObject.Members.Remove($name)
}

# show properties of an object
function showProps{
    param (
        [Parameter(Mandatory = $True)]$obj,
        [Parameter()]$parent = 'myobject',
        [Parameter()]$search = $null
    )
    if($obj.getType().Name -eq 'String' -or $obj.getType().Name -eq 'Int64'){
        if($null -ne $search){
            if($parent.ToLower().Contains($search) -or ($obj.getType().Name -eq 'String' -and $obj.ToLower().Contains($search))){
                "$parent = $obj"
            }
        }else{
            "$parent = $obj"
        }
        
    }else{ 
        foreach($prop in $obj.PSObject.Properties | Sort-Object -Property Name){
            if($($prop.Value.GetType().Name) -eq 'PSCustomObject'){
                $thisObj = $prop.Value
                showProps $thisObj "$parent.$($prop.Name)" $search
            }elseif($($prop.Value.GetType().Name) -eq 'Object[]'){
                $thisObj = $prop.Value
                $x = 0
                foreach($item in $thisObj){
                    showProps $thisObj[$x] "$parent.$($prop.Name)[$x]" $search
                    $x += 1
                }
            }else{
                if($null -ne $search){
                    if($prop.Name.ToLower().Contains($search.ToLower()) -or ($prop.Value.getType().Name -eq 'String' -and $prop.Value.ToLower().Contains($search.ToLower()))){
                        "$parent.$($prop.Name) = $($prop.Value)"
                    }
                }else{
                    "$parent.$($prop.Name) = $($prop.Value)"
                }
            }
        }
    }
}

# convert syntax to python
function py($p){
    $py = $p.replace("$","").replace("].","]['").replace(".","']['")
    if($py[-1] -ne ']'){
        $py += "']"
    }
    $py
}


# self updater
function cohesityAPIversion([switch]$update){
    if($update){
        $repoURL = 'https://raw.githubusercontent.com/bseltz-cohesity/scripts/master/powershell'
        (Invoke-WebRequest -Uri "$repoURL/cohesity-api/cohesity-api.ps1").content | Out-File -Force cohesity-api.ps1; (Get-Content cohesity-api.ps1) | Set-Content cohesity-api.ps1
        Write-Host "Cohesity-API version updated! Please restart PowerShell"
    }else{
        Write-Host "Cohesity-API version $versionCohesityAPI" -ForegroundColor Green 
    }
}


# paged view list
function getViews([switch]$includeInactive){
    $myViews = @()
    $views = $null
    while(! $views){
        if($includeInactive){
            $views = api get views?includeInactive=true
        }else{
            $views = api get views
        }
    }
    $myViews += $views.views
    $lastResult = $views.lastResult
    while(! $lastResult){
        $lastViewId = $views.views[-1].viewId
        $views = $null
        while(! $views){
            if($includeInactive){
                $views = api get "views?maxViewId=$lastViewId&includeInactive=true"
            }else{
                $views = api get views?maxViewId=$lastViewId
            }
        }
        $lastResult = $views.lastResult
        $myViews += $views.views
    }
    return $myViews
}

# old version history
# . . . . . . . . . . . . . . . . . . . . . . . . 
# 0.06 - Consolidated Windows and Unix versions - June 2018
# 0.07 - Added saveJson, loadJson and json2code utility functions - Feb 2019
# 0.08 - added -prompt to prompt for password rather than save - Mar 2019
# 0.09 - added setApiProperty / delApiProperty - Apr 2019
# 0.10 - added $REPORTAPIERRORS constant - Apr 2019
# 0.11 - added storePassword function and username parsing - Aug 2019
# 0.12 - added -password to apiauth function - Oct 2019
# 0.13 - added showProps function - Nov 2019
# 0.14 - added storePasswordFromInput function - Dec 2019
# 0.15 - added support for PS Core on Windows - Dec 2019
# 0.16 - added ServicePoint connection workaround - Jan 2020
# 0.17 - fixed json2code line endings on Windows - Jan 2020
# 0.18 - added REINVOKE - Jan 2020
# 0.19 - fixed password encryption for PowerShell 7.0 - Mar 2020
# 0.20 - refactored, added apipwd, added helios access - Mar 2020
# 0.21 - helios changes - Mar 2020
# 0.22 - added password file storage - Apr 2020
# 0.23 - added self updater - Apr 2020
# 0.24 - added delete with body - Apr 2020
# 0.25 - added paged view list - Apr 2020
# 0.26 - added support for tenants - May 2020
# 0.27 - added support for Iris API Key - May 2020
# 0.28 - added reprompt for password, debug log - June 2020
# 0.29 - update storePasswordInFile - June 2020
# 2020.06.04 - updated version numbering - June 2020
# 2020.06.16 - improved REINVOKE - June 2020
# 2020-06.25 - added API v2 support (-version 2) or (-v2)
# 2020.07.08 - removed timout
# 2020.07.20 - fixed dateToUsecs for international date formats
# 2020.07.30 - quiet ssl handler
# 2020.08.08 - fixed timezone issue
# 2020.10.02 - set PROMPTFORPASSWORDCHANGE to false
# 2020.10.05 - retired REINVOKE
# 2020.10.06 - exit script when attempting unauthenticated api call
# 2020.10.13 - fixed timeAgo function for i14n
# . . . . . . . . . . . . . . . . . . . . . . . . 

