pwsh-GC.psm1

function ConvertFrom-UnixTime {
    [cmdletbinding()]
    param (
        [Parameter(Mandatory,ValueFromPipeline)]
        [Int64]
        $UnixDate
    )
    process {
        foreach ( $ThisUnixDate in $UnixDate ) {
            $Origin = New-Object DateTime 1970, 1, 1, 0, 0, 0, ([DateTimeKind]::Utc)

            # Remember: GuardiCore works with epoch times in milliseconds
            $Origin.AddSeconds([Int]($ThisUnixDate/1000)).ToLocalTime()
        }
    }
}

function ConvertTo-UnixTime {
    [cmdletbinding()]
    param (
        [Parameter(Mandatory,ValueFromPipeline)]
        [DateTime]
        $DateTime
    )
    process {
        foreach ( $ThisDateTime in $DateTime ) {
            $Origin = New-Object DateTime 1970, 1, 1, 0, 0, 0, ([DateTimeKind]::Utc)
            [Int64]($ThisDateTime.ToUniversalTime()-$Origin).TotalMilliseconds
        }
    }
}

function Get-Agent {
    [CmdletBinding()]
    param (
        [String]
        $Search,

        [String[]]
        $Version,

        [String[]]
        $Kernel,

        [ValidateSet("Unknown","Windows","Linux")]
        [String[]]
        $OS,

        [PSTypeName("GCLabel")]
        $Label,

        [ValidateSet("Online","Offline")]
        [String[]]
        $Status, # = display_status

        [ValidateSet("undefined",1,2,3,4,5,6,7,8,9,10,11,12,13,14)]
        $Flag,

        [ValidateSet("Active","Not Deployed","Disabled")]
        [String[]]
        $Enforcement, # = module_status_enforcement

        [ValidateSet("Active","Not Deployed")]
        [String[]]
        $Deception, # = module_status_deception

        [ValidateSet("Active","Not Deployed")]
        [String[]]
        $Detection, # = module_status_detection

        [ValidateSet("Active","Not Deployed")]
        [String[]]
        $Reveal,  # = module_status_reveal

        [ValidateSet("last_month","last_week","last_12_hours","last_24_hours","not_active")]
        [String[]]
        $Activity,

        [ValidateRange(0,1000)]
        [Int32]
        $Limit = 20,

        [ValidateRange(0,500000)]
        [Int32]
        $Offset,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/agents"
    }

    # Building the request body with given parameters

    $Body = @{
        version = $Version -join ","
        kernel = $Kernel -join ","
        os = $OS -join ","
        labels = $Label.id -join ","
        display_status = $Status -join ","
        status_flags = $Flag -join ","
        module_status_deception = $Deception -join ","
        module_status_detection = $Detection -join ","
        module_status_reveal = $Reveal -join ","
        activity = $Activity -join ","
        gc_filter = $Search
        limit = $Limit
        offset = $Offset
    }

    # This one's unique
    if ( $Enforcement ) {
        $Add += foreach ($ThisEnforcement in $Enforcement) {
            if ($ThisEnforcement -eq "Disabled") {
                $ThisEnforcement = "Enforcement disabled from management console"
                $ThisEnforcement
            } else {
                $ThisEnforcement
            }
        }

        $Body.module_status_enforcement = $Add -join ","
    }

    # Removing empty hashtable keys
    $RequestBody = Remove-EmptyKeys $Body

    # Making the call
    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCAgent"); $_}
    }
}

function Get-Aggregator {
    [cmdletbinding()]
    param (
        [string]
        $Search,

        [ValidateSet("UP","DOWN","RUNTIME_ERROR")]
        [string[]]
        $Status,

        [string[]]
        $Version,

        [int32]
        $Limit = 20,

        [int32]
        $Offset,

        [switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/agent_aggregators"
    }

    # Building the request body with given parameters

    $Body = @{
        gc_filter = $Search
        display_status = $Status -join ","
        version = $Version -join ","
        limit = $Limit
        offset = $Offset
    }

    # Removing empty hashtable keys
    $RequestBody = Remove-EmptyKeys $Body

    # Making the call
    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCAggregator"); $_}
    }
}

function Get-ApiKey {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory=$true,ParameterSetName = "ByName")]
        [System.String]
        $Server,

        [Parameter(Mandatory=$true,ParameterSetName = "ByUri")]
        [String]
        $Uri,

        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [Alias('Credentials','Cred')]
        [PSCredential]
        $Credential,

        [Switch]
        $Export
    )
    begin {
        if ( $Server ) {
            $Uri = "https://" + $Server + ".cloud.guardicore.com/api/v3.0"
        } else {
            $Uri = $Uri + "/api/v3.0"
        }
        $TempUri = $Uri + "/authenticate"
        $Body = [PSCustomObject]@{
            "username" = ""
            "password" = ""
        }
    }
    process {
        $Body.username = $Credential.UserName
        $Body.password = $Credential.GetNetworkCredential().Password
        $BodyJson = $Body | ConvertTo-Json -Depth 99

        if ( $pscmdlet.ShouldProcess("$Server","Invoke-RestMethod -Uri $TempUri -Method 'Post'") ) {
            try {
                $Token = Invoke-RestMethod -Uri $TempUri -Method "Post" -Body $BodyJson -ContentType "application/json" | Select-Object -ExpandProperty "access_token"
            }
            catch {
                throw $_.Exception
            }
        }

        if ( $Export ) {
            # Returns the object on the pipeline.

            [PSCustomObject]@{
                PSTypeName = "GCApiKey"
                Token = $Token
                Uri = $Uri
            }
        } else {
            # Saves the object in a global (session scope) variable called GCApiKey, so other functions don't need a key input.

            $Global:GCApiKey = [PSCustomObject]@{
                PSTypeName = "GCApiKey"
                Token = $Token
                Uri = $Uri
            }
        }
    }
}

