IdentityCommand.DiscoveryMgmt.psm1

function ConvertTo-DMFilterString {
    <#
    .SYNOPSIS
    Assembles a Discovery Management filter expression.
 
    .DESCRIPTION
    Formats field/value pairs into the filter expression accepted by the Discovery Management
    service. Each pair becomes an equality clause, and clauses are joined with AND.
 
        type eq SomeType AND recurrenceType eq SomeRecurrence
 
    A value containing whitespace is quoted, and a quote character within a quoted value is escaped
    with a backslash - both handled by ConvertTo-FilterClause. Callers pass raw values and never
    pre-quote. Clauses are ordered by field name.
 
    The expression is not url encoded - ConvertTo-QueryString encodes the query string as a whole.
 
    .PARAMETER Filter
    Hashtable of field names and the values to compare them against.
 
    .EXAMPLE
    ConvertTo-DMFilterString -Filter @{ type = 'SomeType' }
 
    Outputs: type eq SomeType
 
    .EXAMPLE
    ConvertTo-DMFilterString -Filter @{ type = 'SomeType'; lastInstanceStatus = 'Some Status' }
 
    Outputs: lastInstanceStatus eq "Some Status" AND type eq SomeType
 
    .OUTPUTS
    String
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true
        )]
        [AllowEmptyCollection()]
        [hashtable]$Filter
    )

    process {

        $Clauses = foreach ($Field in ($Filter.Keys | Sort-Object)) {

            ConvertTo-FilterClause -Field $Field -Operator eq -Value $Filter[$Field]

        }

        if (@($Clauses).Count -gt 0) {

            $Clauses -join ' AND '

        }

    }

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Add-DMDiscoveryScanDefinition {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$name,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet(
            'WIN_NIX_LIST', 'WIN_NIX_DOMAIN', 'POSTGRESQL', 'MYSQL', 'ORACLE', 'SQL_SERVER', 'INVENTORY_WIN_NIX_LIST'
        )]
        [string]$type,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable]$properties,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('AWS', 'AZURE', 'ON_PREM')]
        [string]$locationType,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable[]]$credentials,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$networkId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string[]]$tags,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('IMMEDIATE', 'SCHEDULED', 'RECURRING')]
        [string]$recurrenceType,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable]$recurrenceData,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ACTIVE', 'INACTIVE')]
        [string]$State

    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-definitions"

        #Get bound parameters to include in request
        $BoundParameters = $PSBoundParameters | Get-Parameter

        if ($BoundParameters.ContainsKey('State')) {

            #PowerShell hashtables are case-insensitive, so "State" & "state" are the same key.
            #The value must be removed before being re-added, otherwise the API's expected
            #lowercase "state" property name is never actually applied to the stored key.
            $StateValue = $BoundParameters['State']
            $BoundParameters.Remove('State')
            $BoundParameters['state'] = $StateValue

        }

        #Create Request Body
        $Body = $BoundParameters | ConvertTo-Json -Depth 10

        if ($PSCmdlet.ShouldProcess($name, 'Add Scan Definition')) {

            #Send request to web service
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $Body

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Add-DMDiscoveryScanResource {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('WIN_NIX_LIST', 'SQL_SERVER', 'ORACLE', 'MYSQL', 'POSTGRESQL', 'INVENTORY_WIN_NIX_LIST')]
        [string]$type,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$name,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable]$content,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('AWS', 'AZURE')]
        [string]$locationType
    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/discovery-scans/resources"

        #Get bound parameters to include in request
        $BoundParameters = $PSBoundParameters | Get-Parameter

        #Ensure a single machine list entry is not collapsed to a JSON object instead of an array
        if ($BoundParameters['content'].ContainsKey('machineList')) {

            $BoundParameters['content']['machineList'] = $BoundParameters['content']['machineList'] -as [array]

        }

        #Create Request Body
        $Body = $BoundParameters | ConvertTo-Json -Depth 10

        if ($PSCmdlet.ShouldProcess($name, 'Add Discovery Scan Resource')) {

            #Send request to web service
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $Body

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Get-DMDiscoveryScan {
    [CmdletBinding()]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$id
    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-instances/$id"

        #Send request to web service
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $Result) {

            $Result

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Get-DMDiscoveryScanDefinition {
    [CmdletBinding(DefaultParameterSetName = 'GetAllScanDefinitions')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byID'
        )]
        [string]$id,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [string]$search,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [ValidateSet(
            'creationTime', 'updateTime', 'name', 'type', 'recurrenceType',
            'lastInstanceStatus', 'lastInstanceCreationTime'
        )]
        [string]$sort,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [ValidateSet('asc', 'desc')]
        [string]$sortDirection,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [string]$type,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [string]$recurrenceType,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [string]$lastInstanceStatus,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [bool]$extendedDetails,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'GetAllScanDefinitions'
        )]
        [ValidateSet(
            'ACTIVE', 'INACTIVE'
        )]
        [string]$State

    )

    begin {

        #Parameter to include as filter value in url
        $Parameters = [Collections.Generic.List[String]]@('lastInstanceStatus', 'recurrenceType', 'type')

    }

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-definitions"

        switch ($PSCmdlet.ParameterSetName) {

            'byID' {

                $URI = "$URI/$id"

                break

            }

            'GetAllScanDefinitions' {

                #Sort properties which are only valid when extendedDetails is requested
                $ExtendedOnlySortProperties = @('lastInstanceStatus', 'lastInstanceCreationTime')

                if ($PSBoundParameters.ContainsKey('sort') -and ($ExtendedOnlySortProperties -contains $sort) -and ($extendedDetails -ne $true)) {

                    throw "Sort property '$sort' is only valid when -extendedDetails is set to `$true"

                }

                if ($PSBoundParameters.ContainsKey('sort') -or $PSBoundParameters.ContainsKey('sortDirection')) {

                    if ($PSBoundParameters.ContainsKey('sort')) {
                        $sortProperty = $sort
                    } else {
                        $sortProperty = 'creationTime'   #default sort property
                    }

                    if ($PSBoundParameters.ContainsKey('sortDirection')) {
                        $direction = $sortDirection
                    } else {
                        $direction = 'asc'   #default sort direction
                    }

                    #Append sort direction to sort property for correct query string creation
                    $PSBoundParameters['sort'] = "$sortProperty $direction"

                }

                #Get Parameters to include in request
                $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove ($Parameters + 'sortDirection')
                $filterParameters = $PSBoundParameters | Get-Parameter -ParametersToKeep $Parameters
                $FilterString = ConvertTo-DMFilterString -Filter $filterParameters

                # Append filter string if present
                if ($null -ne $FilterString) {

                    $boundParameters['filter'] = $FilterString

                }

                #Create Query String, escaped for inclusion in request URL
                $queryString = $boundParameters | ConvertTo-QueryString

                if ($null -ne $queryString) {

                    #Build URL from base URL
                    $URI = "$URI`?$queryString"

                }

            }

        }

        #Send request to web service
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $Result) {

            switch ( $PSCmdlet.ParameterSetName ) {

                'byID' {

                    #Single ruleset returned
                    $Output = $result

                    break

                }

                'GetAllScanDefinitions' {

                    #Multiple rulesets returned
                    $Output = $result.Items

                    break
                }

            }

            $Output

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Get-DMDiscoveryScanLog {
    [CmdletBinding()]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$id
    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-instances/$id/logs/url/"

        #Send request to web service
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $Result) {

            $Result

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Get-DMDiscoveryScanResource {
    [CmdletBinding()]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$id
    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/discovery-scans/resources/$id"

        #Send request to web service
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $Result) {

            $Result

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function New-DMDiscoveryScanResourceCloudObject {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'AWS')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AWS'
        )]
        [string]$account,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AWS'
        )]
        [string[]]$vpcs,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AZURE'
        )]
        [string]$subscription_id,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AZURE'
        )]
        [string[]]$resource_groups,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AZURE'
        )]
        [string[]]$vnet_ids,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AWS'
        )]
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AZURE'
        )]
        [string[]]$regions,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AWS'
        )]
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'AZURE'
        )]
        [string[]]$cloud_tags
    )

    process {

        #Get bound parameters to include in the returned object
        $boundParameters = $PSBoundParameters | Get-Parameter

        if ($PSCmdlet.ParameterSetName -eq 'AWS') {
            $ShouldProcessTarget = $account
        } else {
            $ShouldProcessTarget = $subscription_id
        }

        if ($PSCmdlet.ShouldProcess($ShouldProcessTarget, 'Create Discovery Scan Resource Cloud Object')) {

            $boundParameters

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function New-DMDiscoveryScanResourceMachineObject {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$Address,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$Username,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$Database,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [int]$Port,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$Domain,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string[]]$Tags,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$Instances
    )

    process {

        #Get bound parameters to include in the returned object
        $boundParameters = $PSBoundParameters | Get-Parameter

        if ($PSCmdlet.ShouldProcess($Address, 'Create Discovery Scan Resource Machine Object')) {

            $boundParameters

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Remove-DMDiscoveryScanDefinition {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$id
    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-definitions/$id"

        if ($PSCmdlet.ShouldProcess($id, 'Remove Scan Definition')) {

            #Send Delete request to web service
            Invoke-IDRestMethod -Uri $URI -Method DELETE

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Set-DMDiscoveryScanDefinition {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$id,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$name,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet(
            'WIN_NIX_LIST', 'WIN_NIX_DOMAIN', 'POSTGRESQL', 'MYSQL', 'ORACLE', 'SQL_SERVER', 'INVENTORY_WIN_NIX_LIST'
        )]
        [string]$type,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable]$properties,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('AWS', 'AZURE', 'ON_PREM')]
        [string]$locationType,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable[]]$credentials,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$networkId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string[]]$tags,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('IMMEDIATE', 'SCHEDULED', 'RECURRING')]
        [string]$recurrenceType,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [hashtable]$recurrenceData,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ACTIVE', 'INACTIVE')]
        [string]$State

    )

    begin {

        #Properties which can be sent in a PUT request, and which can be backfilled from the
        #current scan definition when not specified, so they are not cleared by the request
        #Named distinctly from the -properties parameter: PowerShell variable names are
        #case-insensitive, so a variable named $Properties here would be the same variable as
        #the -properties parameter and would silently overwrite its bound value
        $MergeableProperties = [Collections.Generic.List[String]]@('name', 'type', 'properties', 'locationType', 'credentials',
            'networkId', 'tags', 'recurrenceType', 'recurrenceData', 'state')

    }

    process {

        #Get bound parameters to include in request
        $BoundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove id

        #Get current scan definition property values
        $ExistingScanDefinition = Get-DMDiscoveryScanDefinition -id $id

        if ($null -ne $ExistingScanDefinition) {

            #For any property not included in the request, but present on the existing scan definition,
            #add the existing property value to the request unchanged, so it is not cleared by the PUT request.
            #Values are copied as-is, without flattening, since properties/credentials/recurrenceData are
            #nested objects/arrays whose shape already matches what the API expects in the request body.
            $MergeableProperties | ForEach-Object {

                if ((-not $BoundParameters.ContainsKey($PSItem)) -and ($null -ne $ExistingScanDefinition.$PSItem)) {

                    $BoundParameters[$PSItem] = $ExistingScanDefinition.$PSItem

                }

            }

        }

        if ($BoundParameters.ContainsKey('State')) {

            #PowerShell hashtables are case-insensitive, so "State" & "state" are the same key.
            #The value must be removed before being re-added, otherwise the API's expected
            #lowercase "state" property name is never actually applied to the stored key.
            $StateValue = $BoundParameters['State']
            $BoundParameters.Remove('State')
            $BoundParameters['state'] = $StateValue

        }

        #Ensure array properties are not collapsed to a single value
        'tags', 'credentials' | ForEach-Object {

            if ($BoundParameters.ContainsKey($PSItem)) {

                $BoundParameters[$PSItem] = $BoundParameters[$PSItem] -as [array]

            }

        }

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-definitions/$id"

        #Create Request Body
        $Body = $BoundParameters | ConvertTo-Json -Depth 10

        if ($PSCmdlet.ShouldProcess($id, 'Update Scan Definition')) {

            #Send request to web service
            Invoke-IDRestMethod -Uri $URI -Method PUT -Body $Body

        }

    }

    end {}

}