function Get-Asset {
    [CmdletBinding()]
    param (
        [System.String]
        $Search,

        [ValidateSet("on","off")]
        [System.String]
        $Status,

        [ValidateSet("0","1","2","3")]
        [string[]]
        $Risk,

        [Parameter(Mandatory=$false,ValueFromPipeline=$true)]
        [PSTypeName("GCLabel")]
        $Label,

        [Alias("Assets","ID")]
        $Asset,

        [ValidateRange(0,1000)]
        [Int32]$Limit = 20,

        [ValidateRange(0,500000)]
        [Int32]$Offset,

        [Switch]$Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
            $Uri = "/assets"
        }
    }

    process {
        # Handling pipeline input
        $LabelIDs = foreach ($L in $Label) {
            $L.id
        }

        $Body = @{
            search = $Search
            status = $Status
            risk_level = $Risk
            labels = $LabelIDs -join ","
            asset = ""
            limit = $Limit
            offset = $Offset
        }

        # Handling strange asset case

        if ( $Asset ) {
            if ( $Asset[0]._id ) {
                $Body.asset = "vm:" + $Asset[0]._id
            } else {
                $Body.asset = "vm:" + $Asset
            }
        }

        # Removing empty hashtable keys
        $RequestBody = Remove-EmptyKeys $Body

        # Making the call
        if ( $Raw ) {
            pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
        } else {
            pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCAsset"); $_}
        }
    }
}

function Get-Collector {
    [cmdletbinding()]
    param (
        [string]
        $Search,

        [ValidateSet("UP","DOWN")]
        [string[]]
        $Status,

        [string[]]
        $Version,

        [int32]
        $Limit = 20,

        [int32]
        $Offset,

        [switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/collectors"
    }

    # Building the request body with given parameters

    $Body = @{
        gc_filter = $Search
        display_status = $Status -join ","
        version = $Version -join ","
        limit = $Limit
        offset = $Offset
    }

    # Removing empty hashtable keys
    $RequestBody = Remove-EmptyKeys $Body

    # Making the call
    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCCollector"); $_}
    }
}

function Get-Incident{
    [CmdletBinding()]
    param (
        [DateTime]
        $StartTime,

        [DateTime]
        $EndTime,

        [ValidateSet("Low","Medium","High")]
        [System.String[]]
        $Severity,

        [ValidateSet("Incident","Deception","Network Scan","Reveal","Experimental")]
        $IncidentType,

        [String]
        $SourceAsset,

        [String]
        $DestinationAsset,

        [String]
        $AnySideAsset,

        [PSTypeName("GCLabel")]
        $SourceLabel,

        [PSTypeName("GCLabel")]
        $DestinationLabel,

        [PSTypeName("GCLabel")]
        $AnySideLabel,

        [System.Array]
        $IncludeTag,

        [System.Array]
        $ExcludeTag,

        [ValidateRange(0,1000)]
        $Limit = 20,

        [ValidateRange(0,500000)]
        [Int32]
        $Offset = 0,

        [String]
        $ID,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        # This sort is required for legacy URI building
        $Uri = "/incidents?sort=-start_time"
    }

    # Handling start and end time defaults

    if ( -not $StartTime ) {
        $StartTime = $(Get-Date).AddHours(-1)
    }

    if ( -not $EndTime ) {
        $EndTime = Get-Date
    }

    [Int64]$StartTime = $StartTime | ConvertTo-GCUnixTime
    [Int64]$EndTime = $EndTime | ConvertTo-GCUnixTime

    # Building request body with parameters

    $Body = @{
        from_time = $StartTime
        to_time = $EndTime
        severity = $Severity
        incident_type = $IncidentType
        tag = $IncludeTag -join ","
        tags__not = $ExcludeTag -join ","
        id = $ID
        limit = $Limit
        offset = $Offset
    }

    # Removing empty keys

    $RequestBody = Remove-EmptyKeys $Body


    # This legacy URI building is actually necessary,
    # due to the complicated way in which the URI needs to be structured.
    ### SOURCE ###

    $Uri += "&source="

    if ( $SourceLabel ) {
        $Uri += "labels:"
        $Uri += $SourceLabel.id -join "|"
    }

    if ( $SourceAsset ) {
        $Uri += "assets:"
        $Uri += $SourceAsset -join ","
    }

    if ( $Uri[$Uri.length-1] -eq "=" ) {
        $Uri = $Uri.SubString(0,$Uri.Length-8)
    }

    ### DESTINATION ###

    $Uri += "&destination="

    if ( $DestinationLabel ) {
        $Uri += "labels:"
        $Uri += $DestinationLabel.id -join "|"
    }

    if ( $DestinationAsset ) {
        $Uri += "assets:"
        $Uri += $DestinationAsset -join ","
    }

   if ( $Uri[$Uri.length-1] -eq "=" ) {
        $Uri = $Uri.SubString(0,$Uri.Length-13)
    }

    ### ANY SIDE ###
    $Uri += "&any_side="

    if ( $AnySideLabel ) {
        $Uri += "labels:"
        $Uri += $AnySideLabel.id -join "|"
    }

    if ( $AnySideAsset ) {
        $Uri += "assets:"
        $Uri += $AnySideAsset -join ","
    }

    if ( $Uri[$Uri.length-1] -eq "=" ) {
        $Uri = $Uri.SubString(0,$Uri.Length-10)
    }

    # Making the call

    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCIncident"); $_}
    }
}

function Get-Label {
    [CmdletBinding()]
    param (
        [String]
        $Search,

        [System.String]
        $LabelKey,

        [System.String]
        $LabelValue,

        [Switch]
        $FindMatches,

        [Int32]
        $DynamicCriteriaLimit = 10,

        [ValidateRange(0,1000)]
        [Int32]
        $Limit = 20,

        [ValidateRange(0,500000)]
        [Int32]
        $Offset,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/labels"
    }

    # Building the request body with given parameters

    $Body = @{
        find_matches = $FindMatches:isPresent
        text_search = $Search
        key = $LabelKey
        value = $LabelValue
        dynamic_criteria_limit = $DynamicCriteriaLimit
        limit = $Limit
        offset = $Offset
    }

    # Removing empty keys

    $RequestBody = Remove-EmptyKeys $Body

    # Making the call

    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCLabel"); $_}
    }
}

function Get-LabelGroup {
    param (
        [String]
        $Key,

        [String]
        $Value,

        [PSTypeName("GCAsset")]
        $Asset,

        [PSTypeName("GCLabel")]
        $Label,

        [string[]]
        $Status,

        [int32]
        $Limit = 20,

        [int32]
        $Offset,

        [switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $RequestKey = $ApiKey
        } else {
            $RequestKey = $global:GCApiKey
        }
        $Uri = "/visibility/label-groups"
    }

    # Building the request body based on given parameters
    $Body = @{
        key = $Key
        value = $Value
        assets = $Asset.id -join ","
        criteria = $Label.id -join ","
        assets_status = $Status -join ","
        limit = $limit
        offset = $offset
    }

    # Removing empty hashtable keys
    $RequestBody = Remove-EmptyKeys $Body

    # Making the call
    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $RequestKey
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $RequestKey | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCLabelGroup"); $_}
    }
}

function Get-Policy {
    [CmdletBinding()]
    param (
        [System.String]
        $Search,

        [ValidateSet("allow","alert","block","override","override_allow","override_alert","override_block")]
        [System.String[]]
        $Section = @("allow","alert","block"),

        [ValidateSet("UNCHANGED","CREATED","MODIFIED","DELETED")]
        [string[]]
        $State,

        [ValidateSet("TCP","UDP")]
        [System.Array]
        $Protocol = @("TCP","UDP"),

        [ValidateRange(1,65535)]
        [System.Array]
        $Port,

        [PSTypeName("GCLabel")]
        $SourceLabel,

        [PSTypeName("GCLabel")]
        $DestinationLabel,

        [PSTypeName("GCLabel")]
        $AnySideLabel,

        [System.Array]
        $SourceProcess,

        [System.Array]
        $DestinationProcess,

        [System.Array]
        $AnySideProcess,

        [PSTypeName("GCAsset")]
        $SourceAsset,

        [PSTypeName("GCAsset")]
        $DestinationAsset,

        [PSTypeName("GCAsset")]
        $AnySideAsset,

        [ValidateScript({
            foreach ($Subnet in $_) {
                if ( -not ($Subnet -match "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/([1-9]|[1-2][0-9]|[3][0-2])") ) {
                    throw "The subnet provided is not a valid subnet. Please provide a subnet in 0.0.0.0/0 format."
                }

                $true
            }
        })]
        [System.Array]
        $SourceSubnet,

        [ValidateScript({
            foreach ($Subnet in $_) {
                if ( -not ($Subnet -match "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/([1-9]|[1-2][0-9]|[3][0-2])") ) {
                    throw "The subnet provided is not a valid subnet. Please provide a subnet in 0.0.0.0/0 format."
                }

                $true
            }
        })]
        [System.String]
        $DestinationSubnet,

        [ValidateScript({
            foreach ($Subnet in $_) {
                if ( -not ($Subnet -match "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/([1-9]|[1-2][0-9]|[3][0-2])") ) {
                    throw "The subnet provided is not a valid subnet. Please provide a subnet in 0.0.0.0/0 format."
                }

                $true
            }
        })]
        [System.String]
        $AnySideSubnet,

        [System.String]
        $Ruleset,

        [System.String]
        $Comments,

        [Switch]
        $SourceInternet,

        [Switch]
        $DestinationInternet,

        [Switch]
        $AnySideInternet,

        [ValidateRange(0,1000)]
        [Int32]
        $Limit = 20,

        [ValidateRange(0,500000)]
        [Int32]
        $Offset = 0,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/policy/rules?limit=" + $Limit
    }

    # Building the request body with given parameters

    $Body = @{
        sections = $Section -join ","
        protocols = $Protocol -join ","
        offset = $Offset
        search = $Search
        comments = $Comments
        ruleset = $Ruleset
        state = $State -join ","
        port = $Port -join ","
    }

    # Removing empty keys

    $RequestBody = Remove-EmptyKeys $Body

    # Legacy URI building

    ##### SOURCES #####

    $Uri += "&source="

    if ( $SourceLabel ) {
        $Uri += "labels:"
        foreach ($Group in $SourceLabel) {
            $Uri += $Group.id -join ">"
            $Uri += "|"
        }

        $Uri = $Uri.SubString(0,$Uri.length-1) #Remove trailing "|"

        $Uri += ","
    }

    if ( $SourceProcess ) {
        $Uri += "processes:"
        $Uri += $SourceProcess -join "|"

        $Uri += ","
    }

    if ( $SourceAsset ) {
        $Uri += "assets:"
        $Uri += $SourceAsset.id + "|"

        $Uri += ","
    }

    if ( $SourceSubnet ) {
        $Uri += "subnet:" + $SourceSubnet + ","
    }

    if ( $PSBoundParameters.ContainsKey("SourceInternet") ) { #checks for the existence of the parameter
        if ( $SourceInternet -eq $true ) {
            $Uri += "address_classification:Internet,"
        } elseif ( $SourceInternet -eq $false ) {
            $Uri += "address_classification:Private,"
        }
    }

    #If any above parameter was present, remove the trailing ","; if nothing above was present, remove "source="
    if ( $Uri.SubString($Uri.length-1) -eq "," ) {
        $Uri = $Uri.SubString(0,$Uri.length-1)
    } else {
        $Uri = $Uri.SubString(0,$Uri.length-8)
    }

    ###################

    ##### DESTINATIONS #####

    $Uri += "&destination="

    if ( $DestinationLabel ) {
        $Uri += "labels:"
        foreach ($Group in $DestinationLabel) {
            $Uri += $Group.id -join ">"

            $Uri += "|"
        }

        $Uri = $Uri.SubString(0,$Uri.length-1) #Remove trailing "|"

        $Uri += ","
    }

    if ( $DestinationProcesses ) {
        $Uri += "processes:"
        $Uri += $DestinationProcesses -join "|"

        $Uri += ","
    }

    if ( $DestinationAsset ) {
        $Uri += "assets:"
        $Uri += $DestinationAsset.id -join "|"

        $Uri += ","
    }

    if ( $DestinationSubnet ) {
        $Uri += "subnet:" + $DestinationSubnet + ","
    }

    if ( $PSBoundParameters.ContainsKey("DestinationInternet") ) {
        if ( $DestinationInternet -eq $true ) {
            $Uri += "address_classification:Internet,"
        } elseif ( $DestinationInternet -eq $false ) {
            $Uri += "address_classification:Private,"
        }
    }

    #If any above parameter was present, remove the trailing ","; if nothing above was present, remove "&destination="
    if ( $Uri.SubString($Uri.length-1) -eq "," ) {
        $Uri = $Uri.SubString(0,$Uri.length-1)
    } else {
        $Uri = $Uri.SubString(0,$Uri.length-13)
    }

    ########################

    ##### ANY SIDE #####

    $Uri += "&any_side="

    if ( $AnySideLabel ) {
        $Uri += "labels:"
        foreach ($Group in $AnySideLabel) {
                $Uri += $Group.id -join ">"
                $Uri += "|"
        }

        $Uri = $Uri.SubString(0,$Uri.length-1) #Remove trailing "|"

        $Uri += ","
    }

    if ( $AnySideProcesses ) {
        $Uri += "processes:"
        foreach ($Process in $AnySideProcesses) {
            $Uri += $Process + "|"
        }

        $Uri = $Uri.SubString(0,$Uri.length-1) #Remove trailing "|"

        $Uri += ","
    }

    if ( $AnySideAsset ) {
        $Uri += "assets:"
        $Uri += $AnySideAsset -join "|"

        $Uri += ","
    }

    if ( $AnySideSubnet ) {
        $Uri += "subnet:" + $AnySideSubnet + ","
    }

    if ( $PSBoundParameters.ContainsKey("AnySideInternet") ) { #checks for the existence of the parameter
        if ( $AnySideInternet -eq $true ) {
            $Uri += "address_classification:Internet,"
        } elseif ( $AnySideInternet -eq $false ) {
            $Uri += "address_classification:Private,"
        }
    }

    #If any above parameter was present, remove the trailing ","; if nothing above was present, remove "&any_side="
    if ( $Uri.SubString($Uri.length-1) -eq "," ) {
        $Uri = $Uri.SubString(0,$Uri.length-1)
    } else {
        $Uri = $Uri.SubString(0,$Uri.length-10)
    }

    ####################

    # Make the call

    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCPolicy"); $_}
    }
}