# .ExternalHelp IdentityCommand.DiscoveryMgmt-help.xml
function Start-DMDiscoveryScan {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$id
    )

    process {

        #Create URL for Request
        $URI = "$(Resolve-RequestUrl)/api/scan-instances"

        $Body = $PSBoundParameters | Get-Parameter | ConvertTo-Json

        if ($PSCmdlet.ShouldProcess($id, 'Start Discovery Scan')) {

            #Send Delete request to web service
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $Body

        }

    }

    end {}

}

#Copy IdentityCommand's private helpers into this module: this module's functions call them.
#Each copy is created from the function definition, so it runs in this module's scope and uses this
#module's $ISPSSSession, whether IdentityCommand loaded from source or from its combined psm1.
#Resolve a single IdentityCommand module: with more than one version loaded, Get-Module returns
#an array.
$Module = Get-Module -Name IdentityCommand | Sort-Object Version -Descending | Select-Object -First 1

if ($null -eq $Module) {
    throw 'The IdentityCommand module is not loaded. Import IdentityCommand and try again.'
}

& $Module { Get-ChildItem -Path Function: } |

    Where-Object { $_.ModuleName -eq $Module.Name -and -not $Module.ExportedFunctions.ContainsKey($_.Name) } |

    ForEach-Object {

        . ([scriptblock]::Create("function $($_.Name) {$($_.Definition)}"))

    }

# Script scope session object for session data
$ISPSSSession = [ordered]@{
    tenant_url         = $null
    User               = $null
    TenantId           = $null
    SessionId          = $null
    Subdomain          = $null
    WebSession         = $null
    StartTime          = $null
    ElapsedTime        = $null
    LastCommand        = $null
    LastCommandTime    = $null
    LastCommandResults = $null
    LastError          = $null
    LastErrorTime      = $null
} | Add-CustomType -Type IdCmd.Session

New-Variable -Name ISPSSSession -Value $ISPSSSession -Scope Script -Force

#Platform discovery key of the service, used to resolve the service URL from the IdentityCommand session
New-Variable -Name ServiceKey -Value 'dms' -Scope Script -Force