function Get-RawFlow {
    [cmdletbinding()]
    param (
        [DateTime]
        $StartTime,

        [DateTime]
        $EndTime,

        [System.Array]
        $SourceProcess,

        [System.Array]
        $DestinationProcess,

        [System.Array]
        $AnySideProcess,

        [System.Array]
        $SourceAsset,

        [System.Array]
        $DestinationAsset,

        [System.Array]
        $AnySideAsset,

        [System.Array]
        $SourceLabel,

        [System.Array]
        $DestinationLabel,

        [System.Array]
        $AnySideLabel,

        [Switch]
        $SourceInternet,

        [Switch]
        $DestinationInternet,

        [ValidateSet("successful","failed","redirected_to_hpvm")]
        $Type,

        [ValidateSet("allowed","alerted_by_management","blocked_by_management","blocked")]
        $Action,

        [Int32]
        $Limit = 20,

        [Int32]
        $Offset,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/connections?sort=slot_start_time"
    }

    # Handling default time values

    if ( -not $StartTime ) {
        $StartTime = $(Get-Date).AddHours(-1)
    }

    if ( -not $EndTime ) {
        $EndTime = Get-Date
    }

    # Building the request body


    $Body = @{
        from_time = (ConvertTo-GCUnixTime $StartTime)
        to_time = (ConvertTo-GCUnixTime $EndTime)
        connection_type = $Type -join ","
        policy_verdict = $Action -join ","
        offset = $Offset
        limit = $Limit
    }

    # Removing empty keys

    $RequestBody = Remove-EmptyKeys $Body

    # Legacy URI building

    ### Source ###

    if ( $SourceProcess -or $SourceAsset -or $SourceLabel -or $PSBoundParameters.ContainsKey("SourceInternat") ) {
        $Uri += "&source="
    }

    if ( $SourceInternet -eq $true ) {
        $Uri += "address_classification:Internet"
    } elseif ( $PSBoundParameters.ContainsKey("SourceInternet") -and ($SourceInternet -eq $false) ) {
        $Uri += "address_classification:Private"
    }

    if ( $SourceProcess ) {
        $Uri += "processes:"
        $Uri += $SourceProcess -Join ","
    }

    if ( $SourceAsset ) {
        $Uri += "assets:"
        $Uri += $SourceAsset.id -Join ","
    }

    if ( $SourceLabel ) { #2D array; outer group is OR, inner groups are AND
        $Uri += "labels:"
        foreach ($Group in $SourceLabel) {
            $Uri += $Group.id -Join ">"
            $Uri += "|"
        }

        $Uri = $Uri.SubString(0,$Uri.Length-1) #Removing last "|"
    }

    ### Destination ###

    if ( $DestinationProcess -or $DestinationAsset -or $DestinationLabel -or $PSBoundParameters.ContainsKey("DestinationInternet") ) {
        $Uri += "&destination="
    }

    if ( $DestinationInternet -eq $true ) {
        $Uri += "address_classification:Internet"
    } elseif ( $PSBoundParameters.ContainsKey("DestinationInternet") -and ($DestinationInternet -eq $false) ) {
        $Uri += "address_classification:Private"
    }

    if ( $DestinationProcess ) {
        $Uri += "processes:"
        $Uri += $DestinationProcess -Join ","
    }

    if ( $DestinationAsset ) {
        $Uri += "assets:"
        $Uri += $DestinationAsset.id -Join ","
    }

    if ( $DestinationLabel ) {
        $Uri += "labels:"
        foreach ($Group in $DestinationLabel) {
            $Uri += $Group.id -Join ">"
            $Uri += "|"
        }

        $Uri = $Uri.SubString(0,$Uri.Length-1) #Removing last "|"
    }

    ### Any Side ###

    if ( $AnySideProcess -or $AnySideAsset -or $AnySideLabel ) {
        $Uri += "&any_side="
    }

    if ( $AnySideProcess ) {
        $Uri += "processes:"
        $Uri += $AnySideProcess -Join ","
    }

    if ( $AnySideAsset ) {
        $Uri += "assets:"
        $Uri += $AnySideAsset.id -Join ","
    }

    if ( $AnySideLabel ) {
        $Uri += "labels:"
        foreach ($Group in $AnySideLabel) {
            $Uri += $Group.id -Join ">"
            $Uri += "|"
        }
        $Uri = $Uri.SubString(0,$Uri.Length-1) #Removing last "|"
    }

    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCRawFlow"); $_}
    }
}

function Get-SavedMap {
    [CmdletBinding()]
    param (
        [System.String]
        $Search,

        [ValidateSet("READY","IN_PROGRESS","QUEUED","CANCELLED","FAILED","EMPTY")]
        [System.String]
        $State,

        [ValidateSet("include_processes","time_resolution")]
        $Features,

        [PSTypeName("GCAsset")]
        $Asset,

        [PSTypeName("GCLabel")]
        $Label,

        [DateTime[]]
        $TimeRange,

        [System.String]
        $AuthorID,

        [Int32]
        $Limit = 20,

        [Int32]
        $Offset,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/saved-maps"
    }

    # Building request body from parameters

    $Body = @{
        author_id = $AuthorID -join ","
        state = $State -join ","
        features = $Features -join ","
        included_asset_ids = $Asset.id -join ","
        included_label_ids = $Label.id -join ","
        time_range_filter = ""
        search = $Search
        limit = $Limit
        offset = $Offset
    }

    # Weird parameter

    if ( $TimeRange ) {
        if ( $TimeRange.count -ne 2 ) {
            throw "Incorrect time range syntax"
        }

        $Range0 = $TimeRange[0] | ConvertTo-GCUnixTime
        $Range1 = $TimeRange[1] | ConvertTo-GCUnixTime

        $Body.time_range_filter = $Range0 + "," + $Range1
    }

    # Removing empty keys

    $RequestBody = Remove-EmptyKeys $Body

    if ( $Raw ) {
        pwsh-GC-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-GC-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCSavedMap"); $_}
    }
}

function Get-User {
    [cmdletbinding()]
    param (
        [String[]]
        $Name,

        [int]
        $Limit = 20,

        [int]
        $Offset,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/system/users"
    }

    $Body = @{
        username = $Name -join ","
        limit = $Limit
        offset = $Offset
    }

    $RequestBody = Remove-EmptyKeys $Body

    if ( $Raw ) {
        pwsh-gc-get-request -Raw -Uri $Uri -Body $RequestBody -ApiKey $Key
    } else {
        pwsh-gc-get-request -Uri $Uri -Body $RequestBody -ApiKey $Key | foreach {$_.PSTypeNames.Clear(); $_.PSTypeNames.Add("GCUser"); $_}
    }
}

function New-BlankLabel {
    [cmdletbinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory)]
        [String]
        $LabelKey,

        [Parameter(Mandatory)]
        [String]
        $LabelValue,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/labels"
    }

    $Body = [PSCustomObject]@{
        id = $null
        key = $LabelKey
        value = $LabelValue
        criteria = @()
    }

    $Should = $Body.key + ": " + $Body.value
    if ( $PSCmdlet.ShouldProcess($Should,"pwsh-GC-post-request on $Uri with $Key") ) {
        pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
    }
}

function New-DynamicLabel {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [System.String]
        $LabelKey,

        [System.String]
        $LabelValue,

        [System.String]
        $Argument,

        [ValidateSet("name","numeric_ip_addresses","id")]
        [System.String]
        $Field,

        [ValidateSet("STARTSWITH","ENDSWITH","EQUALS","CONTAINS","SUBNET","WILDCARDS")]
        [System.String]
        $Operation,

        [Parameter(ValueFromPipeline)]
        [Array]
        $Criteria,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
            $Uri = "/visibility/labels"
        }

        $Body = [PSCustomObject]@{
            id = $null
            key = $LabelKey
            value = $LabelValue
            criteria = @() #This is an array of "criteria objects" that can be specified by an array of these objects from the pipeline, via the $Criteria parameter. You can also create a rule with just a single criteria by directly specifying Argument, Field, and Operation.
        }
    }
    process {
        if ( -not ($LabelKey -and $LabelValue -and (($Argument -and $Field -and $Operation) -or $Criteria)) ) {
            throw "Parameters required: LabelKey, LabelValue, and either one or more Criteria objects, an Argument, Field, and Operation, or a label object"
        }
        if ( $Criteria ) {
            $Body.criteria += $Criteria
        } else {
            $Body.criteria += [PSCustomObject]@{
                argument = $Argument
                field = $Field
                op = $Operation
            }
        }
    }
    end {
        $Should = $Body.key + ": " + $Body.value
        if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-get-request -Raw -Uri $Uri -ApiKey $Key") ) {
            pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
        }
    }
}

function New-Policy {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateSet("allow","alert","block","override_allow","override_alert","override_block")]
        [string]
        $Section,

        [Parameter(Mandatory=$true)]
        [ValidateSet("allow","alert","block","block_and_alert")]
        [System.String]
        $Action,

        [ValidateSet("TCP","UDP")]
        [System.Array]
        $Protocol = @("TCP","UDP"),

        [ValidateRange(1,65535)]
        [System.Array]
        $Port,

        [System.Array]
        $PortRange,

        [System.Array]
        $SourceLabel,

        [System.Array]
        $DestinationLabel,

        [System.Array]
        $SourceProcesses,

        [System.Array]
        $DestinationProcesses,

        [System.Array]
        $SourceAsset,

        [System.Array]
        $DestinationAsset,

        [string[]]
        $SourceSubnet,

        [string[]]
        $DestinationSubnet,

        [System.String]
        $Ruleset,

        [System.String]
        $Comments,

        [Switch]
        $SourceInternet,

        [Switch]
        $DestinationInternet,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey  ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/policy/sections/" + $Section + "/rules"
    }

    $ordering_value = $null #Required to be $null by the API call
    $ruleset_id = $null #Required to be $null by the API call

    $Body = [PSCustomObject]@{
        ordering_value = $ordering_value
        rule = [PSCustomObject]@{
            ruleset_id = $ruleset_id
            port_ranges = @()
            ports = @()
            source = [PSCustomObject]@{}
            destination = [PSCustomObject]@{}
            ip_protocols = $Protocol
            action = $Action
        }
    }

    if ( $Port ) {
        $Body.rule.ports = $Port
    }

    if ( $PortRange ) {
        #The validation doesn't work in ValidateScript for some reason, so I'm just doing it here
        foreach ($P in $PortRange) {
            if ( $P.length -ne 2 ) {
                throw "Parameter PortRange: Each range must consist of starting and ending port"
            }

            foreach ($Port in $P) {
                if ( -not (($Port -is [int]) -and ($Port -gt 0) -and ($Port -lt 65536)) ) {
                    throw "Parameter PortRange: Ports may only be integers from 1 to 65535"
                }
            }

            if ( $P[1] -le $P[0] ) {
                throw "Parameter PortRange: Each range's end value must be greater than its start value"
            }

            $port_range = [PSCustomObject]@{
                start = $P[0]
                end = $P[1]
            }

            $Body.rule.port_ranges += $port_range
        }
    }

    if ( $SourceLabel ) {
        $temp = [PSCustomObject]@{}
        $Body.rule.source | Add-Member -MemberType NoteProperty -Name labels -Value $temp
        $Body.rule.source.labels | Add-Member -MemberType NoteProperty -Name or_labels -Value @()

        $or_labels = @()

        foreach ($Group in $SourceLabel) {
            $and_labels = [PSCustomObject]@{
                and_labels = @()
            }

            foreach ($Item in $Group) {
                $and_labels.and_labels += $Item.id
            }

            $or_labels += $and_labels
        }

        $Body.rule.source.labels.or_labels = $or_labels
    }

    if ( $DestinationLabel ) {
        $temp = [PSCustomObject]@{}
        $Body.rule.destination | Add-Member -MemberType NoteProperty -Name labels -Value $temp
        $Body.rule.destination.labels | Add-Member -MemberType NoteProperty -Name or_labels -Value @()

        $or_labels = @()

        foreach ($Group in $DestinationLabel) {
            $and_labels = [PSCustomObject]@{
                and_labels = @()
            }

            foreach ($Item in $Group) {
                $and_labels.and_labels += $Item.id
            }

            $or_labels += $and_labels
        }

        $Body.rule.destination.labels.or_labels = $or_labels
    }

    if ( $SourceProcesses ) {
        $Body.rule.source | Add-Member -MemberType NoteProperty -Name processes -Value $SourceProcesses
    }

    if ( $DestinationProcesses ) {
        $Body.rule.destination | Add-Member -MemberType NoteProperty -Name processes -Value $DestinationProcesses
    }

    if ( $SourceAsset ) {
        $Body.rule.source | Add-Member -MemberType NoteProperty -Name asset_ids -Value @($SourceAsset.id)
    }

    if ( $DestinationAsset ) {
        $Body.rule.destination | Add-Member -MemberType NoteProperty -Name asset_ids -Value @($DestinationAsset.id)
    }

    if ( $SourceSubnet ) {
        $Body.rule.source | Add-Member -MemberType NoteProperty -Name subnets -Value $SourceSubnet
    }

    if ( $DestinationSubnet ) {
        $Body.rule.destination | Add-Member -MemberType NoteProperty -Name subnets -Value $DestinationSubnet
    }

    if ( $PSBoundParameters.ContainsKey("SourceInternet") ) { #checks for the existence of the parameter
        if ( $SourceInternet -eq $true ) {
            $Body.rule.source | Add-Member -MemberType NoteProperty -Name address_classification -Value "Internet"
        } elseif ( $SourceInternet -eq $false ) {
            $Body.rule.source | Add-Member -MemberType NoteProperty -Name address_classification -Value "Private"
        }
    }

    if ( $PSBoundParameters.ContainsKey("DestinationInternet") ) {
        if ( $DestinationInternet -eq $true ) {
            $Body.rule.destination | Add-Member -MemberType NoteProperty -Name address_classification -Value "Internet"
        } elseif ( $DestinationInternet -eq $false ) {
            $Body.rule.destination | Add-Member -MemberType NoteProperty -Name address_classification -Value "Private"
        }
    }

    if ( $Comments ) {
        $Body.rule | Add-Member -MemberType NoteProperty -Name comments -Value $Comments
    }

    if ( $Ruleset ) {
        $Body.rule | Add-Member -MemberType NoteProperty -Name ruleset_name -Value $Ruleset
    }

    $Should = $Ruleset
    if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -ApiKey $Key") ) {
        pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
    }
}

function New-SavedMap{
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [System.String]
        $Name,

        [Switch]
        $Public,

        [HashTable]
        $FilterHashTableInclude,

        [HashTable]
        $FilterHashTableExclude,

        [DateTime]
        $StartTime,

        [DateTime]
        $EndTime,

        [System.Array]
        $TimeRange,

        [Switch]
        $IncludeProcesses,

        [Switch]
        $TimeResolution,

        [Switch]
        $EmailOnProgress,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/saved-maps"
    }

    # Building the request body based on parameters

    $Body = [PSCustomObject]@{
        name = ""
        map_type = 1
        filters = [PSCustomObject]@{}
        start_time_filter = $null
        end_time_filter = $null
        include_processes = $IncludeProcesses.IsPresent
        time_resolution = $TimeResolution.IsPresent
        email_on_progress = $EmailOnProgress.IsPresent
    }

    if ( -not $StartTime ) {
        $Body.start_time_filter = $($(Get-Date).AddHours(-1) | ConvertTo-GCUnixTime)
    } else {
        $Body.start_time_filter = $StartTime | ConvertTo-GCUnixTime
    }

    if ( -not $EndTime ) {
        $Body.end_time_filter = $(Get-Date | ConvertTo-GCUnixTime)
    } else {
        $Body.end_time_filter = $EndTime | ConvertTo-GCUnixTime
    }

    if ( $Name ) {
        $Body.name = $Name
    }

    if ( $Public ) {
        $Body.map_type = 0
    }

    if ( $FilterHashTableInclude ) {
        $temp = [PSCustomObject]@{}
        $Body.filters | Add-Member -MemberType NoteProperty -Name include -Value $temp
        foreach ($Hash in $FilterHashTableInclude.Keys) {
            $Body.filters.include | Add-Member -MemberType NoteProperty -Name $Hash -Value @($FilterHashTableInclude[$Hash])
        }
    }

    if ( $FilterHashTableExclude ) {
        $temp = [PSCustomObject]@{}
        $Body.filters | Add-Member -MemberType NoteProperty -Name exclude -Value $temp
        foreach ($Hash in $FilterHashTableExclude.Keys) {
            $Body.filters.exclude | Add-Member -MemberType NoteProperty -Name $Hash -Value @($FilterHashTableExclude[$Hash])
        }
    }

    if ( $TimeRange ) {
        if ( $TimeRange.count -ne 2 ) {
            throw "Incorrect time range syntax"
        }

        $Start = $TimeRange[0] | ConvertTo-GCUnixTime
        $End = $TimeRange[1] | ConvertTo-GCUnixTime

        $Body.start_time_filter = $Start
        $Body.end_time_filter = $End
    }

    $Should = $Name
    if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -ApiKey $Key") ) {
        pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
    }
}

function New-StaticLabel {

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)]
        [PSTypeName("GCAsset")]
        $Asset,

        [Parameter(Mandatory)]
        [System.String]
        $LabelKey,

        [Parameter(Mandatory)]
        [System.String]
        $LabelValue,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
            $Uri = "/assets/labels/" + $LabelKey + "/" + $LabelValue
        }

        $Body = [PSCustomObject]@{
            "vms" = @()
        }
    }
    process {
        if ( $Asset ) {
            $Body.vms += foreach ($ThisAsset in $Asset) {
                $ThisAsset.id
            }
        }
    }
    end {
        $Should = $LabelKey + ": " + $LabelValue
        if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -ApiKey $Key") ) {
            pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
        }
    }
}

function New-User {
    [cmdletbinding(SupportsShouldProcess)]

    param (
        [Parameter(Mandatory)]
        [String]
        $Name,

        [String]
        $Description,

        [String]
        $Email,

        [Parameter(Mandatory)]
        [String[]]
        $Permissions,

        [Switch]
        $TwoFactor,

        [Parameter(Mandatory)]
        [String]
        $Password,

        [Switch]
        $IncidentPasswordAccess,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/system/user"
    }

    if ( -not $Description ) {
        $Description = "Created by the API"
    }

    $Body = [PSCustomObject]@{
        action = "create"
        can_access_passwords = $IncidentPasswordAccess.IsPresent
        description = $Description
        email = $Email
        password = $Password
        password_confirm = $Password
        permission_scheme_ids = @($Permissions)
        two_factor_auth_enabled = $TwoFactor.IsPresent
        username = $Name
    }

    $Should = $Name
    if ( $PSCmdlet.ShouldProcess($Should, "pwsh-gc-post-request -Raw -Uri $Uri -ApiKey $Key") ) {
        pwsh-gc-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
    }
}

function Publish-Policy {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [System.String]
        $Comments,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    if ( GCApiKey-present $ApiKey ) {
        if ( $ApiKey ) {
            $Key = $ApiKey
        } else {
            $Key = $global:GCApiKey
        }
        $Uri = "/visibility/policy/revisions"
    }

    # Building the request body from parameters

    $Body = [PSCustomObject]@{
        action = "publish"
        comments = $Comments
    }

    $Should = $Body.action
    if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -ApiKey $Key") ) {
        pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
    }
}

function Remove-Label {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)]
        [PSTypeName("GCLabel")]
        $Label,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
        }
    }
    process {
        foreach ($ThisLabel in $Label) {
            $Uri = "/visibility/labels/" + $ThisLabel.id
            $Should = $Uri
            if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-delete-request -Uri $Uri -ApiKey $Key") ) {
                pwsh-GC-delete-request -Uri $Uri -ApiKey $Key
            }
        }
    }
}

function Remove-Policy {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)]
        [System.Array]
        $Policy,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
        }

        $Body = [PSCustomObject]@{
            action = "delete"
        }
    }
    process {
        foreach ($ThisPolicy in $Policy) {
            $Uri = "/visibility/policy/rules/" + $ThisPolicy.id

            $Should = $Uri
            if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key") ) {
                pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
            }
        }
    }
}

function Remove-SavedMap {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)]
        [PSTypeName("GCSavedMap")]
        $Map,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
        }

        $Body = [PSCustomObject]@{
            action = "delete"
        }
    }
    process {
        foreach ($ThisMap in $Map) {
            $Uri = "/visibility/saved-maps/" + $ThisMap.id

            $Should = [string]$Uri
            if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key") ) {
                pwsh-GC-post-request -Raw -Uri $Uri -Body $Body -ApiKey $Key
            }
        }
    }
}

function Remove-User {
    [cmdletbinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [String[]]
        $username,

        [Switch]
        $Raw,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
            $Uri = "/system/user"
        }
    }

    process {
        foreach ( $ThisUser in $Username ) {
            $Body = [PSCustomObject]@{
                action = "delete"
                confirm = $true
                username = $ThisUser
            }

            $Should = $username
            if ( $PSCmdlet.ShouldProcess($Should, "pwsh-gc-post-request -Uri $Uri -Body $Body -ApiKey $Key -Raw:$Raw.IsPresent") ) {
                pwsh-gc-post-request -Uri $Uri -Body $Body -ApiKey $Key -Raw:$Raw.IsPresent
            }
        }
    }
}

function Set-Label {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(ValueFromPipeline)]
        [PSTypeName("GCLabel")]
        $Label
    )

    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
        }
    }

    process {
        foreach ( $ThisLabel in $Label ) {
            $Uri = "/visibility/labels/" + $ThisLabel.id
            $RequestBody = $ThisLabel | Select-Object -ExcludeProperty id,_id

            $Should = $Uri
            if ( $PSCmdlet.ShouldProcess($Should, "pwsh-GC-post-request -Raw -Uri $Uri -Method Put -ApiKey $Key") ) {
                pwsh-GC-post-request -Raw -Uri $Uri -Body $RequestBody -Method Put -ApiKey $Key
            }
        }
    }
}

function Set-Password {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [Parameter(ValueFromPipeline)]
        [Alias("Username","User")]
        [PSTypeName("GCUser")]$Name,

        [Parameter(Mandatory)]
        [String]$Password,

        [PSTypeName("GCApiKey")]$ApiKey
    )

    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
            $Uri = "/system/user"
        }
    }

    process {
        foreach ( $ThisUser in $User ) {
            # Serialize/deserialize
            $RequestUser = $ThisUser | ConvertTo-Json -Depth 2 | ConvertFrom-Json

            $RequestUser | Add-Member -MemberType NoteProperty -Name "action" -Value "update"
            $RequestUser | Add-Member -MemberType NoteProperty -Name "password" -Value ($Password | ConvertFrom-SecureString)
            $RequestUser | Add-Member -MemberType NoteProperty -Name "password_confirm" -Value $Password
            $RequestBody = $RequestUser | Select-Object -Property action,can_access_passwords,description,email,id,permission_scheme_ids,two_factor_auth_enabled,username,password,password_confirm

            $Should = $ThisUser.username
            if ( $PSCmdlet.ShouldProcess($Should,"pwsh-GC-post-request -Raw -Uri $Uri -Method Post -ApiKey $Key") ) {
                pwsh-GC-post-request -Raw -Uri $Uri -Body $RequestBody -Method Post -ApiKey $Key
            }
        }
    }
}

function Set-Policy {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)]
        [PSTypeName("GCPolicy")]
        $Policy,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )
    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
        }
    }
    process {
        foreach ($ThisPolicy in $Policy) {
            # Serialize/deserialize data
            $PCopy = $ThisPolicy | ConvertTo-Json -Depth 99 | ConvertFrom-Json
            $Uri = "/visibility/policy/rules/" + $PCopy.id

            # Have to parse the source/destination labels to only contain IDs,
            # instead of all the other info that they come with from Get-GCLabel
            # Passing that extra info to the API errors out,
            # because this api call is just like the one for creating new policy,
            # and only uses label IDs for the source/destination

            if ( $PCopy.source.labels ) {
                $OrCount = $PCopy.source.labels.or_labels.count
                for ($i = 0; $i -lt $OrCount; $i++) {
                    $AndCount = $PCopy.source.labels.or_labels[$i].and_labels.count
                    for ($j = 0; $j -lt $AndCount; $j++) {
                        $temp = $PCopy.source.labels.or_labels[$i].and_labels[$j].id
                        $PCopy.source.labels.or_labels[$i].and_labels[$j] = $temp
                    }
                }
            }

            if ( $PCopy.destination.labels ) {
                $OrCount = $PCopy.destination.labels.or_labels.count
                for ($i = 0; $i -lt $OrCount; $i++) {
                    $AndCount = $PCopy.destination.labels.or_labels[$i].and_labels.count
                    for ($j = 0; $j -lt $AndCount; $j++) {
                        $temp = $PCopy.destination.labels.or_labels[$i].and_labels[$j].id
                        $PCopy.destination.labels.or_labels[$i].and_labels[$j] = $temp
                    }
                }
            }

            $RequestBody = $PCopy

            $Should = $RequestBody.ruleset_name
            if ( $PSCmdlet.ShouldProcess($Should,"pwsh-GC-post-request -Raw -Uri $Uri -Method Put -ApiKey $Key") ) {
                pwsh-GC-post-request -Raw -Uri $Uri -Body $RequestBody -Method Put -ApiKey $Key
            }
        }
    }
}

function Set-User {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [Parameter(ValueFromPipeline)]
        [PSTypeName("GCUser")]
        $User,

        [PSTypeName("GCApiKey")]
        $ApiKey
    )

    begin {
        if ( GCApiKey-present $ApiKey ) {
            if ( $ApiKey ) {
                $Key = $ApiKey
            } else {
                $Key = $global:GCApiKey
            }
            $Uri = "/system/user"
        }
    }

    process {
        foreach ( $ThisUser in $User ) {
            # Serialize/deserialize
            $RequestUser = $ThisUser | ConvertTo-Json -Depth 2 | ConvertFrom-Json

            $RequestUser | Add-Member -MemberType NoteProperty -Name "action" -Value "update"
            $RequestBody = $RequestUser | Select-Object -Property action,can_access_passwords,description,email,id,permission_scheme_ids,two_factor_auth_enabled,username

            $Should = $ThisUser.username
            if ( $PSCmdlet.ShouldProcess($Should,"pwsh-GC-post-request -Raw -Uri $Uri -Body $RequestBody -Method Post -ApiKey $Key") ) {
                pwsh-GC-post-request -Raw -Uri $Uri -Body $RequestBody -Method Post -ApiKey $Key
            }
        }
    }
}

function GCApiKey-present {
    [cmdletbinding()]

    param (
        $ApiKey
    )

    if ( -not ($ApiKey -or $global:GCApiKey) ) {
        throw "No API key present."
    } else {
        return $true
    }
}

function pwsh-GC-delete-request {
    [cmdletbinding()]

    param (
        [Parameter(Mandatory)]
        [String]$Uri,

        [Parameter(Mandatory)]
        [PSTypeName("GCApiKey")]$ApiKey
    )
    
    $RequestToken = $ApiKey.Token | ConvertTo-SecureString -AsPlainText -Force
    $RequestUri = $ApiKey.Uri + $Uri

    try {
        Invoke-RestMethod -Uri $RequestUri -Method Delete -Authentication Bearer -Token $RequestToken
    }
    catch {
        throw $_.Exception
    }
}

function pwsh-GC-get-request {
    [cmdletbinding()]

    param (
        [Parameter(Mandatory)]
        [String]$Uri,

        [HashTable]$Body,

        [Parameter(Mandatory)]
        [PSTypeName("GCApiKey")]$ApiKey,

        [Switch]$Raw
    )
    
    begin {
        $RequestToken = $ApiKey.Token | ConvertTo-SecureString -AsPlainText -Force
        $RequestUri = $ApiKey.Uri + $Uri
    }
    
    process {
        $Request = try {
            Invoke-RestMethod -Uri $RequestUri -Method Get -Body $Body -Authentication Bearer -Token $RequestToken
        }
        catch {
            throw $_.Exception
        }
    
        switch ($Raw) {
            $true {
                $Request
            }
    
            default {
                if ( $Request.objects ) {
                    $Request.objects
                } 
            }
        }
    }
}

function pwsh-GC-post-request {
    param (
        [Parameter(Mandatory)]
        [String]$Uri,
    
        [PSCustomObject]$Body,

        [Parameter(Mandatory)]
        [PSTypeName("GCApiKey")]$ApiKey,

        [ValidateSet("Post","Put")][String]$Method = "Post",
    
        [Switch]$Raw
    )
    
    $RequestToken = $ApiKey.Token | ConvertTo-SecureString -AsPlainText -Force
    $RequestUri = $ApiKey.Uri + $Uri
    $RequestBody = $Body | ConvertTo-Json -Depth 10

    $Request = try {
        Invoke-RestMethod -Uri $RequestUri -Method $Method -Body $RequestBody -ContentType "application/json" -Authentication Bearer -Token $RequestToken
    }
    catch {
        throw $_.Exception
    }

    switch ($Raw) {
        $true {
            $Request
        }

        default {
            if ( $Request.objects ) {
                $Request.objects
            }
        }
    }
}

function Remove-EmptyKeys {
    param (
        $Body
    )

    $KeyList = New-Object -TypeName System.Collections.Generic.List[string]

    foreach ($HashKey in $Body.Keys) {
        $KeyList.Add($HashKey)
    }

    foreach ($HashKey in $KeyList) {
        if ([string]::isNullOrEmpty($Body[$HashKey])) {
            $Body.Remove($HashKey)
        }
    }

    $Body
}

Set-Alias -Name api -value Get-GCApiKey

Export-ModuleMember -Function ConvertFrom-UnixTime,ConvertTo-UnixTime,Get-Agent,Get-Aggregator,Get-ApiKey,Get-Asset,Get-Collector,Get-Incident,Get-Label,Get-LabelGroup,Get-Policy,Get-RawFlow,Get-SavedMap,Get-User,New-BlankLabel,New-DynamicLabel,New-Policy,New-SavedMap,New-StaticLabel,New-User,Publish-Policy,Remove-Label,Remove-Policy,Remove-SavedMap,Remove-User,Set-Label,Set-Password,Set-Policy,Set-User -Alias api